Strona wykorzystuje ciasteczka by usprawnić komfort z jej korzystania. Korzystając ze strony akceptujesz naszą Politykę Ciasteczek. X

Zrozumieć symbol "Graphics"

Każdy kto spędził trochę czasu z ActionScript'em na pewno doskonale zna działanie MovieClip'ów jednak Flash pozwala również na stworzenie czegoś w miarę podobnego, ale zwanego symbolem "Graphics". Na pierwszy rzut oka mogą się po prostu wydawać uproszczonymi wersjami MovieClip'ów i jest to po części prawda ale w ich działaniu obowiązuje pewna o wiele prostsza zasada... w trakcie eksportu do SWF wszystkie obiekty "Graphics" zostają rozbite na kawałki, podobnie jak to robi polecenie "break apart" (CLTR+B) z tą różnicą, że wszystkie ustawienia i tween'y zostają nałożone na obiekty w środku. Z punktu widzenia ActionScript daje to nam ciekawe możliwości optymalizacji w sytuacjach gdy mamy do czynienia z zagnieżdżającymi się MovieClip'ami.
Weźmy na przykład scenariusz w którym chcemy, aby dwa obiekty krążyły w około siebie ale jednocześnie razem przesuwały się na nową pozycję:

Tak więc symbol Anim zawiera animację krążących obiektów, a tymczasem na scenie przesuwamy całość w prawo. Nic nadzwyczajnego, ale jeśli dodamy do tego chęć dostępu do owych krążących obiektów z poziomu ActionScript instynktownie pierwszym krokiem będzie zmiana Anim z "Graphics" na "MovieClip" by następnie nadać mu nazwę instancji i dalej odwoływać się przez nią by dotrzeć do dwóch obiektów w środku. To właśnie w takich sytuacjach wykorzystanie symbolu Graphics zamiast MovieClip pozwoli pozbyć się dodatkowego zagnieżdżenia, o ile oczywiście wiemy jak Graphics działa.
Proponuje przeprowadzić następującym eksperyment, stwórz na scenie dwa MovieClip'y z dwoma różniącymi się klatkami animacji, następnie nazwij je "mc1" oraz "mc2" (nazwy instancji, tak żeby można było się do nich odwołać z ActionScript) a całość dodatkowo zamknij w symbolu Graphics. Co się stanie jeśli ze sceny gdzie znajduje się owy symbol Graphics wywołamy poniższe funkcje?
mc1.gotoAndStop(1);
mc2.gotoAndStop(2);
Ponieważ Graphics zostanie rozbity i wszystkie wewnętrzne elementy wylądują na zewnątrz odwołania do "mc1" i "mc2" przejdą bez problemów.
Gotowy przykład do ściągnięcia tutaj: GraphExample.zip

Tworzenie podklasy z przygotowaną grafiką [podstawowy]

Jakiś czas temu opisałem jak wykorzystać Interface'y do utworzenia systemu popup'ów - dokładnie tutaj. Wspomniałem wtedy, że w podobnym celu również można utworzyć klasę nadrzędną, jednak w tym wypadku we Flash'u może okazać się to trudniejsze niż można byłoby się tego spodziewać, albo przynajmniej przysporzyć nieoczekiwanych kłopotów. By zrozumieć czym dokładnie są te kłopoty najlepiej będzie zacząć od końca, czyli od wytłumaczenie jak je obejść - sposoby są dwa:

1. Jedna klasa nadrzędna obsługuje wszystkie klasy podrzędne.
Powiedzmy, że mamy dwa popup'y, jeden z tekstem a drugi z obrazkiem, ale oba mają dwa wspólne elementy: tło oraz przycisk "OK". W tym momencie logiczne byłoby utworzenie w sumie trzech klas: MyPopup który będzie klasą nadrzędną opisującą działanie tła (nazwanym np. "mcBackground") wraz z przyciskiem ("mcButton"), oraz MyPopupImage odpowiedzialny za popup z obrazkiem i MyPopupText odpowiedzialny za popup z tekstem. Niestety, Flash odmówi skompilowania takiego projektu tłumacząc, że wystąpił konflikt między tłem w klasie MyPopup a tłem w klasach nadrzędnych MyPopupImage i MyPopupText. Oczywiście można by było stosować różne nazwy, ale to mija się z pierwotnym celem - właściwym rozwiązaniem jest … usunięcie klas nadrzędnych, a dokładnie ich plików, pozwalając by Flash utworzył je sam.

