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

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:

Debugowanie po stronie użytkownika [podstawowy]

Debugowanie to potoczna nazwa procesu usuwania błędów (lub po prostu nie pożądanego działania) z kodu źródłowego. Programując w Flash Professional lub FlashDevelop mamy dostęp do narzędzie debuggera które swoją rolę sprawuje całkiem nieźle i na pewno nie jednej osobie pomogło już usunąć błędy we własnych projektach. Niestety debugger jest narzędziem dostępnym jedynie twórcom aplikacji którzy jednak nie zawsze są w stanie przewidzieć (i sprawdzić) wszystkie możliwe scenariusze działania które podejmie docelowy odbiorca. Dlatego dla pewności zawsze warto zaimplementować system wyłapywania błędów od strony korzystających z aplikacji użytkowników - w tym celu przyjrzymy się kilku najpopularniejszym mechanizmom.

1. Główna pętla.
Ten, zdawać by się mogło, trywialny element powinien być częścią każdej szanującej się aplikacji i to nie tylko dlatego, że ułatwia debugowanie ale również czyni kod przejrzystszym i mniej podatnym na wycieki pamięci. Ale czym właściwie jest "główna pętla"? We Flash'u sprowadza się do posiadania tylko JEDNEGO zdarzenia "ENTER_FRAME" w całej aplikacji, co oznacza, że wszystkie inny wykonywane funkcja będą wywodzić się bezpośrednio (lub pośrednio) z głównej pętli. Takie podejście ułatwia wyłapywanie błędów w nieporównywalnym stopniu bo wystarczy, że kod głównej pętli obejmiemy słowami kluczowymi try i catch, a już żaden wyrzucony Error nie umknie naszej uwadze. Główna pętla z try i catch może wyglądać na przykład tak:

public function render(e:Event):void {
	try {
		processPlayer();
		processEnemy();
		processMap();
	} catch(e:Error) {
		trace("Error!");
	}
}

2. Dziennik.
Chociaż wyłapywanie błędów jest bardzo ważne, to jednak samo w sobie może okazać się mniej istotne niż na przykład okoliczności w których doszło do wyrzucenia wyjątku, dlatego drugim bardzo ważnym elementem każdej aplikacji powinien być dziennik zapisujący wykonywanie wszystkich kluczowych funkcji przez użytkownika - w jakim stanie rozpoczął korzystanie z aplikacji (wersja Flash, system operacyjny itd.), jakich przycisków używał, przez jakie okienka przeszedł itp. A jeśli będzie to gra, to na pewno na jakiej jest mapie, w jakiej jest pozycji i z czym miał już do czynienia. Przechowując wszystkie te informacji łatwiej będzie nam odtworzyć warunki w jakich użytkownik otrzymał błąd.
Klasa dziennika jest zawsze najprostszym elementem do zaimplementowania:

public class Log {
	private static var log:String = "";
	public function Log() {}
	public static function add(s:String):void {
		log += s+"n";
	}
	public static function toClipboard():void {
		System.setClipboard(log);
	}
}

3. Konsola.
Chyba każdy gracz chociaż raz korzystał z konsoli w grach PC, choćby po to żeby sobie wpisać "kody na nieśmiertelność". Konsola to potoczne określenie (zwykle ukrytej) linii komend w aplikacji, pozwalająca użytkownikowi wymusić działanie pewnych, z góry ustalonych funkcji. Zaimplementowanie dobrej konsoli to już nieco roboty, na szczęście w Internecie można znaleźć sporo gotowych rozwiązań, jak na przykład: Flash Console. Zdecydowanie każdy powinien mieć linie komend w swojej aplikacji - jeden raz wystarczy by już na zawsze się w niech zakochać.

Na koniec gdy nasza aplikacja jest już gotowa, warto w głównej pętli pozbyć się funkcji trace i zastąpić ją czymś co pozwoli nam otrzymać informacje o ewentualnych błędach, na przykład wysyłając je sobie na e-mail.

Szczęśliwych Świąt Wielkanocnych!

Dzisiaj mam tylko parę wiadomości odnośnie bloga:
Przede wszystkim przez ostatni tydzień trudno było dostać się na blog ponieważ firma hostingowa pracowała nad swoimi serwerami uniemożliwiając do nich dostęp - na szczęście już powinno być po wszystkim.
A tym razem nie poruszamy żadnych nowych tematów na blogu ponieważ pracowałem nad czymś specjalnym - mobilną wersją bloga. Innymi słowy lżejszej i (mam nadzieje) szybszej wersji całej strony dedykowanej użytkownikom przeglądających Internet na urządzeniach mobilnych. Zainteresowane osoby zapraszam tutaj: 4as.pl/mobile aczkolwiek mobilni użytkownicy zostaną tam przekierowani automatycznie przy wejściu na bloga. Mała uwaga: odchudzona wersja może wymagać jeszcze paru szlifów.

Podstawy silnika kafelkowego część 3: pole widzenia [zaawansowany]

