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

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.

5 rzeczy które zabijają płynność animacji we Flash'u

Na pewno każdy z nas słyszał chociaż raz, że Flash bywa powolny i choć z każdym wydaniem staje się coraz szybszy, wciąż można znaleźć gry Flash które mogą nadwyrężyć nawet najwydajniejszy komputer. O dziwo winna wcale nie musi leżeć po stronie programisty, lecz, zdawać by się mogło, niepozornych grafików - zgadza się, ActionScript (w szczególności 3) już jest na tyle szybki, że jego wpływ na działanie aplikacji może być nieporównywanie mniejszy w stosunku do wykorzystywanej grafiki. Dlatego pora dać odetchnąć programistom i zamiast tego raczej pomyśleć czy przez wypadek nie korzystamy z pięciu najbardziej wymagających elementów graficznych:
1. Filtry.
Niekwestionowany mistrz spowalniania Flash'a, wystarczy jeden niefortunnie położony filtr by zmienić naszą płynną animację w pokaz slajdów. Nie zagłębiając się w szczegóły, wykorzystanie dowolnego z filtrów na MovieClip'ie powoduje, że przestaje on być wyświetlany jako grafika wektorowa a staje się bitmapą - przy czym powodem spadku płynności nie jest sama bitmapa ale proces jej tworzenia, który dodatkowo wydłuża się geometrycznie wraz ze wzrostem rozmiaru grafiki. Dlatego korzystając z filtrów należy pamiętać przede wszystkim o dwóch rzeczach:
- unikać obszernych grafik (nie większe niż przeciętny banner na stronach WWW)
- unikać animowanych filtrów (z powodów o których pisze w następnym punkcie: "cache as bitmap")
2. Cache as bitmap (przechowuj jako bitmapę).
Bitmapy to dość intrygująca sprawa we Flash'u; weźmy na przykład zwykły czerwony kwadrat narysowany grafice wektorowej oraz w bitmapie - w tym wypadku Flash o wiele szybciej wyświetli ten w grafice wektorowej niż w bitmapie. Jeśli jednak owy kwadrat zostanie wypełniony różnymi detalami, wzorkami lub gradientami, szybko może okazać się, że bitmapa będzie o wiele łatwiejsza do wyświetlania. Gdzie leży granica? Cóż, jest to coś, co wymaga doświadczenia zabranego na własną rękę, jednak w większości przypadków już samo dodanie gradientów jest dobrym powodem by przejść na "cache as bitmap". Niestety, nie ma róży bez kolców... bo o ile wyświetlanie bitmapy może być często szybsze to już wykorzystanie jakiejkolwiek transformacji (zmiana kolor, skalowanie, rotacja) może mięć zaskakująco tragiczne skutki. Tak więc z bitmapami należy obchodzić się ostrożnie i pamiętać:
- cache as bitmap jest kompletnie bezużyteczny na animowanym MovieClip'ie.
- cache'owane MovieClip'y i bitmapy są bardzo powolne przy wszelakich transformacjach.
- jeśli już musisz lepiej jest transformować prawdziwą bitmapę niż MovieClip z "cache as bitmap".
3. Alpha (przeźroczystość).
Tak wiem, "zrezygnować z przeźroczystości? Nie ma mowy!" Jednak nie bez powodu alpha zajmuje 3 miejsce na tej liście - ten cichy zabójca zwiększa dwukrotnie czas potrzebny na wyświetlanie pikseli na ekranie. Więc jeśli tylko możesz, zamiast znowu tworzyć cień półprzeźroczystym czarnym kształtem, poświeć trochę więcej czasu i naprawdę przyciemnij te kolory na ziemi.
4. Nadmiar MovieClip'ów.
Bardzo łatwo można się przyzwyczaić do wygody jaką dają MovieClip'y, w końcu tak łatwo możne je podmienić, edytować nie wpływając na inne obiekty i co najważniejsze zduplikować wielokrotnie by wypełnić scenę szczegółami. Oczywiście za tą wygodą kryję się pewna cena, cena wydajności. Każdy MovieClip na scenie to nie tylko dodatkowa grafika ale także cała masa parametrów: pozycja, skala, filtry, kolory, nazwa, widoczność, cache'owanie itd. i nawet jeśli z nich nie korzystasz to one wciąż są brane pod uwagę na nowo w każdej klatce animacji. Dlatego gdzie jest to tylko możliwe zamień swoje MovieClip'y na obiekty Grafiki (choć najwydajniejszy zawsze będzie zwyczajny obiektu Kształtu).
5. Ukryte elementy.
Czyli wszystko to czego nie widzimy, ale wciąż tam jest. Najgorsza w tym wypadku jest maska, bo mimo że przykrywa jakiś element to on wcale nie przestaje istnieć, wręcz przeciwnie, z punktu widzenia Flash'a cała grafika zasłonięta przez maskę wciąż jest wyrysowywane na ekranie, tylko potem, po wszystkim przycinane do wyznaczonych granic. Podobnie jest z obszarem wyświetlania Flash'a, on również niczego nie usuwa - każdy element na scenie, nie ważne czy jest poza czy na ekranie, wciąż ma taki sam wpływ na wydajność.

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.

Singleton'y we Flash'u [podstawowy]