Szczerze mówiąc nie mam pojęcia jak to działa, ale jedno jest pewno, od teraz będziemy mogli tworzyć popup'y wykorzystując nazwy z klasy nadrzędnej bez żadnych problemów, oczywiście po za faktem, że teraz będziemy musieli tworzyć logikę wszystkich popup'ów w jednym miejscu.

2. Wykorzystując funkcję GET.
Tutaj sytuacja jest dokładnie odwrotna, wszystkie klasy nadrzędne będziemy musieli stworzyć sami, nawet jeśli będzie to plik zawierający zero wykonywanego kodu.
Wracając do przykładu z 3 klasami: MyPopup, MyPopupImage i MyPopupText - przygotujemy je tak, by żadne zmienne nie konfliktowały ze zmiennymi w klasie nadrzędnej:

//MyPopup class
public class Popup extends MovieClip {
	public function Popup() {
	}
}
//MyPopupImage
public class MyPopupImage extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MovieClip;
		
	public function MyPopupImage() {	
	}
}
//MyPopupText
public class MyPopupText extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MovieClip;
		
	public function MyPopupText() {	
	}
}
No dobra, ale czy to się nie mija z celem? W końcu klasa nadrzędna nie będzie mieć dostępu do "mcBackground" i "mcButton", a więc tak czy siak potrzebne będzie napisanie ich logiki dwa razy (dla każdego z popup'ów). Tutaj właśnie do akcji wchodzą funkcję GET, które dodamy do MyPopup w następujący sposób:
public class MyPopup extends MovieClip {
	public function MyPopup() {
	}
	protected function get popupBackground():MovieClip {
		return null;
	}
	protected function get popupButton():MovieClip {
		return null;
	}
}
A z kolei MyPopupImage i MyPopupText przebudujemy tak:
//MyPopupImage
public class MyPopupImage extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MyButton;
	
	public function MyPopupImage() {	
	}
	
	override protected function get popupBackground():MovieClip {
		return mcBackground;
	}
	override protected function get popupButton():MovieClip {
		return mcButton;
	}
}
//MyPopupText
public class MyPopupText extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MyButton;
	
	public function MyPopupText() {	
	}
	
	override protected function get popupBackground():MovieClip {
		return mcBackground;
	}
	override protected function get popupButton():MovieClip {
		return mcButton;
	}
}
I gotowe, teraz klasa nadrzędna MyPopup ma dostęp do tła i przycisku z klas nadrzędnej:
public function MyPopup() {
	super();
	trace(popupBackground!=null); //true
}

Przetestuj samemu: subclass.zip

Wysyłanie danych z preloader'a do wczytanego SWF'a [podstawowy]

Kontynuują temat interfejsów z poprzedniego wpisu, wspomniałem tam o scenariuszach w których ich wykorzystanie jest niezastąpione i jednym z takich przykładów jest komunikacja z plikiem SWF wczytanym do aplikacji, najczęściej spotykanym w preloaderach właśnie.
Zacznijmy od standardowego przykładu preloadera:
var loadLoader:Loader = new Loader();
loadLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoaded);
loadLoader.load(new URLRequest("MyAnimation.swf"));
		
function onLoaded(e:Event):void {
	addChild(loadLoader.content);
}
W tym wypadku plik MyAnimation.swf i jego zawartość zostanie wrzucony na scenę jak tylko preloader go w pełni załaduje. Jest to najprostsze rozwiązanie nie dające nam praktycznie żadnej kontroli nad tym kto i jak może wczytać ową animację, dlatego powiedzmy że chcielibyśmy aby ta odtwarzała się jedynie wtedy gdy preloader poda prawidłowe hasło. W tym celu należy przejść do pliku animacji i utworzyć dla niej nową klasę dokumentu:

W taki sposób aby animacja nie startowała automatycznie oraz aby nie dało się tego wymusić z zewnątrz:
public class MyAnimation extends MovieClip {
	public function MyAnimation() {
		super.gotoAndStop(1);
	}
public override function gotoAndStop(frame:Object,scene:String=null):void {
		return;
	}
	public override function gotoAndPlay(frame:Object,scene:String=null):void {
		return;
	}
	public override function play():void {
		return;
	}

}
Teraz nawet my nie będziemy w stanie odtworzyć danej animacji, dlatego czas przygotować obejście z którego tylko my będziemy mogli skorzystać, oczywiście wykorzystując do tege interfejs. Niech wygląda on tak:
public interface IMyInterface {
	function playUsingPassword(pass:String):void;
}
A zaimplementowany w naszej klasie MyAnimation:
public class MyAnimation extends MovieClip implements IMyInterface {
	public function MyAnimation() {
		super.gotoAndStop(1);
	}
	public function playUsingPassword(pass:String):void {
		if(pass == "MyPassword") super.play();
	}
public override function gotoAndStop(frame:Object,scene:String=null):void {
		return;
	}
	public override function gotoAndPlay(frame:Object,scene:String=null):void {
		return;
	}
	public override function play():void {
		return;
	}

}
(Nie zapomnij o implements!)
Napisanie super.play() spowoduje, że pominięta zostanie nasza implementacja a wykorzystany zostanie "prawdziwy" play() z klasy MovieClip. Pozostaje nam już tylko to prze testować w preloaderze:
var loadLoader:Loader = new Loader();
loadLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoaded);
loadLoader.load(new URLRequest("MyAnimation.swf"));
		
function onLoaded(e:Event):void {
	addChild(loadLoader.content);

	var main:IMyInterface = loadLoader.content as IMyInterface;
	if(main != null) main.playUsingPassword("MyPassword");
}
W efekcie dostając:
(Plik animacji znajduje się tutaj: MyAnimation.swf - każdy kto szuka wyzwania może spróbować wczytać ten plik samemu)
Źródła: PreloaderInterface.zip

Interface'y we Flash'u [podstawowy]

Interfejsami w programowaniu nazywamy abstrakcyjne definicje metod które sam z siebie nic nie robią po za narzucaniem implementującym je klasą pewnej składni. Innymi słowy nazwę funkcji i jej argumenty definiujemy w jednym miejscu a w drugim implementujemy (docelowej klasie) jej działanie. Nie wątpliwie są szybsze i łatwiejsze sposoby by tworzyć funkcje we Flash’u jednak interfejsy mają jedną szczególna cechę powodującą, że są wręcz nie zastąpione w specyficznych scenariuszach. Tą cechą jest możliwość rzutowania obiektów do implementowanych przez nich klas, a co za tym idzie będziemy mogli się odwołać do definiowanych metod bez dokładnej znajomości całego obiektu.
Myślę, że najlepiej będzie to przedstawić na przykładzie, zaczynając od samego początku: powiedzmy, że tworzymy grę w której informacje przekazujmy przez wyskakujące okienka zawierające przycisk “OK”.
public class MyPopup extends MovieClip {
	private var myButton:OKButton;
	public function MyPopup() {
		myButton = new OKButton(this);
		addChild(myButton);
	}

	public function close():void {
		this.visible = false;
	}
}
public class OKButton extends MovieClip {
	private var myPopup:MyPopup;
	public function OKButton(popup:MyPopup) {
		myPopup = popup;
		this.addEventListener(MouseEvent.CLICK,onMouse);
	}
	private function onMouse(e:MouseEvent):void {
		myPopup.close();
	}
}
Z początku wszystko może wydawać się w porządku, ale jeśli tylko zaczniemy rozbudowywać nasz grę o kolejne okienka, ograniczenia powyższego kodu staną się oczywiste - co zrobić gdy dodamy MyPopup2 ale będziemy chcieli wykorzystać ten sam przycisk? Czy dopisanie do konstruktora kolejnego argumentu ( OKButton(popup:MyPopup, popup2:MyPopup2) {} ) wystarczy? A co jeśli później dodamy kolejne okienka? Jednym rozwiązaniem jest na pewno utworzenie klasy podrzędnej wspólnej dla wszystkich okienek, odwołując się do niej zamiast wszystkich nadrzędnych klas z osobna. Jednak klasa bazowa może przysporzyć we Flash’u kłopotów których nie będę tutaj opisywał. Myślę, że w tym momencie jest już jasne do czego zmierzam - najlepszym rozwiązaniem będzie wykorzystanie interfejsów. Tak więc stwórzmy osobny plik AS:
public interface IPopup {
	function close():void
}
A istniejące klasy przeróbmy w następujący sposób:
public class MyPopup extends MovieClip implements IPopup{
	private var myButton:OKButton;
	public function MyPopup() {
		myButton = new OKButton(this);
		addChild(myButton);
	}

	public function close():void {
		this.visible = false;
	}
}
public class OKButton extends MovieClip {
	private var myPopup:IPopup;
	public function OKButton(popup:IPopup) {
		myPopup = popup;
		this.addEventListener(MouseEvent.CLICK,onMouse);
	}
	private function onMouse(e:MouseEvent):void {
		myPopup.close();
	}
}
Różnica jest niewielka, ale możliwości o wiele większe. Od teraz MyButton może się komunikować ze wszystkimi stworzonymi okienkami bez potrzeby poznania ich klasy, wystarczy że implementują ten sam interfejs.

