03.01.2012 21:10
Pasek Wyszukiwania we Flash'u [podstawowy]
Szczęśliwego Nowego Roku 2012!
Często w Internecie widuje pytanie o to jak stworzyć pasek wyszukiwania w filmiku (podobnie jak ma to większość standardowych odtwarzaczy wideo, np. na YouTube) jednak ostatecznie niewiele z tych osób go implementuje - dlaczego tak jest? Okazuje się, że problem leży w sposobie obsługi MovieClip'ów przez Flash'a, dlatego za nim przejdę dalej, poświęcę parę słów właśnie nim.
Może trochę trudno to sobie wyobrazić, ale każdy MovieClip w animacje to tak naprawdę osobny filmik który ma swoją linię czasu (i ustawienia) i kontrolowany jest całkowicie nie zależnie od pozostałych, co najlepiej zobrazuje poniższy przykład (wybierz dowolny moment, naciśnij prawy klawisz myszki i odhacz "odtwarzaj"):
Jak można byłoby się tego spodziewać, cała animacja zostanie natychmiast zatrzymana... poza piłką która jest MovieClip'em. Nie ma co się zagłębiać dlaczego tak jest - po prostu jest i nigdy się nie zmieni. Pozostaje nam jedynie szukać alternatywnych rozwiązań, a ponieważ poradzenie sobie z zatrzymywaniem MovieClip'ów bezpośrednio pomoże nam przy tworzeniu paska wyszukiwania dobrze jest przyjrzeć dwóm możliwym obejściom tego problemu:
1. Pozbycie się wszystkich MovieClip'ów w animacji.
2. Ręczne zatrzymanie każdego z MóvieClip'ów za pomocą ActionScript.
Pierwsze rozwiązanie jest idealna, o ile oczywiście dopiero zaczynasz tworzyć swoją animacje, bo wtedy łatwiej jest ją odpowiednio przygotować niż później konwertować wszystkie MovieClipy na obiekty Graphics. A mając wszystko w graphics można zwyczajnie wywołać metodę gotoAndPlay by przenieść się w dowolne miejsce w animacji, co jest dokładnie tym czego potrzebujemy w pasku wyszukiwania. Drugie rozwiązanie ma sens tylko przy niewielkiej ilości MovieClip'ów, ponieważ wyszukanie i zatrzymanie dużej ich liczby może być zbyt obciążające dla procesora, szczególnie, że w przypadku paska wyszukiwania oznaczało by to przeszukanie CAŁEGO filmiku. Ale cóż, czasem po prostu nie ma innego wyjścia.
Kod odpowiedzialny za zatrzymanie wszystkich MovieClip'ów jest właściwie całkiem prosty:
Jak widać przy przewijaniu MovieClip'y zdają się żyć własnym życiem.
Dobra, ale dlaczego by nie odjąć od docelowej pozycji klatki w której pojawia się MovieClip? Jeśli chcemy przejść do 15 klatki animacji a MovieClip pojawia się na 10 to odejmujemy te 10 od 15 i wywołujemy gotoAndPlay(5) - główna linia czasu byłaby na 15 klace a MovieClip na 5-tej. Zgadza się, takie rozwiązanie byłoby idealne, ale haczyk w tym, że nie mamy skąd wziąć początkowej pozycji MovieClip'a. Tutaj właśnie pojawia się owe wyzwanie dla procesora o którym wspomniałem wcześniej, ponieważ kolejnym krokiem byłoby wyszukanie i spisanie tych wszystkich początkowych pozycji MovieClip'ów; tylko problem w tym, że przy standardowej animacji trwającej minutę i odtwarzanej przy prędkości 20 klatek na sekundę, do sprawdzenia będzie 1200 klatek! A jeśli do tego jeszcze dodamy np. 10 MovieClip'ów na klatkę... Dość powiedzieć, że jest to mało praktyczne rozwiązanie, dlatego w dalszej części artykułu zakładam, że animacje są w całości przygotowane na obiektach grafik.
Tyle teorii, czas stworzyć pasek wyszukiwania - zakładając absolutne minimum, będziemy potrzebować dwa MovieClipy: pasek reprezentujący długość animacji oraz wskaźnik aktualnej pozycji. Dla ułatwienia oba połóż na głównej linii czasu osobnego MovieClip'a (cały pasek będzie w jednym miejscu co ułatwi np. jego kopiowanie między projektami) i nazwij je mcBar oraz mcMarker (nazwy instancji). Pozostaje jeszcze tylko wstawić poniższy kod na główną linię czasu MovieClip'a przechowującego nasz pasek wyszukiwania:
Całość można pobrać stąd: seekbar.zip
A dla tych wszystkich którzy chcą spróbować rozwiązania z przeszukiwaniem MovieClip'ów przygotowałem specjalną klasę oraz przykład jak ją wykorzystać: seekbarEX.zip. W tym wypadku, żeby nie wpłynąć na filmik, MovieClip w którym umieściliśmy pasek i znacznik będzie eksportowany do ActionScript'u klasą ForusSeekBar.
Może trochę trudno to sobie wyobrazić, ale każdy MovieClip w animacje to tak naprawdę osobny filmik który ma swoją linię czasu (i ustawienia) i kontrolowany jest całkowicie nie zależnie od pozostałych, co najlepiej zobrazuje poniższy przykład (wybierz dowolny moment, naciśnij prawy klawisz myszki i odhacz "odtwarzaj"):
Jak można byłoby się tego spodziewać, cała animacja zostanie natychmiast zatrzymana... poza piłką która jest MovieClip'em. Nie ma co się zagłębiać dlaczego tak jest - po prostu jest i nigdy się nie zmieni. Pozostaje nam jedynie szukać alternatywnych rozwiązań, a ponieważ poradzenie sobie z zatrzymywaniem MovieClip'ów bezpośrednio pomoże nam przy tworzeniu paska wyszukiwania dobrze jest przyjrzeć dwóm możliwym obejściom tego problemu:
1. Pozbycie się wszystkich MovieClip'ów w animacji.
2. Ręczne zatrzymanie każdego z MóvieClip'ów za pomocą ActionScript.
Pierwsze rozwiązanie jest idealna, o ile oczywiście dopiero zaczynasz tworzyć swoją animacje, bo wtedy łatwiej jest ją odpowiednio przygotować niż później konwertować wszystkie MovieClipy na obiekty Graphics. A mając wszystko w graphics można zwyczajnie wywołać metodę gotoAndPlay by przenieść się w dowolne miejsce w animacji, co jest dokładnie tym czego potrzebujemy w pasku wyszukiwania. Drugie rozwiązanie ma sens tylko przy niewielkiej ilości MovieClip'ów, ponieważ wyszukanie i zatrzymanie dużej ich liczby może być zbyt obciążające dla procesora, szczególnie, że w przypadku paska wyszukiwania oznaczało by to przeszukanie CAŁEGO filmiku. Ale cóż, czasem po prostu nie ma innego wyjścia.
Kod odpowiedzialny za zatrzymanie wszystkich MovieClip'ów jest właściwie całkiem prosty:
stopMovies(root as MovieClip); function stopMovies(mc:MovieClip):void { for(var i:int = 0; i<mc.numChildren; i++) { var d:DisplayObject = mc.getChildAt(i); if(d is MovieClip) { (d as MovieClip).stop(); stopMovies(d as MovieClip); } } }Teraz mogłoby się wydawać, że wystarczy jeszcze dopisać gotoAndPlay i będziemy ustawieni. Niestety, nie ma tak łatwo, ponieważ MovieClip'y mają swoją własną linię czasu, w ogóle nie będą reagować na zmianę położenia głównej linii czasu (w przeciwieństwie do obiektów grafik). Z kolei zamiana wszystkich stop'ów na gotoAndPlay mija się z celem bo chęć przejścia na 10 klatkę głównej animacji nie oznacza, że wszystkie MovieClip'y również mają zostać ustawione na 10 klatce - w końcu jeśli MovieClip we Flash'u pojawi się na 6 klatce, na 10 klatce filmiku będzie dopiero w 4 klatce własnej linii czasu. Najlepiej będzie to pokazać na przykładzie gdzie główna linia czasu ma 30 klatek, a MovieClip pojawiający się na 10 klatce ma ich 20 (wszystkie ponumerowane):
Jak widać przy przewijaniu MovieClip'y zdają się żyć własnym życiem.
Dobra, ale dlaczego by nie odjąć od docelowej pozycji klatki w której pojawia się MovieClip? Jeśli chcemy przejść do 15 klatki animacji a MovieClip pojawia się na 10 to odejmujemy te 10 od 15 i wywołujemy gotoAndPlay(5) - główna linia czasu byłaby na 15 klace a MovieClip na 5-tej. Zgadza się, takie rozwiązanie byłoby idealne, ale haczyk w tym, że nie mamy skąd wziąć początkowej pozycji MovieClip'a. Tutaj właśnie pojawia się owe wyzwanie dla procesora o którym wspomniałem wcześniej, ponieważ kolejnym krokiem byłoby wyszukanie i spisanie tych wszystkich początkowych pozycji MovieClip'ów; tylko problem w tym, że przy standardowej animacji trwającej minutę i odtwarzanej przy prędkości 20 klatek na sekundę, do sprawdzenia będzie 1200 klatek! A jeśli do tego jeszcze dodamy np. 10 MovieClip'ów na klatkę... Dość powiedzieć, że jest to mało praktyczne rozwiązanie, dlatego w dalszej części artykułu zakładam, że animacje są w całości przygotowane na obiektach grafik.
Tyle teorii, czas stworzyć pasek wyszukiwania - zakładając absolutne minimum, będziemy potrzebować dwa MovieClipy: pasek reprezentujący długość animacji oraz wskaźnik aktualnej pozycji. Dla ułatwienia oba połóż na głównej linii czasu osobnego MovieClip'a (cały pasek będzie w jednym miejscu co ułatwi np. jego kopiowanie między projektami) i nazwij je mcBar oraz mcMarker (nazwy instancji). Pozostaje jeszcze tylko wstawić poniższy kod na główną linię czasu MovieClip'a przechowującego nasz pasek wyszukiwania:
import flash.events.MouseEvent; import flash.events.Event; import flash.display.MovieClip; var bSeek:Boolean = false; var nLength:Number = mcBar.width-mcMarker.width; mcMarker.mouseEnabled = false; mcMarker.mouseChildren = false; if( !mcBar.hasEventListener(MouseEvent.MOUSE_DOWN) ) { //by nie dodawac zbednie event'ow gdy animacja zrobi petle mcBar.addEventListener(MouseEvent.MOUSE_DOWN,onSeekStart); mcBar.stage.addEventListener(MouseEvent.MOUSE_MOVE,onSeekDrag); mcBar.stage.addEventListener(MouseEvent.MOUSE_UP,onSeekStop); mcBar.stage.addEventListener(Event.ENTER_FRAME,onRender); } function onRender(e:Event):void { if(bSeek) return; mcMarker.x = ((root as MovieClip).currentFrame/(root as MovieClip).totalFrames)*nLength; } function onSeekStart(e:MouseEvent):void { bSeek = true; onSeekDrag(e); } function onSeekDrag(e:MouseEvent):void { if(!bSeek) return; mcMarker.x = mcBar.mouseX; if(mcMarker.x > nLength) { mcMarker.x = nLength; } else if(mcMarker.x<0) { mcMarker.x = 0; } var frame:int = int((mcMarker.x/nLength)*(root as MovieClip).totalFrames); (root as MovieClip).gotoAndStop(frame); mcMarker.x = (frame/(root as MovieClip).totalFrames)*nLength; } function onSeekStop(e:MouseEvent):void { bSeek = false; (root as MovieClip).play(); }Nie jest to szczególnie skomplikowane - kliknięcie myszka na pasek przenosi w to miejsce znacznik i tłumaczy położenie X na pozycje w animacji.
Całość można pobrać stąd: seekbar.zip
A dla tych wszystkich którzy chcą spróbować rozwiązania z przeszukiwaniem MovieClip'ów przygotowałem specjalną klasę oraz przykład jak ją wykorzystać: seekbarEX.zip. W tym wypadku, żeby nie wpłynąć na filmik, MovieClip w którym umieściliśmy pasek i znacznik będzie eksportowany do ActionScript'u klasą ForusSeekBar.