Dzisiaj postanowiłem napisać parę słów singleton'ach czyli obiektach które mają tylko jedną instancję - czym są, jak je tworzyć we Flash'u i do czego służą.
Singleton to jeden z wielu wzorców projektowych i tak jak większość z nich może wydawać się nie potrzebnym komplikowaniem spraw które można rozwiązać w o wiele łatwiejszy sposób, jednak prawda jest taka, że w zamian zyskujemy przejrzystszy i bardziej wyspecjalizowany kod co jest szczególnie przydane jeśli pracujemy w większej grupie lub musimy wracać do starszych projektów których już nie pamiętamy, choć też i w małych jednoosobowych aplikacjach warto skorzystać z tego rozwiązania.
Tworząc klasę singleton'u chcemy aby tylko jeden obiekt istniał w całym systemie, tak aby zawsze mieć dostęp do tych samych wartości niezależnie od miejsca wywołania. W większości popularnych języków programowania można to osiągnąć pisząc zwyczajnie:
private class Single {
	private static const instance:Single = new Single();
	private function Single():void { }
	public static function getInstance():Single {
		return instance;
	}
}
Niestety we Flash'u tak łatwo nie będzie, gdyż ten nie pozwala na tworzenie konstruktorów w przestrzeni private, dlatego najczęściej stosuje się obejście wymagające od osoby trzeciej podania parametru którego na pewno nie będzie znał:
public class Singleton {
	private static const instance:Singleton = new Singleton(getInstance);
	public var myVar:Number;
	public function Singleton(f:Function = null) {
		if(f != getInstance) throw new Error("Singleton nie moze byc powielany! Skorzystaj z getInstance().");
		myVar = 5;
	}
	public static function getInstance():Singleton {
		return instance;
	}
}
I to w zasadzie tyle, choć po tym prostym przykładzie trudno stwierdzić jakieś konkretne zalety na rzecz Singleton'ów, dlatego pomyślmy o nieco bardziej rozbudowanej klasie, na przykład kontroli dźwięków w aplikacji. Zwykle w takim przypadku najlepiej trzymać dźwięki w jednym miejscu by wczytywać, odtwarzać i zatrzymywać je za jednym zamachem, a więc właściwie idealna sytuacja by wykorzystać taki singleton (w bardzo uproszczonej formie):
public class SoundControl {
	private static var instance:SoundControl;
	private var bIsLoading:Boolean;
	private var aQueue:Array;
	private var aSounds:Array;
	public function SoundControl(f:Function = null) {
	if(f != getInstance) throw new Error("SoundControl nie moze byc powielany! Skorzystaj z getInstance().");
		aSounds = new Array();
		aQueue = new Array();
	}
	public function play(name:String):void {
		if(bIsLoading) {
			aQueue.push(name);
		} else {
			aSounds.push( new Sound(name) );
			/* odtworz dzwiek */
		}
	}
	public function stopAll():void {
		for each(var snd:Sound in aSounds) {
			snd.stop();
		}
		aQueue = new Array();
	}
	private function loadAll():void {
		bIsLoading = true;
		/* wczytaj wszystkie dzwieki i na koniec wywolaj onLoaded */
	}
	private function onLoaded():void {
		bIsLoading = false;
		/* odtworz wszystkie dzwieki przechowywane w aQueue */
	}
	public static function getInstance():Singleton {
		if(instance == null) {
			instance = new SoundControl(getInstance);
			instance.loadAll();
		}
		return instance;
	}
}
Jak widać SoundControl robi całkiem sporo, jednak dla osoby "z zewnątrz" widoczne będą tylko trzy funkcje: getInstance(), play() oraz stopAll(), czyli właściwie wszystko czego by potrzebował do prawidłowego odgrywania dźwięków w aplikacji, zostawiając całą brudną robotę (kontrola głośności, przydzielanie kanałów, zwalnianie zasobów itp.) nam, twórcom klasy. Dodatkowo cały czas mamy pewność, że nikt nie stworzy sobie osobnego obiektu SoundControl i nie zacznie odtwarzać dźwięków na własną rękę by potem wszyscy główkowali skąd one pochodzą, przecież jeden jedyny obiekt SoundControl z którego wszyscy mieli korzystać nic o tych dźwiękach nie wie...

Podstawy Stage3D część II: poruszanie kamerą i funkcja poinatAt [ekspert]

W poprzedniej części artykułu pisałem jak rozbudować przykład z dokumentacji ActionScript3 na temat grafiki 3D by obsługiwał wyświetlanie wielu obiektów przy jednoczesnym zachowaniu możliwości transformacji każdego obiektu z osobna. Pojawiła się tam również wzmianka o matrycy odpowiedzialnej za kamerę ("view"), która tak naprawdę nie do końca działała jak należy ponieważ brakowało w niej kilku podstawowych ustawień. Tu właśnie zaczyna się schody dla osób które nigdy nie miały styczności z grafiką 3D, ponieważ informacje dostępne w Internecie na temat owych ustawień i jak powinny wyglądać są dość mieszane, szczególnie że prawidłowe poruszanie kamerą będzie zależało od ustawień i implementacji samego środowiska 3D. Dlatego tą cześć artykułu o podstawach Stage3D poświecę prawidłowemu stosowaniu wirtualnej kamery, a w szczególności konstrukcji, pozycjonowaniu i korzystaniu z funkcji pointAt.

Zaczynając gdzie skończyliśmy ostatnim razem (kod źródłowy z poprzedniej części artykułu: Tutorial3D.zip) mamy już całkiem niezły start do zaimplementowanie wirtualnej kamery. Co prawda można dla niej przygotować jakąś osobną klasę, ale matryca na razie w zupełności wystarcza. W każdym razie, matryca kamery jest gotowa i można z niej skorzystać jednak rezultaty transformacji będą co najmniej... nieprzewidywalne, albo przynajmniej innej niż się można było by spodziewać (np. x+=-5 okaże się przesunięciem w prawo zamiast lewo). Wiąże się to z pominięciem pewnego szczegółu w funkcji renderującej w przykładzie dostarczonym przez Adobe, a który może nam przysporzyć kilku nieprzespanych nocy. O co dokładnie chodzi? Zerknijmy jak tworzona jest matryca finalTransform w funkcji render:

finalTransform.identity();   
finalTransform.append(box.getMatrix3D());   
finalTransform.append(world);   
finalTransform.append(view);   
finalTransform.append(projection);
Niby wszystko wydaje się w porządku, ale wystarczy się zatrzymać na chwilę i przyjrzeć dokładnie - powiedzmy, że chcemy aby cały świat był przesunięty w lewo, a kamera w prawo, dlatego ustawiamy world na x = -10, a view na x = 5. W idealnym świecie oznaczało by to, że teraz jest między nimi 15 jednostek różnicy, ale oczywiście w naszej aplikacji tak nie będzie, a wszystko przez to jak powstaje finalTransform. Spójrzmy jeszcze raz, world to przesuniecie o -10 a view o +5:
finalTransform.append(-10);   
finalTransform.append(5);
Wiec jaki będzie wynik? Oczywiście każdy obiekt w programie zostanie najpierw przesunięty w lewo (-10) a potem w prawo (+5), ostatecznie lądując na pozycji x = -5. Niby oczywista oczywistość, ale bardzo ważne jest zrozumienie, że tak naprawdę nie przesuwamy kamery, ale cały świat wokół niej. I to jest ten ważny szczegół: nie przesuwamy kamery w lewo, tylko cały świat w prawo! I prawda jest taka, że można było by to załatwić prostym "po prostu transformuj kamerę w przeciwnym kierunku i będzie ok", ale … to tragiczny pomysł - jeszcze rotacje i pozycje da się tak zmieniać, ale o funkcji poinatAt możesz zapomnieć. Na szczęście właściwie rozwiązanie jest bardzo proste, wystarczy obrócić matrycę widoku:
var camera:Matrix3D = view.clone();  
camera.invert();
Zgadza się, tyle wystarczy. Cała funkcja render będzie teraz wyglądać tak:
private function render(event:Event):void {  
renderContext.clear(.3, .3, .3);  
var camera:Matrix3D = view.clone();  
camera.invert();  
for each(var box:Box3D in vBox) {  
finalTransform.identity();  
finalTransform.append(box.getMatrix3D());  
finalTransform.append(world);  
finalTransform.append(camera);  
finalTransform.append(projection);  
renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true);  
box.render(renderContext);  
}  
renderContext.present();  
}
Te dwie linijki kodu wystarczą by nasza kamera zaczęła działać dokładnie tak jak się tego spodziewamy, przesunięcie view w lewo autentycznie będzie wyglądać jakbyśmy przesunęli kamerę w lewo, a co najważniejsze będziemy mogli skorzystać z funkcji pointAt, o czym napiszę za chwilę. W tym momencie najlepiej byłoby przetestować poruszanie widokiem w rezultacie dostając coś takiego:
(Poruszanie na klawiszach strzałek)
W razie czego możesz skorzystać z kodu na końcu poradnika.
Teraz gdy już wszystko jest poprawnie ustawione, czas wziąć się za funkcję pointAt. Bez wiedzy o tym, że matryce view należy obrócić (funkcją invert) przed użyciem w finalTransform, pointAt nigdy nie działało by jak należy! Jednak to nie koniec problemów, zostaje jeszcze kwestia parametrów które są potrzebne dla pointAt, bo oczywiście domyślne nie będą działać. Te parametry to:
1. Pozycja celu - matryca zostanie zwrócona w kierunku tego punktu.
2. Kierunek zwrotu - innymi słowy kierunek "do przód" bez żadnych transformacji; dla kamery zawsze będzie to Vector3D(0, 0, -1).
3. Kierunek wznoszenia - w którą stronę jest "do góry"; domyślnie Vector3D(0, -1, 0).
To powinno wystarczyć. Poprawnie działający pointAt będzie wyglądać tak:
Niestety, zmiana pozycji nie uwzględnia kierunku w jakim spogląda kamera - zostawmy to na kiedy indziej.
Kod źródłowy: Tutorial3D-2.zip

Podstawy Stage3D część I: wyświetlanie wielu obiektów [zaawansowany]