Mam już za sobą rzut izometryczny i wyszukiwanie ścieżki, teraz przyszedł czas na ostatni punkt tego 3 częściowego artykułu: pole widzenia. W praktyce polega to zwykle na sprawdzaniu czy między jednym punktem (na przykład postacią gracza) a drugim (przeciwnikiem) jest bezpośredni kontakt, bez przeszkód po drodze. Zwykle jest to dość skomplikowane zagadnienie, jednak w silnikach kafelkowych okazuje się wyjątkowo trywialne, wystarczy znaleźć sobie jakiś algorytm do rysowania linii. Po co dokładnie? Algorytmy rysujące linie startują z jakiegoś punktu i krok po kroku zastępują piksele wybranym kolorem kierując się w stronę jakiegoś innego punktu by ostatecznie stworzyć linię. Gdyby jednak zastąpić wstawienie pikseli funkcją sprawdzając kafelki mielibyśmy algorytm który krok po kroku sprawdza czy coś nie stoi na przeszkodzie między dwoma punktami.
Tak więc, pozostaje tylko znaleźć jakiś algorytm - osobiście polecam ten opisany na Wikipedii: Algorytm Bresenhama. Zaimplementowany w ActionScript może wyglądać tak:
function isInSight(tiles:Vector.<Vector.<int>>,x1:Number, y1:Number, x2:Number, y2:Number):Boolean {
	var dx:Number;
	var dy:Number;
	if (x1>x2) {
		dy = y1;
		y1 = y2;
		y2 = dy;
		dx = x1;
		x1 = x2;
		x2 = dx;
	}
	dx = x2-x1;
	dy = y2 - y1;
			
	var y:Number;
	var x:Number;
	var m:Number;
	if (Math.abs(dx)>Math.abs(dy)) {
		m = dy/dx;
		y = y1;
		for (x=x1; x<x2; x++) {
			if(tiles[x][y] == 1) return false;
			y = y+m;
		}
	} else {
		m = dx/dy;
		x = x1;
		if (y1<y2) {
			for (y = y1; y < y2; y++) {
				if(tiles[x][y] == 1) return false;
				x = x+m;
			}
		} else {
			for (y = y1; y > y2; y--) {
				if(tiles[x][y] == 1) return false;
				x = x-m;
			}
		}
	}
	
	if(tiles[x2][y2] == 1) return false;
	return true;
}
Funkcja isInSight zwróci false jeśli po drodze do punktu 2 z punktu 1, trafią się jakieś kafelki oznaczone jako 1 w dwuwymiarowej tablicy tiles.
Łatwizna. Do tego algorytmy rysujące linie zwykle są bardzo szybkie, więc spokojnie można stosować to rozwiązanie na naprawdę obszernych planszach.

Podstawy silnika kafelkowego część 2: odnajdywanie ścieżki [zaawansowany]

W poprzedniej części omówiłem pokrótce przekształcenie izometryczne i jak uzyskać je w najłatwiejszy sposób. Tym razem, zgodnie z obietnicą zajmiemy się znajdywaniem ścieżki w silniku kafelkowym wykorzystując jak najprostszą metodę (aczkolwiek nie najszybszą). Nie wiem czy algorytm który tutaj przybliżę ma jakąś konkretną nazwę, ale na pewno jest jednym z najbardziej bezpośrednich spośród wszystkich dostępnych.
Dla wszystkich tych których nie interesują szczegóły przygotowałem klasę z całym algorytmem oraz dodatkowo plikiem FLA pokazującym jak go wykorzystać: PathFind.zip
W działaniu wygląda tak:
(Wybierz punkt startowy i końcowy naciskając na puste pola. Spacja by stworzyć nową mapę)

Działanie algorytmu jest całkiem proste i dzieli się na dwie fazy:
1. Poszukiwanie celu. Z punktu startowego krok po kroku sprawdzamy co raz większy obszar w poszukiwaniu docelowej pozycji, w międzyczasie zapisując do siatki aktualny krok iteracji. Numery iteracji będą nam później potrzebne by odtworzyć ścieżkę.
Urywek kodu z załączonego przykładu:

// ...
var vWrite:Vector.<Vector.<int>> = recreateTiles(tiles);
vWrite[startx][starty] = 1;
vWrite[endx][endy] = -1;
var vCheck:Vector.<Point>;
var vRead.<Point> = new Vector.<Point>();
			
var tile:Point;
var nStep:int = 2;
vRead.push( new Point(startx,starty) );
while(vRead.length != 0) {
				
	vCheck = vRead;
	vRead = new Vector.<Point>();
	for each(tile in vCheck) if(lookupTile(tile.x, tile.y, nStep, vRead,vWrite,vTile)) {
		return retracePath(tile.x,tile.y,nStep,vWrite);
	}
	nStep ++;
}
return null;
// ...
Funkcja lookupTile poszukuje kolejnych kafelek do iteracji. Jeśli któryś z 4 kierunków zawiera docelowy punkt, pętla zostaje zakończona i zwrócona zostaje znaleziona ścieżka. W przeciwnym wypadku w vWrite zapisywany jest aktualny krok, a do vRead wpisywana jest pozycja kolejnej kafelki do sprawdzenia.
2. Odtwarzanie ścieżki. Gdy już odnajdziemy punkt docelowy wystarczy krok po kroku zacząć wracać po zapisanych liczbach w vWrite by uzyskać naszą ścieżkę. Powiedzmy że doszliśmy do docelowej pozycji w 20 iteracji - teraz musimy zacząć się wracać zaczynając od znalezienia najbliższej kafelki z numerem 19, potem 18, 17, itd. Aż do 1 który będzie punktem startowym (0 powinny mieć kafelki które nigdy nie zostały sprawdzone).

Na koniec jeszcze tylko mała uwaga: algorytm można przyspieszyć wykonując go jednocześnie na punkcie startowym i końcowym, przez co ścieżka będzie odnaleziona gdy oba spotkają się w połowie drogi. Taki też sposób został użyty w dołączonej klasie.
Następnym razem przyjrzymy się idei "pola widzenia" w silnikach kafelkowych.

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.


<<<Nowsze postyStarsze posty>>>