Ciasteczka we Flash'u [podstawowy]

Każdy kto regularnie korzysta z Internetu na pewno wie czym są "ciasteczka", albo chociaż o nich słyszał. Ciasteczkami nazywamy niewielkie informacje zapisywane po stronie użytkownika, takie jak na przykład zapamiętane hasło lub ustawienia strony (wygląd i zachowanie). Flash również nie jest tutaj wyjątkiem i posiada bardzo podobny mechanizm nazwany Shared Objects pozwalający na zapisanie do 100Kb informacji (więcej wymaga zgody użytkownika).
Stworzenie (lub odczytanie istniejącego) ciasteczka to kwestia jednej linijki kodu:
var soData:SharedObject = SharedObject.getLocal("MyCookie","/");
"MyCookie" to nazwa dla ciasteczka - jeśli takowe już istnieje wczytane zostanie do pamięci.
Mając już nasz Shared Object możemy zacząć wpisywać do niego co nam się żywnie podoba, poprzez pole "data":
soData.data.message = "Hello world!"
soData.data.myVar = 1337;
W identyczny sposób możemy też odczytać dane z Shared Object:
trace(soData.data.myVar); //1337
Wszystkie informacje zostają zapisywane na komputerze użytkownika automatycznie, ale w razie potrzeby można to również zrobić wykorzystując funkcję flush(), która dodatkowo pozwala na ustalenie docelowego rozmiaru, innymi słowy możemy sami zdecydować kiedy poprosić użytkownika o więcej niż 100Kb miejsca.
Przykładowe zastosowanie:
(Po odświeżeniu strony wpisana wiadomość będzie widoczna w górnym polu)
Źródło: Share.zip

Debugowanie w lokalnym środowisku [podstawowy]

Ostatnim razem wspomniałem o kilku trikach debugowania w aplikacjach który już zostały wydane na świat - tym razem zerkniemy na debugowanie w lokalnym środowisku, w trakcie produkcji. Oczywiście wszystko w tym wypadku zależy od nas id od tego jak przygotujemy aplikację, jednak jest parę uniwersalnych rozwiązań które na pewno zawsze się przydadzą:

1. Pomiar prędkości działania
Pomijając fakt, że zawsze warto programować aplikacje biorąc pod uwagę również słabsze komputery, pomiar prędkości jest dobrym sposobem by przetestować poprawność algorytmów, szczególnie jeśli z góry znam czas jaki powinny osiągnąć. Do pomiarów najlepiej dodać pętle by łatwiej było dostrzec różnice w czasie:

public static function measureProcessTime(fun:Function, repeat:int = 10):Number {
	var time:Number = getTimer();
	for(var i:int = 0; i < repeat; i++) {
		fun();
	}
	return getTimer() - time;
}
Warto dodać, że w tym wypadku bardzo przydają się lokalne funkcje które we Flash'u można utworzyć dosłownie wszędzie i zawsze będą działać, wystarczy dowolny kod zamknąć w następujący sposób:
var someVar:Number = 0;
someVar = 1;
function debug() {
	var otherVar:Number = 10;
	someVar += otherVar;
}
trace(measureProcessTime(debug));
}

2. Wędrowanie przez pętle.
Kolejny sposób na testowanie algorytmów. Najprościej mówiąc chodzi tutaj o wyświetlanie informacji tylko na wybranym punkcie pętli ale tak, żeby w każdej chwili można było zmienić jego położenie. Przykładowa implementacja:

private static var DEBUG_COUNT:int = 0;
private static var DEBUG_STEP:int = 0;
public static function nextStep():void {
	DEBUG_STEP++;
}
public static function prevStep():void {
	DEBUG_STEP--;
	if(DEBUG_STEP<0) DEBUG_STEP=0;
}
public static function resetDebug():void {
	DEBUG_COUNT = 0;
}
public static function countDebug():Boolean {
	DEBUG_COUNT++;
	return  DEBUG_COUNT == DEBUG_STEP;
}
Funkcje nextStep i prevStep należy podpiąć pod klawisze strzałek by można było się poruszać po pętli. Funkcja resetDebug powinna się pojawić przed rozpoczęciem testowanej pętli. I w końcu countDebug która zwróci true gdy doliczy do wybranego kroku. Choć nie ma tutaj nic nadzwyczajnego, wędrowanie po pętli ułatwia debugowanie w nie porównywalnym stopniu. W działaniu, z wykorzystaniem algorytmu rysowania linii może to wyglądać tak:
(Użyj klawiszy góra i dół by wybrać krok algorytmu)