Dwa tygodnie temu pisałem jak przystosować Flash Professional do obsługi Flash Player 11 przy okazji korzystając z przykładu w dokumentacji na wyświetlanie grafiki 3D (http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display3D/Context3D.html#includeExamplesSummary). Jednak skorzystanie z gotowego rozwiązania to jedno, a zrozumienie co właściwie robi i jak działa to już zupełnie inna sprawa - dlatego tym razem napiszę co nieco o podstawowych zagadnieniach Stage3D (jak to Adobe nazywa swoją akcelerację 3D we Flash'u).
Tak na wszelki wypadek cały kod z owego przykładu (wraz z dodatkowymi bibliotekami potrzebnymi do jego uruchomienia) znajduje się tutaj: Context3DExample.zip

Pierwszą cześć tego artykułu poświęcę na opis tworzenia i wyświetlania kilku obiektów naraz przy jednoczesnym zachowaniu możliwości ich transformacji. Dla ułatwienia sugeruje stworzyć osobną klasę dla sześcianów, kopiując już istniejący kod (z przykładu w dokumentacji) odpowiedzialny za tworzenie geometrii: IndexBuffer i VertexBuffer. Taka klasa może wyglądać tak:

private var vbBuffer:VertexBuffer3D;
private var ibIndex:IndexBuffer3D;
private var m3D:Matrix3D;
private var nCount:int;

public function Box3D(context:Context3D) {
			
            var triangles:Vector.<uint> = Vector.<uint>([...]);
	ibIndex = context.createIndexBuffer(triangles.length);
	ibIndex.uploadFromVector(triangles, 0, triangles.length);
			
	const dataPerVertex:int = 6;
	var vertexData:Vector.<Number> = Vector.<Number>([...]);
	vbBuffer = context.createVertexBuffer(vertexData.length / dataPerVertex, dataPerVertex);
	vbBuffer.uploadFromVector(vertexData, 0, vertexData.length / dataPerVertex);
					
	m3D = new Matrix3D();
	nCount = 12;	
}
				
public function render(context:Context3D):void {
	context.setVertexBufferAt( 0, vbBuffer, 0, Context3DVertexBufferFormat.FLOAT_3 );
	context.setVertexBufferAt( 1, vbBuffer, 3, Context3DVertexBufferFormat.FLOAT_3 );
	context.drawTriangles(ibIndex, 0, nCount);
}
				
public function getMatrix3D():Matrix3D {return m3D;}
Nie ma tutaj nic szczególnie skomplikowanego - warto jedynie pamiętać, że matryca m3D będzie przechowywać transformacje (pozycję, skalę) więc sześcian najlepiej zbudować na pozycji 0,0,0 a później go przestawić w wybrane miejsce za pomocą funkcji appendTranslation.
Może jeszcze tylko wyjaśnię, że funkcją setVertexBufferAt ustawiamy który VertexBuffer będzie wykorzystywany do wyrysowania trójkątów (funkcja drawTriangles), a wywoływana jest dwa razy bo za pierwszym razem przekazujemy wierzchołki, a za drugim kolory.
Tak więc przygotowanie klasy sześcianu jest relatywnie łatwe, problemy zaczynają się gdy chcemy je wyświetlić. Zakładając, że wszystkie te kostki będą przechowywane w wektorze vBox funkcję render w przykładzie Context3DExample należy przebudować na coś takiego:
private function render(event:Event):void {
	renderContext.clear(.3, .3, .3);
			
	for each(var box:Box3D in vBox) {
		finalTransform.identity();
		finalTransform.append(box.getMatrix3D());
		finalTransform.append(world);
		finalTransform.append(view);
		finalTransform.append(projection);
	renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true);
				
		box.render(renderContext);
	}
			
	renderContext.present();
}
Tutaj już się robi trochę ciekawiej, a to za sprawą dwóch najważniejszych rzeczy: funkcji setProgramConstantsFromMatrix oraz konstrukcji matrycy finalTransform. Za pomocą setProgramConstantsFromMatrix ustawiamy matrycę (transformację) dla renderowanego obiektu i nie da się jej pominąć (a przynajmniej nie ma to sensu, bo nic nie zobaczymy na ekranie), podczas gdy finalTransform posłuży nam do przygotowanie owej matrycy.
Dla osób który nigdy nie miały do czynienia z grafiką 3D będzie to zapewne z początku trudne do zrozumienia, ale wszystko sprawdza się do faktu, że każdy obiekt (sześciany w naszym wypadku) tak naprawdę istnieje niezmodyfikowany przez cały czas działania aplikacji. Innymi słowy gdzieś tam tworzymy naszą kostkę, definiujemy jej trójkąty i nadajemy kolor, ale sama z siebie nie ma żadnej pozycji w przestrzeni - do tego wykorzystywane są właśnie matryce. Sęk w tym, że matryce również same z siebie nic konkretnego nie robią i nie mają żadnego bezpośredniego wpływy na geometrię aż do momentu gdy nie wykorzystamy setProgramConstantsFromMatrix by połączyć geometrię z transformacją. Jednak należy pamiętać, że jest to tylko pewnego rodzaju filtr - "Program" (osobny kod odpowiedzialny za renderowanie) bierze geometrię i przepuszcza ją przez matryce, a rezultat wyświetla na ekranie, nie modyfikując przy tym żadnych zmiennych.
Teraz jeśli chodzi o finalTransform - każdą klatkę animacji zaczynamy od wyzerowania tej matrycy (funkcja identity) by mieć pewność, że nie pozostaną tam jakieś inne transformacje. Ma to szczególne znacznie podczas operacji dodawania (append), ponieważ już istniejąca transformacja ma wpływ na późniejsze rezultaty, na przykład dodanie przesunięcia o 10 jednostek w lewo a potem obrotu o 90 stopni ustawi obiekt w innym miejscu niż gdybyśmy najpierw dodali obrót o 90 stopni a potem przesuniecie o 10 jednostek w lewo. Dokładnie jak w prawdziwym świecie, kierunek jaki obejmiemy na komendę "idź w lewo" będzie zależeć od tego w jaką stronę już spoglądamy. W każdym bądź razie, najlepiej samemu z tym poeksperymentować, pamiętając jedynie, że kolejność dodawani do siebie matryc ma ogromne znaczenie i dlatego właśnie w takiej a nie innej kolejności dodawane są do finalTransform matryce odpowiedzialne za: świat (world), pozycję kamery (view) oraz sposób wyświetlania (projection).

Jak zwykle cały kod użyty w tym artykule jest spakowany i gotowy do pobrania stąd: Tutorial3D.zip

Publikowanie dla odtwarzacza Flash 11 w Flash Professional CS5.5 [podstawowy]

Niedawno Adobe wypuściło nową wersję Flash Player'a, tym razem z numerem 11 wprowadzając między innymi akceleracje 3D (o której mam nadzieje napisać co nieco w przyszłości), obsługę większych bitmap oraz JSON itp. Jednak tych wszystkich którzy już chcieli by skorzystać z nowych funkcji wykorzystując Flash Professional czeka niemiła niespodzianka - Adobe wciąż nie udostępniło żadnych aktualizacji pozwalający na publikowanie plików Flash'a w nowej wersji. Problem można obejść, aczkolwiek nie będzie to idealne rozwiązanie, choć z drugiej strony nie ma innego sposób oprócz przejścia na inne środowisko programistyczne (jak np. darmowy Flashdevelop który jest świetną alternatywą dla osób chcących zająć się programowanie w ActionScript na poważnie).

Tak więc, jak dodać obsługę Flash Player'a 11 w Flash Professional CS5.5?

1. Przede wszystkim potrzebne są najnowsze biblioteki Flash'a: http://www.adobe.com/support/flashplayer/downloads.html. Pod koniec paragrafu poświęconemu "ADOBE FLASH PLAYER 11" znajduje się link do pliku "PlayerGlobal (.swc)" który będzie potrzebny w następnym kroku.

2. Teraz przejdź do folderu w którym zainstalowałeś Flash Professional (na XP domyślnie jest to: "C:\Program Files\Adobe\Adobe Flash CS5.5"), następnie do "Common" -> "Configuration" -> "ActionScript 3.0". W tym miejscu powinny już się znajdować takie foldery jak "FP9" lub "AIR2.5" - my zajmiemy się Flash Player 11 dla którego należy stworzyć "FP11". Do nowo powstałego "FP11" wrzuć wcześniej ściągnięty "playerglobal11_0.swc" i zmień jego nazwę na: "playerglobal.swc". Ostatecznie ścieżka do tego pliku powinna wyglądać mniej więcej tak: "C:\Program Files\Adobe\Adobe Flash CS5.5\Common\Configuration\ActionScript 3.0\FP11\playerglobal.swc".

3. Owe biblioteki pozwalają nam korzystać z najnowszych funkcji dostępnych w ActionScript ale musimy jeszcze powiedzieć Flash Professional by z nich korzystał. W tym celu udaj się do folderu "Players" (znajduje się w "Configuration", czyli parę kroków wstecz od naszej ostatniej lokalizacji) i zduplikuj plik powiązany z "FlashPlayer" (jeśli będzie ich kilka zduplikuj ten z najwyższą liczbą w nazwie) przy okazji nadając mu nazwę "FlashPlayer11". Następnie otwórz ten plik w jakimś edytorze tekstowym (może to być Notatnik) i u samej góry znajdź linijki w których będzie pisać coś na wzór:

  <player id="FlashPlayer10" version="10" asversion="3">
   <name>Flash Player 10 & 10.1</name>
Zastąp je tym:
  <player id="FlashPlayer11" version="13" asversion="3">
   <name>Flash Player 11</name>
("version="13"" to nie błąd)
Do tego jeszcze parę linijek niżej znajduje się wpis: "playerglobal.swc" którą należy zmienić tak aby wskazywała na folder "FP11" (który utworzyliśmy w poprzednim kroku).
Ewentualnie plik profilu można pobrać stąd: FlashPlayer11.zip

4. Teoretycznie tyle wystarczy by móc eksportować pliki SWF do najnowszej wersji. Niestety "teoretycznie" dlatego, że praktycznie w Flash Professional ich nie obejrzysz, gdyż ten korzysta z odtwarzacza w starej wersji. W tym momencie mam dobrą i złą wiadomość: owy odtwarzać można podmienić... ale tylko dla "debuggera" (zwyczajne testowanie filmów przez CTRL+ENTER nie będzie działać). Właśnie dlatego nie jest to idealne rozwiązanie, choć zapewne lepsze to niż nic.
By podmienić stary odtwarzać Flash musisz najpierw ściągnij najnowszą wersję projektora przeznaczonego do debugowania: http://www.adobe.com/support/flashplayer/downloads.html ("Flash Player 11.0 Projector content debugger"). Następnie odtwórz folder "Players" znajdujący się w miejscu instalacji Flash Profesional (na XP będzie to: "C:\Program Files\Adobe\Adobe Flash CS5.5\Players"). Znajdować się tam powinien folder "Debug" a w nim "FlashPlayerDebugger.exe" i to właśnie ten plik trzeba zastąpić projektorem ściągniętym ze strony Adobe (prawdopodobnie będziesz musiał mu zmienić nazwę na " FlashPlayerDebugger.exe").

5. Pozostaje już tylko uruchomić Flash Professional i sprawdzić czy tryb debugowania działa (domyślnie CTRL+SHIFT+ENTER) korzystając z przykładu w dokumentacji: flash.display3D.Context3D - ActionScript 3.0 Reference (na samym dole strony; ponadto przeczytaj komentarze pod przykładem).

Silnik 3D dla początkujących III [zaawansowany]

W poprzednich częściach tego poradnika poświęconemu tworzeniu bardzo minimalistycznego silnika 3D wspomniałem jak stworzyć iluzję perspektywy oraz jak płynnie poruszać kamerą. Tym razem zajmiemy się obrotem widoku. Będzie to już ostatni rozdział tego poradnika gdyż dalsze zagłębianie się w temacie wymagać będzie znajomości bardziej zaawansowanych zagadnień, a wtedy już po prostu lepiej wziąć się za "prawdziwy" silnik (jak np. Alternativa3D lub Away3D). Do tego najnowsza wersja Flash'a z numerem 11 rozstała rozszerzona o akcelerację 3D co oznacza, że nic nie stoi na przeszkodzie by napisać w ActionScript gry na miarę naszych czasów jak Crysis albo Gears of War (szczególnie, że Adobe zaprezentowało Unreal Engine 3 działający we Flash'u).
No ale na to przyjdzie jeszcze czas, na razie zajmijmy się tą rotacją.