3. Nakładka graficzna.
Chyba nie ma wątpliwości, że wyświetlanie informacji w postaci przejrzystej grafiki zamiast listy zmiennych jest zawsze o niebo przyjemniejsze, dlatego każda porządna aplikacja powinna dawać możliwość rysowania (z wykorzystaniem klasy Graphics) ponad wszystkimi obiektami. Osiągnąć to można trzymając całą aplikację w jednym MovieClipie, coś na rodzaj "zbiornika", a tuż nad nim umieścić obiekt, np. Sprite, który będzie pusty, ale do którego zawsze będzie dostęp z każdej lokacji w aplikacji:
public static debug:Sprite;
public function DocumentClass() {
	var main:MyApplication = new MyApplication();
	addChild(main);

	var debug:Sprite = new Sprite();
	addChild(debug);
}
Teraz wystarczy napisać DocumentClass.debug.graphics by mieć dostęp do grafiki widocznej ponad całą aplikacją. Osobiście często korzystam z tego by sprawdzić rozmiary obiektów znajdujących się na ekranie:

Podstawy silnika kafelkowego część 1: widok izometryczny [podstawowy]

W tym trzyczęściowym artykule postaram się przybliżyć kilka najbardziej podstawowych zagadnień z silników opartych o kratki - czy też kafelki (tile-based). Owe silniki istnieją już od bardzo dawna i wydawać by się mogło, że już wszystko w tej sprawie zostało powiedziane, jednak wciąż wiele osób ma z nimi problemy, nierzadko komplikując trywialne zagadnienia.
Jednym z takich zagadnień jest widok izometryczny (czasem nazywanym "widokiem po skosie") będący specyficznym rodzajem transformacji 3D bez dodatku perspektywy, często wykorzystywanym w starszych grach strategicznych. Chociaż definicja mówi o transformacji 3D takowa wcale nie jest wymagana i można ją zastąpić czymś o wiele prostszym, co niestety nie wszyscy próbują zrobić i dlatego w Internecie można całkiem sporo podobnych artykułów, które jednak najpierw tłumaczą podstawy 3D - nie potrzebnie.
Całość można sprowadzić do pojedynczej funkcji:
function getTile(nTargetX:Number,nTargetY:Number,nWidth:Number,nHeight:Number):Point {
	var nScale:Number = nWidth/nHeight;
	var nTransY:Number = (nScale*nTargetY-nTargetX)/2;
	var nTransX:Number = nTargetX+nTransY;
	var nTmpY:Number = Math.round( nTransY/(nHeight*nScale) );
	var nTmpX:Number = Math.round( nTransX/nWidth );	
	var nTileX:Number = (nTmpX-nTmpY)*nWidth;
	var nTileY:Number = (nTmpX+nTmpY)*nHeight;
	return new Point(nTileX,nTileY);
}
W efekcie dostając:
(Zielone kółko to Point zwrócony przez getTile)
Gotowy przykład: tile_example.zip

I to w zasadzie wszystko, dalej tylko jeszcze poświecę parę słów na wyjaśnienie działania funkcji getTile krok po kroku:
1.
var nScale:Number = nWidth/nHeight;
Skala będzie nam potrzebna przy transformacji by wyrównać wysokość do szerokości (ponieważ algorytm działa tylko w skali 1:1).
2.
var nTransY:Number = (nScale*nTargetY-nTargetX)/2;
var nTransX:Number = nTargetX+nTransY;

W tym miejscu następuje transformacja punktu na ekranie, na punkt w rzucie izometrycznym.
Najczęściej mówiąc o rzucie izometrycznym mamy na myśli przestrzeń obróconą o kąt 45 stopni, w taki sposób że ruchowi w prawo nie będzie odpowiadać samo +X, ale raczej pewna wartość X plus częściowo również i Y. Najlepiej zobrazować to na przykładzie:

Jak widać przesunięcie kursora w prawo, przesuwa punkt transformacji w prawo tylko po części, ponieważ dochodzi do tego jeszcze przesunięcie do góry. Uzbrojeni w ta wiedzę, możemy spróbować napisać generalny wzór - skoro przesunięcie w prawy (+X) daje nam również trochę przesunięcia do góry (-Y) można powiedzieć, że Y maleje wraz ze wzrostem X, czyli innymi słowy Y = -nTargetX. Jednak należy pamiętać, że nie jest to transformacja x=>y (inaczej mieli byśmy do czynienia z obrotem o 90 stopni), stąd też do równania należy dołączyć również dzielnie przez 2 (bo 90 stopni podzielone przez 2 to nasze 45 stopni... w wielkim uproszczeniu). Teraz gdy już znamy Y, obliczenie X jest trywialne i wystarczy, że odejmiemy wartość Y od docelowego X (by pokonany dystans był identyczny z tym przed transformacją), a ponieważ Y już jest ujemne wstawiamy znak dodawania Y = nTargetX+nTransY;. Przy okazji, plus i minus można w tych równaniach zamienić miejscami by uzyskać transformację dla obrotu -45 stopni.
3.
var nTmpY:Number = Math.round( nTransY/(nHeight*nScale) );
var nTmpX:Number = Math.round( nTransX/nWidth );
var nTileX:Number = (nTmpX-nTmpY)*nWidth;
var nTileY:Number = (nTmpX+nTmpY)*nHeight;

Tutaj zmieniamy wybrany punkt na podpadający pod niego kafelek, poprzez "skurczenie" całej przestrzeni o wysokość i szerokość kratki, następnie usunięcie informacji po przecinku i przywrócenia całości do "normalnych rozmiarów". Ponieważ uwielbiam przykłady o to następny: jeśli szerokość kafelki numer #0 wynosi 20 pikseli, to wszystko pomiędzy 0 a 20 pikseli będzie podpadać właśnie pod nią, tak więc: jak sprawdzić do której kafelki należy siódma piksela? Bierzemy owe 7, dzielimy przez 20 i dostajemy 0.35. Z rezultatu usuwamy wszystko po przecinku i dostajemy naszą kratkę #0. Jeszcze spróbujmy tego samego dla pikseli 27; 27/20 = 1.35; usuwamy liczby po przecinku i dostajemy #1, co się zgadza bo dwudziesta siódma piksela nie należy do kratki #0.

Uff, starczy na dzisiaj. Za dwa tygodnie napisze jak w najłatwiejszy sposób zaimplementować znajdywanie ścieżki w silnikach kafelkowych.

Preloader'y we Flash'u [podstawowy]

"Preloading" to prosta idea która istnieje już od samego początku Internetu - a może nawet i wcześniej. Chodzi tutaj o uprzednie wczytywanie aplikacji lub multimediów z których będziemy korzystać do pamięci komputera, dzięki czemu będziemy mogli z nich korzystać bez przerw na doczytywanie brakujących danych. Nie inaczej jest w przypadku Flash'a, korzystając z preloader'a mamy pewność, że nasz widz będzie oglądał płynną animację, a nie pokaz slajdów.
O ile idea preloader'a jest prosta to z implementacją we Flash'u może być różnie. Generalnie, zależnie od tego czego potrzebujemy, wszystko sprowadza się do 3 rozwiązań:

1. Dla animacji.
Filmiki we Flash'u, które w minimalnym stopniu korzystają z jakiegokolwiek kodu ActionScript, są najłatwiejszym celem do implementacji Preloadera - zwykle wystarczy jedynie skopiować gotowe rozwiązanie, a tych w Internecie jest cała masa.
Jednym z najprostszych jest stworzenie w pierwszej klatce animacji MovieClip'a o nazwie instancji "preloader" (na zakładce właściwości) a potem wklejenie do tej samej klatki poniższego kodu:

import flash.events.Event;
import flash.events.ProgressEvent;

stop();
loaderInfo.addEventListener(ProgressEvent.PROGRESS,onProgress);
loaderInfo.addEventListener(Event.COMPLETE,onLoaded);

function onProgress(e:ProgressEvent):void {
	var percent:Number = loaderInfo.bytesLoaded/loaderInfo.bytesTotal;
	preloader.gotoAndStop( int(percent*preloader.totalFrames)+1);
}
function onLoaded(e:Event):void {
	loaderInfo.removeEventListener(ProgressEvent.PROGRESS,onProgress);
	loaderInfo.removeEventListener(Event.COMPLETE,onLoaded);
	//klatka do ktorej ma sie udac animacja po wczytaniu filmu
	gotoAndPlay(2);
}
MovieClip "preloader" zostanie przesunięty do klatki która odpowiada procentowemu postępowi procesu wczytywania filmiku, tak więc jeśli "preloader" ma 10 klatek animacji, a filmik jest już wczytany w 20%, wyświetlona zostanie 2 klatka (10*20% = 2).
Gotowy przykład: preloaderSimple.zip