1. Za nim przejdę do konkretów do klasy utworzonej w poprzednich rozdziałach dopiszemy dwie stałe oraz jedną zmienną:

const SPEED:Number = 20;
const ROTATION:Number = Math.PI/16;
var a:Number = 0;
SPEED będzie prędkością poruszania się kamery, a ROTATION prędkością obrotu. Zmienna a posłuży do przechowywania aktualnego kąta obrotu.

2. Pamiętasz gdzie znajduje się kod odpowiedzialny za transformacje informacji 3D na 2D? Zmodyfikujemy go nieco by uwzględniał rotacje widoku:

var nx:Number;
var nz:Number;
var rx:Number;
var rz:Number;
var bx2:Array = new Array(bx.length);
var by2:Array = new Array(by.length);
var i:Number = 0;
while(i<bz.length) {
	nx = cx-bx[i];
	nz = cz-bz[i];
	rx = cx+( nx*Math.cos(-a) )-( nz*Math.sin(-a) );
	rz = cz+( nx*Math.sin(-a) )+( nz*Math.cos(-a) );
	bx2[i] = vw+( ((cx-rx) / (cz-rz) )*vw);
	by2[i] = vh+( ((cy-by[i]) / (cz-rz) )*vh);
	++i;
}
Właściwie różnica nie jest duża, dodałem jedynie parę zmiennych dla przejrzystości oraz wzór na obrót (wzdłuż osi Y, czyli tak samo jak w każdej grze FPS). Jeśli matematyka nie jest ci obca prawdopodobnie nie znajdziesz tutaj nic szokującego. W skrócie z każdego punktu wyliczany jest wektor przesunięcia (na przykładzie x: nx = cx-bx[i];), który następnie poddawany jest obrotowi (( nx*Math.cos(-a) )-( nz*Math.sin(-a) );) i ustawiany na nową pozycję ( rx = cx+...). Nowy punkt trafia do znanej nam już transformacji z 3D do 2D.

3. Teraz pozostaje nam jeszcze tylko zmodyfikować sposób poruszania się kamery tak aby korzystała ona z rotacji. W tym celu wrócimy do kodu odpowiedzialnego za przetwarzanie informacji o wciśniętych klawiszach na komendy w aplikacji, czyli switch(key). Przy okazji to również dobry moment by go trochę uprościć - stary kod w całości zastąp tym:

switch(key&0x0000ff) {
case 0x00000f:
		cz += Math.cos(-a)*SPEED;
		cx += Math.sin(-a)*SPEED;
	break;
	case 0x0000f0:
		cz -= Math.cos(-a)*SPEED;
		cx -= Math.sin(-a)*SPEED;
		break;
}
switch(key&0x00ff00) {
	case 0x000f00:
	a += ROTATION;
		break;
	case 0x00f000:
		a -= ROTATION;
		break;
}
W poprzedniej wersji switch sprawdzał wszystkie możliwe kombinacje zmiennej key, podczas gdy tutaj pierwszy switch wyciąga z key stan jedynie klawiszy góra i dół (0x000f0f&0x0000ff da w rezultacie 0x00000f a więc informacja o wciśniętym klawiszu lewo, 0x000f00, zostanie pominięta), a drugi klawiszy lewo i prawo.
Okej, więc teraz lepiej rozróżniamy wciśnięte klawisze, ale co właściwie się tutaj dzieje? Zaczynając od końca: klawisze lewo i prawo odpowiedzialne są za zmianę kąta obrotu, podczas gdy góra i dół za poruszanie się do przodu i do tyłu. Oczywiście teraz gdy mamy do czynienia z rotacją, definicja "do przodu" cały czas się zmienia stąd też potrzeba wykorzystania funkcji cos i sin które w tym przypadku wyliczają dla nas wektor przesunięcia zależny od aktualnego kąta.

4. Czas sprawdzić czy wszystko działa. Efekt końcowy powinien wyglądać mniej więcej tak :
(Poruszanie się na strzałkach)


A cała klasa do ściągnięcia jest tutaj: box3d_rot.zip

Od AS2 do AS3: duplicateMovieClip() w ActionScript 3 [zaawansowany]

Programiści którzy postanowią przejść na AS3 z AS2 prędzej czy później na pewno zauważą kompletny brak odpowiednika funkcji duplicateMovieClip() w najnowszej iteracji ActionScript'a. Może wydawać się to nieco dziwne, w końcu trudno sobie wyobrazić by Adobe nie było wstanie zaimplementować takiej funkcji - powodem ten decyzji jest prawdopodobnie chęć zabicia złych nawyków w użytkownikach AS3. W każdym razie, czasem po prostu trzeba zduplikować jakiś MovieClip dlatego poniżej prezentuje listę możliwych rozwiązań:

1. Własna klasa.
Myślę, że około 90% ludzi którzy szukają alternatywy dla duplicateMovieClip() powinni być zadowoleni z tego rozwiązania. W skrócie chodzi tutaj o stworzenie klasy podrzędnej MovieClip'a i powiązanie jej z grafiką którą chcemy powielić. Dla przykłady powiedzmy, że tworzymy aplikacje która w tle będzie miała spadające gwiazdy i oczywiście by uzyskać ten efekt stworzymy MovieClip jednej gwiazdy a następnie zaczniemy ją kopiować tyle razy ile jest to potrzebne. W tym celu należy udać się do właściwości MovieClip'a owej gwiazdy i zaawansowanych opcjach zaznaczyć "Eksportuj dla ActionScript".
Eksporotwanie dla ActionScript
Od teraz klasa "Star" zawiera grafikę gwiazdy i można ją duplikować do woli pisząc:

var mc:MovieClip = new Star();
addChild(mc);
I tyle.

2. Skorzystanie z funkcji loadBytes() w klasie Loader.
Więc metoda #1 jest całkiem przyjemna ale co jeśli chcemy zduplikować plik wczytany z zewnętrznego źródła? Zobaczmy jak może w takim wypadku wyglądać ładowanie grafiki:

import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;

lLoad = new Loader();
lLoad.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoaded);
lLoad.load(new URLRequest("your_file.swf"));

function onLoaded(e:Event):void {
	addChild(lLoad.content);
}
Cóż, klasa Loader nie ma zbyt wielu funkcji i po załadowaniu pliku SWF możemy go praktycznie tylko dodać na scenę albo załadować inną grafikę. Oczywiście ponowne wczytanie grafiki z serwera odpada (aczkolwiek jeśli użytkownik ma włączone przechowywanie obiektów w pamięci podręcznej Flash skorzysta właśnie z niej zamiast nawiązywać połączenie jeszcze raz) dlatego duplikacji dokonamy wykorzystując loadBytes(). Trik polega na tym, że loadBytes() jest w stanie wczytać dowolny, obsługiwany przez load(), obiekt zapisany w ByteArray. Pytanie tylko jak uzyskać ByteArray wczytanego MovieClip'a? Okazuje się, że rozwiązanie jest bardzo proste:
function onLoaded(e:Event):void {
	addChild(lLoad.content);
	lLoad.loadBytes(lLoad.content.loaderInfo.bytes);
}
Wartość lLoad.content.loaderInfo.bytes przechowuje wczytaną animację jako ByteArray, więc wystarczy ją wrzucić do funkcji loadBytes by ponownie wywołać zdarzenie onLoaded i dostać kolejny MovieClip bez nawiązywania jakichkolwiek dodatkowych zewnętrznych połączeń.
Jest tylko jeden problem, zdarzenie onLoaded nie zostanie wywołane natychmiast... co oznacza, że wczytany MovieClip zostanie zduplikowany dopiero po kilku lub kilkunastu milisekundach (zależnie od wartości FPS).

3. Skorzystanie z wartości constructor w obiekcie wczytam przez Loader.
Alternatywnym rozwiązaniem do duplikowania SWF wczytanych z zewnątrz jest wykorzystanie wartości constructor dostępnej we wszystkich klasach Flash'a. Bez owijania w bawełnę korzysta się z niej w następujący sposób:

function onLoaded(e:Event):void {
	var d:DisplayObject = new lLoad.content["constructor"]();
	addChild(d);
}
(Zmienna constructor jest dynamiczna więc to jedyny sposób by się do niej dostać. Ponadto nie zapomnij o słowie kluczowym new!)
Niestety, nie jest to idealne rozwiązanie. Kod zdziała tylko wtedy gdy wczytana animacja będzie miała niestandardową "Klasę Dokumentu" (może to być cokolwiek, byle tylko pole "Klasa Dokumentu" nie było puste), co w praktyce oznacza, że to ty musisz być twórcą wczytywanego pliku.

By zaoszczędzić trochę na pracy zaimplementowałem te rozwiązania w jedną klasę - powinna być w stanie zduplikować każdy MovieClip (i nie tylko): foras_clone.zip
A korzysta się z niej w następujący sposób:

import foras.utils.ForCloner;
import flash.display.DisplayObject;
if(stage==null) return;
var fas_Clone:ForCloner = new ForCloner();
var d:DisplayObject = fas_Clone.cloneMovie(my_mc,onClone);
if(d!=null) {
	d.x = stage.stageWidth*Math.random();
	d.y = stage.stageHeight*Math.random();
	addChild(d);
}

function onClone(d:DisplayObject):void {
	d.x = stage.stageWidth*Math.random();
	d.y = stage.stageHeight*Math.random();
	addChild(d);
}
Ponieważ klasa ForCloner również korzysta z metody loadBytes() (o której pisałem w punkcie 2) potrzebna jest funkcja która zajmie się zdarzeniem wczytania pliku i właśnie dlatego cloneMovie() jej wymaga. Oczywiście może się zdarzyć, że wcale nie będzie potrzebna i cloneMovie() natychmiast zwróci zduplikowany MovieClip. Najczęściej jednak będzie to null i wtedy kopia animacji trafi do wybranej funkcji (w tym przykładzie onClone).
Jeszcze jedna uwaga na koniec, linijka if(stage==null) return; zatrzymuje kod przed ponowny wywołaniem - jeśli wszystko inne zawiedzie zduplikowana zostanie cała aplikacja, a to oznacza wczytanie również kodu drugi raz (z całości zostanie wyciągnięty tylko docelowy MovieClip, a reszta zostanie usunięta).

Silnik 3D dla początkujących II [zaawansowany]