2. Dla aplikacji w środowisku Flash Professional.
W tym wypadku już jest nieco trudniej. Tworząc projekty które wymagają dużej ilości ActionScript'u często nie myślimy o Preloaderze w pierwszej kolejności i szczerze mówiąc nie jest w tym nic strasznego, ale może przysporzyć komplikacji, szczególnie jeśli całą grafikę eksportujemy do pierwszej klatki animacji.
Skoro już wspomniałem o eksporcie do pierwszej klatki, zajmijmy się właśnie tym. Eksportując grafikę do ActionScript mamy do dyspozycji opcję "eksportuj w 1 klatce", która robi dokładnie to co opisuje - wsadza całą grafikę do pierwszej klatki - sęk w tym, że chcą później wstawić nasz preloader nie możemy zrobić tego wcześniej niż pierwsza klatka. W Flash Professional można sobie z tym poradzić na dwa sposoby:
- zmieniając docelową klatkę eksportu w ustawieniach ActionScript

- lub po prostu samemu ustawić grafikę na scenie w odpowiedniej klatce. Jeśli na przykład, mamy klasę która korzysta z eksportowanej grafiki w 3 klatce głównej linii czasu, nic nie stoi na przeszkodzie by wyłączyć "eksportowanie do 1 klatki" i samemu ją położyć w drugiej, nawet jeśli będzie po prostu poza ekranem.

3. Dla aplikacji w środowisku FlashDevelop.
… lub jakichkolwiek innych środowisk programistycznych wczytujących SWC. Chodzi tutaj o importowanie zewnętrznych bibliotek ActionScript, które oprócz samych klas zawierać mogą również grafikę lub inne elementy multimedialne. Owe elementy multimedialne zostaną wczytane jak tylko pojawi się odwołanie do klas z nimi powiązaniami, co w skrócie oznacza, że jakikolwiek import z SWC zostanie zablokowany, aż do czasu gdy grafika zostanie w pełni załadowana do pamięci. Z drugiej strony pominięcie jakichkolwiek importów spowoduje, że wybrane klasy zwyczajnie nie zostaną dodane do aplikacji w trakcie kompilowania. Tak więc, rodzi się pytanie: jak coś dodać do aplikacji bez korzystania z importu? Sposób jest jeden i odkryty został przez Keith Peters na: www.bit-101.com. W wielkim skrócie, ten genialny trick sprowadza się do pojedynczej linijki: [Frame(factoryClass="Preloader")] ("Preloader" to nazwa klasy która ma posłużyć za preloader), którą należy wstawić przed definicję klasy dokumentu (ale po wszystkich importach) - dla przykładu, nazwijmy ją "Main". Main zostanie wczytany, ale nie zainicjalizowany, ponieważ rolę główne linii czasu przejmie Preloader.
Teraz jeszcze zostaje kwestia stworzenia Main po zakończeniu procesu wczytywania do pamięci, ponieważ wciąż nie możemy korzystać z importu - my zaimportujemy Main, Main zaimportuje coś innego, co z kolei importuje jeszcze coś innego i prędzej czy później trafi się klasa która importuje z SWC, zatrzymując całą aplikacje na czas wczytywania grafiki. Na pomoc przychodzi funkcja flash.utils.getDefinitionByName, którą należy wywołać gdy aplikacje zostanie już wczytana w następujący sposób:

var mainClass:Class = getDefinitionByName("Main") as Class;
if(mainClass) {
	addChild(new mainClass() as DisplayObject); 
}
UWAGA! Funkcja getDefinitionByName() może od czasu do czasu zwrócić NULL, więc najlepiej do całości jeszcze podpiąć Event ENTER_FRAME, by próbować ponownie co pewien czas.
Jak zwykle do ściągnięcia jest przykładowy kod: preloader.zip
A owy przykład w akcji można zobaczyć tutaj: Preloader.html

Komunikacja pomiędzy AS2 i AS3 [podstawowy]