W pierwszej części poradnika opisałem jak stworzyć podstawy silnika 3D który będzie w stanie wygenerować trójwymiarowy sześcian, jednak bez możliwości poruszania się taki statyczny obraz zapewne nie był zbyt imponujący. Dlatego w tej część zajmiemy się mechanizmami kontroli kamery.
Zacznijmy od dodania do naszego konstruktora box3d dwóch zdarzeń związanych z klawiaturę:
stage.addEventListener(KeyboardEvent.KEY_DOWN,eventKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP,eventKeyUp);
(Nie zapomnij zaimportować flash.events.KeyboardEvent!)
Nasłuchiwacze zostały powiązane z wartością stage ponieważ w przypadku innych obiektów trzeba byłoby najpierw ustawić na nich "focus" co na razie jest nam kompletnie nie przydatne i stage w zupełności wystarczy. W każdym razie, musimy jeszcze stworzyć funkcje obsługujące owe zdarzenia:
private function eventKeyDown(e:KeyboardEvent):void {

}
private function eventKeyUp(e:KeyboardEvent):void {

}
W tym momencie sugerował bym dopisać do którejś z nich linijkę trace(e.keyCode); by upewnić się, że Flash poprawnie przechwytuje naciśnięte klawisze.
W następny kroku zajmiemy się już właściwym wyłapywaniem klawiszy i tłumaczeniem ich na odpowiedni ruch kamerą, i choć całość można byłoby załatwić pisząc po prostu:
if(e.keyCode == Keyboard.LEFT) cx -= 20;
else if(e.keyCode == Keyboard.RIGHT) cx += 20;
Takie rozwiązanie jest niestety nie tylko ograniczające ale w dodatku niezbyt płynne. Dlatego w naszym silniku zastosujemy nieco bardziej zaawansowaną metodę opartą o operacje bitowe - tylko za nim ją opisze, dodaj do klasy nową zmienną: var key:Number = 0; której używać będziemy w następujący sposób:
private function eventKeyDown(e:KeyboardEvent):void {
	switch(e.keyCode) {
		case Keyboard.UP:
			key = key | 0x00000f;
			break;
		case Keyboard.DOWN:
			key = key | 0x0000f0;
			break;
		case Keyboard.LEFT:
			key = key | 0x000f00;
			break;
		case Keyboard.RIGHT:
			key = key | 0x00f000;
			break;
	}
}
(Nie zapomnij importować flash.ui.Keyboard!)
Okej, musze przyznać, że na pierwszy rzut oka wygląda to trochę bez sensu (szczególnie dla osób które nie miały styczności z operacjami bitowymi lub liczbami szesnastkowymi), ale za parę chwil wszystko stanie się jasne. Owy kod przypisuje pewne wartości do zmiennej key zależnie od wciśniętego klawisza - wciskając klawisze strzałek "do góry" i "w lewo" key będzie mieć wartość "0x000f0f". Rezultat ten otrzymujemy poprzez operacje bitowe na liczbach szesnastkowych, a więc dla przykładu jeśli weźmiemy liczby "0x000f00" oraz "0x00000f" i wykonamy na nich operację | (OR) dostaniemy "0x000f0f". Ta kreska | jest znana jako bitowe "lub" i przypomina w pewnych sytuacjach dodawanie - 100|1 da wynik 101 (więcej informacji znajdziesz w dokumentacji ActionScript'a). W każdym razie, dla nas ważne jest tylko to, że key będzie przypominał trochę jakby listwę ze światełkami gdzie 0 = zgaszona lampka (klawisz nie jest wciśnięty) a f = zapalona lampka (klawisz jest wciśnięty). 0x00ffff = wszystkie klawisze wciśnięte, 0x00f00f0 = tylko klawisz w dół i w prawo, 0x000000 = żaden klawisz nie jest wciśnięty itd.
Jednak sam eventKeyDown służy nam jedynie do zapalania światełek. Do pełnego obrazu brakuje nam jeszcze przeciwnej funkcji czyli:
private function eventKeyUp(e:KeyboardEvent):void {
	switch(e.keyCode) {
		case Keyboard.UP:
			key = key ^ 0x00000f;
			break;
		case Keyboard.DOWN:
			key = key ^ 0x0000f0;
			break;
		case Keyboard.LEFT:
			key = key ^ 0x000f00;
			break;
		case Keyboard.RIGHT:
			key = key ^ 0x00f000;
			break;
	}
}
W tym wypadku bitowe ^ (XOR) działa trochę jak odejmowanie, a więc operacja " 0x000f0f ^ 0x00000f" zwróci "0x000f00".
Najgorsze za nami, teraz już mamy w pełni funkcjonalną zmienną key która sygnalizuje nam jakie klawisze są w użyciu i pozostaje nam jedynie przetłumaczyć je na ruch kamery. W tym celu przejdź do funkcji render (utworzyliśmy ją w poprzedniej część poradnika) i przed linijką graphics.clear(); dopisz:
switch(key) {
	case 0x00000f: // ^
		cz += 20;
	break;
	case 0x0000f0: // v
		cz -= 20;
		break;
	case 0x000f00: // <
		cx -= 20;
		break;
	case 0x00f000: // >
		cx += 20;
		break;
}
To już prawie koniec. Skompiluj aplikacje i sprawdź czy wszystko działa poprawnie. Jeśli wcześniej próbowałeś poruszać kamerą bezpośrednio z poziomu eventKeyDown i eventKeyUp na pewno zauważyłeś teraz poprawę (a jeśli nie, proponuję powrót do owego rozwiązania i porównanie z aktualnym).
W każdym razie, została jeszcze jedna mała rzecz - poruszanie się po skosie. Używanie dwóch klawiszy naraz ustala wartość key na liczbę którą nasz switch(key) nie obsługuje, dlatego na koniec musimy go jeszcze rozbudować o cztery dodatkowe warunki:
	case 0x000f0f:
		cz += 20;
		cx -= 20;
		break;
	case 0x00f00f:
		cz += 20;
		cx += 20;
		break;
	case 0x000ff0:
		cz -= 20;
		cx -= 20;
		break;
	case 0x00f0f0:
		cz -= 20;
		cx += 20;
		break;
I tyle. W razie czego całą klasę można pobrać stąd: box3d_2.zip
A sama aplikacja zobaczyć tutaj: box3d.swf


<<<Nowsze postyStarsze posty>>>