Jak tylko ActionScript3 po raz pierwszy ujrzał światło dzienne Adobe od razu stwierdziło, że stoi za nim zupełnie inna wirtualna maszyna niż ta w AS2, a co za tym idzie nie jest możliwa bezpośrednia komunikacja między tymi dwoma językami. W tym momencie za pewne każdy by pomyślał, że w takim razie nie ma co próbować, skoro nie mogą się komunikować to temat jest zamknięty. Ale chwila moment, brak bezpośredniego połączenia to nie jest jednoznaczne z brakiem jakiegokolwiek połączenia. Ujmę to tak: weźmy na przykład MySQL, Flash sam z siebie nie ma żadnego sposobu by połączyć się z nim bezpośrednio, ale to nas nie powstrzymuje by i tak z niego korzystaj przez PHP (lub inne metody). I tu dochodzimy do sedna sprawy: pośrednie połączenie. W przypadku AS3 i AS2 jest to nawet jeszcze prostsze niż korzystanie z PHP, bo oby dwa języki mają wbudowaną klasę do komunikacji między lokalnymi plikami SWF - mowa tutaj o LocalConnection.
LocalConnection w głównym założeniu ma pozwalać na komunikację między aplikacjami Flash uruchomionymi na tym samym komputerze , jednak ponieważ implementacja tej klasy w ogolę się nie zmieniła po przejść do AS3, nic nie stoi na przeszkodzie by wykorzystać ją do wymiany informacji między AS2 i AS3.
Utworzenie LocalConnection w obydwóch przypadkach jest bardzo łatwe i będzie wyglądać prawie identycznie. Dla ActionScript2 będzie to:
var connectionAS2:LocalConnection = new LocalConnection();
connectionAS2.connect("LocalConnectionTestAS2");
A dla ActionScript3:
var connectionAS3:LocalConnection = new LocalConnection();
connectionAS3.client = this;
connectionAS3.connect("LocalConnectionTestAS3");
connectionAS3.addEventListener(StatusEvent.STATUS,onStatus);
function onStatus(e:Event):void { }
O ile wersja dla AS2 jest trywialna, to AS3 już wymaga paru słów wyjaśnienia. Przede wszystkim linijka connectionAS3.client = this;, która służy do wyznaczenia obiektu odpowiedzialnego za wykonywanie funkcji wywoływanych z drugiej strony połączenia, jest bardzo ważna i bez niej aplikacja zwyczajnie nie będzie działać. StatusEvent jest dodany do LocalConnection tylko po to by Flash nie wyrzucał żadnych błędów przed nawiązaniem połączenia.
A i nazwy w funkcji connect() są różne by pozwolić na dwustronną komunikację.
W porządku, teraz gdy już AS2 i AS3 są gotowe do przyjmowania komend od siebie nawzajem, czas stworzyć funkcje które będą mogli wywołać. By cały proces lepiej zobrazować wykorzystamy TextField'y do pokazania danych otrzymanych z zewnątrz. Dla ActionScript3 będzie to:
var tf:TextField = new TextField();
tf.text = "No Messages";
addChild(tf);

function messageFromAS2(msg:String,nx:Number,ny:Number):void {
	tf.text = msg+" "+nx+" "+ny;
	tf.x = nx;
	tf.y = ny;
}
A dla ActionScript2:
this.createTextField("textMsg",this.getNextHighestDepth(),0,0,200,25);
var tf:TextField = this.textMsg;
tf.text = "No Messages";

connectionAS2.messageFromAS3 = function(msg:String,nx:Number,ny:Number):Void {
	tf.text = msg+" "+nx+" "+ny;
	tf._x = nx;
	tf._y = ny;
}
Teraz już tylko pozostało jakoś wywołać te funkcje, np. za pomocą myszki. W ActionScript3:
stage.addEventListener(MouseEvent.MOUSE_MOVE,onMove);
function onMove(e:MouseEvent):void {
	try {
		connectionAS3.send("LocalConnectionTestAS2","messageFromAS3","AS3 MouseEvent",e.stageX,e.stageY);
	} catch(e:Error) {
		return;
	}
}
W ActionScript2:
this.onMouseMove = function():Void {
	try {
		connectionAS2.send("LocalConnectionTestAS3","messageFromAS2","AS2 Mouse Event",_root._xmouse,_root._ymouse);
	} catch(e) {
		return;
	}
}
W obydwóch przypadkach potrzebny jest try-catch by zapobiec wszelkim błędom przed nawiązaniem przez Flash połączenia.
W akcji całość będzie wyglądać tak:
Kod źródłowy: localcon.zip

Przy okazji, w sytuacji w której musielibyśmy coś odczytać z pliku AS2 a który nie obsługuje LocalConnection i którego już nie możemy modyfikować, sugerował bym stworzyć osobny plik SWF w ActionScript2, wczytać (funkcja loadMovie()) w nim nasz docelowy plik, następnie wyciągnąć wszystkie potrzebne informacje i przesłać je dalej za pomocą LocalConnection.

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:
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.


Starsze posty>>>