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

Podstawy Stage3D część IV: korzystanie z shaderów [ekspert]

Shadery są już nie odłączanym elementem w grafice 3D i to do tego stopnia, że nie możliwe jest wyświetlanie obrazu bez ustawienia chociaż najprostszego programu cieniującego. Dlatego tym razem opisze pokrótce jak pracować z shaderami na przykładzie oświetlenia i choć te można symulować na kilka różnych sposobów to właśnie shadery będą najłatwiejszym rozwiązaniem na dodanie świtała w naszym wirtualnym świecie. Programu cieniujące, czyli zestawy instrukcji przekazywanych karcie graficznej wymagają znajomości AGAL który jest zupełnie innym językiem od ActionScript i na jego opanowanie potrzebny byłby całkowicie osobny poradnik - tych na szczęście jest całkiem sporo w Internecie, dlatego na razie pominę szczegóły i skupię się tylko na absolutnym minimum potrzebnym do zastosowania prostego cieniowania.
Mając kod z poprzedniego artykuły, od razu zacznijmy od przygotowania Fragment Shader'a:
private const FRAGMENT_SHADER_LIGHT:String = "dp3 ft1, fc2, v1 n"+
"neg ft1, ft1 n"+
"max ft1, ft1, fc0 n"+
"mul ft2, fc4, ft1 n"+
"mul ft2, ft2, fc3 n"+
"add oc, ft2, fc1";
oraz Vertex Shader'a:
private const VERTEX_SHADER_LIGHT:String = "mov vt0, va0n"+
"m44 op, vt0, vc0n"+
"nrm vt1.xyz, va0.xyzn"+
"mov vt1.w, va0.wn"+	
"mov v1, vt1n" +
"mov v2, va1";
(Kod zapożyczony z http://blog.norbz.net/2012/04/stage3d-agal-from-scratch-part-vii-let-there-be-light)
Powyższe shadery będą rozjaśniać renderowane trójkąty zależnie od ich kąta i ustawienia względem kamery stwarzając wrażenie jakoby ta rzucała światło. Za nim cokolwiek zrobimy z tym kodem musimy go jeszcze skompilować do specjalnego "programu", który przygotujemy wykorzystując AGALMiniAssembler:
vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER_LIGHT, false);
fragmentAssembly.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_LIGHT, false);

programPairLight = renderContext.createProgram();
programPairLight.upload(vertexAssembly.agalcode, fragmentAssembly.agalcode);
(Nie zapomnij utworzyć zmiennej programPairLight)
W tym momencie mamy w naszej aplikacji dwa shadery: standardowy programPair oraz generujący oświetlenie programPairLight. Pozostanie nam jeszcze tylko zdecydować z którego będziemy korzystać podczas renderowania grafiki 3D wykorzystując funkcje setProgram w obiekcie Context3D. Aby łatwiej było widać różnicę w ich działaniu stworzymy osobny obiekt Box3D (nazwijmy go "box3DLight"), a następnie dopiszemy kod odpowiedzialny za jego wyświetlanie przed wywołaniem present() ale po wyrysowania "box3D":
boxMat = box3DLight.getMatrix3D();
boxMat.identity();
boxMat.append(rotMat);
boxMat.appendTranslation(posVec.x + 1, posVec.y + 1, posVec.z+1);

finalTransform.identity();
finalTransform.append(boxMat);
finalTransform.append(world);
finalTransform.append(camera);
finalTransform.append(projection);
	
renderContext.setProgram(programPairLight);
renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true);

var p:Vector3D = camera.position.clone();
p.normalize();
var m3d:Matrix3D = finalTransform.clone();
m3d.invert();
p = m3d.transformVector(p);
p.normalize();
p.negate();

renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([0,0,0,0]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0,0,0,0]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, Vector.<Number>([p.x,p.y,p.z,1]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, Vector.<Number>([1, 1, 1, 1]));
renderContext.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 4, Vector.<Number>([0.6, 0.6, 0.6, 1]) );

box3DLight.render(renderContext);
Jest tego trochę, ale najważniejsze dla nas to to co się dzieje po linijce setProgram (operacje na "boxMat" i "finalTransform" to znane nam już transformacje z 3D do 2D). Termin "program" jest tutaj nieprzypadkowy bo shadery podobnie jak każdy inny program na komputerze wykonują pewne operacje matematyczne na zmiennych i stałych, aczkolwiek wykorzystując do tego bardzo nisko-poziomowy język. Dodatkowo w shaderach istnieją duże ograniczenia i tak np. zmienne mogą być tworzone tylko w programie i może ich być góra 8. Na szczęście inaczej sprawa ma się ze stałymi, ponieważ te możemy przekazywać z zewnątrz wykorzystując do tego funkcje setProgramConstantsFromVector oraz setProgramConstantsFromMatrix (również maksymalnie 8) i to właśnie dzięki nim do programu cieniującego przekażemy informacje o położeniu światła (zmienna "p") i jego kolorze.
Jeśli wszystko pójdzie zgodnie z planem powinniśmy zobaczyć coś takiego:
W tym przykładzie operujemy jedynie na dwóch shaderach, jednak nic nie stoi na przeszkodzie by stworzyć ich o wiele, wiele więcej a następnie wymieniać je funkcją setProgram gdy tylko chcemy, podobnie jak w Graphics wywołujemy funkcję lineStyle.
Całość do pobrania stąd: Tutorial3D_light.zip.

Podstawy Stage3D część III: korzystanie z rotacji [ekspert]

Kontynuując z poprzedniej części tutaj tym razem chce poruszyć temat łączenia rotacji. Każdy kto bez wcześniejszego kontaktu z 3D spróbował obrócić bryłę we Flash'owym Stage3D korzystając z funkcji appendRotation() na pewno prędzej czy później otrzymał wynik, którego się nie spodziewał. Do tego by jeszcze bardziej zniechęcić początkujących, w Internecie główną odpowiedział na owe zagadnienie jest "będziesz musiał skorzystać z liczb zespolonych (kwaternionów dokładnie)". Hmm? Dobra, nie wiem jak bardzo liczby zespolone przydają się do wyliczania obrotu, ale jeśli do rotacji 2D nie potrzebne są obliczenia 3D, to i zapewne w obrocie 3D da się przeżyć bez operacji 4D, a już szczególnie jeśli dopiero zaczynamy poruszać ten temat.
W każdy bądź razie, przejdźmy do sedna sprawy zaczynając od kodu z poprzedniego artykułu: Tutorial3D-2.zip, najpierw usuńmy wszystkie sześciany zostawiając tylko jedno pudełko, reszta nie będzie nam potrzebna. Przy okazji dodać można jeszcze zmienne do przechowywania jednego Vector3D i jednej Matrix3D - przydadzą się później.
Generalnie rotacje można dodać na dwa sposoby: lokalnie i globalnie - innymi słowy biorąc pod uwagę już istniejący obrót lub nie. Jakikolwiek sposób wybierzemy należy pamiętać, że na transformacje ma jeszcze wpływ istniejące przesunięcie, dlatego by ułatwić sobie pracę dobrze jest trzymać samą rotację w osobnej macierzy, a potem ją dodawać do transformacji naszego pudełka 3D. Tak więc zacznijmy od rotacji globalnej oraz utworzenia dodatkowej matrycy (jeśli jeszcze tego nie zrobiłeś):
private var rotMat:Matrix3D = new Matrix3D();
Następnie zmodyfikujemy kod odpowiedzialny za obsługę strzałek klawiatury tak by teraz dodawał obrót do matrycy rotMat:
switch(key&0x0000ff) {
	case 0x00000f: // forward
		rotMat.appendRotation(SPEED, Vector3D.X_AXIS);
		break;
	case 0x0000f0: // backward
		rotMat.appendRotation(-SPEED, Vector3D.X_AXIS);
		break;
}
switch(key&0x00ff00) {
	case 0x000f00: // left
		rotMat.appendRotation(SPEED, Vector3D.Y_AXIS);
		break;
	case 0x00f000: // right
		rotMat.appendRotation(-SPEED, Vector3D.Y_AXIS);
		break;
}
W tym momencie pozostaje nam już tylko dodać rotacje do obiektu sześcianu box3D zanim zostanie utworzona ostateczna transformacja w matrycy finalTransform:
var boxMat:Matrix3D = box3D.getMatrix3D();
boxMat.identity();
boxMat.append(rotMat);
W efekcie dostając:
Żeby uczynić ten przykład nieco ciekawszym i przy okazji sprawdzić, że faktycznie rotacja będzie niezależna od pozycji, możemy wykorzystać wcześniej wspomniany Vector3D. Nazwijmy go:
private var posVec:Vector3D = new Vector3D();
Jego manipulacja nie powinna być problemem na tym poziomie, dlatego wspomnę tylko aby dodać jego wartości w odpowiednim miejscu - zaraz po linijce boxMat.append(rotMat); należy dopisać:
boxMat.appendTranslation(posVec.x, posVec.y, posVec.z);
Otrzymując:
Tak więc globalna rotacja nie była szczególnie skomplikowanym zagadnieniem, ale pozostaje jeszcze kwestia lokalnej transformacji. Na szczęście i tutaj rozwiązanie jest trywialne i sprowadza się tylko do znalezienia prawidłowych osi w obiekcie:
switch(key&0x0000ff) {
	case 0x00000f: // forward
		rotMat.appendRotation(SPEED, rotMat.transformVector(Vector3D.X_AXIS));
		posVec.y += SPEED/50;
		break;
	case 0x0000f0: // backward
		rotMat.appendRotation(-SPEED, rotMat.transformVector(Vector3D.X_AXIS));
		posVec.y -= SPEED/50;
		break;
}
switch(key&0x00ff00) {
	case 0x000f00: // left
		rotMat.appendRotation(SPEED, rotMat.transformVector(Vector3D.Y_AXIS));
		posVec.x -= SPEED/50;
		break;
	case 0x00f000: // right
		rotMat.appendRotation(-SPEED, rotMat.transformVector(Vector3D.Y_AXIS));
		posVec.x += SPEED/50;
		break;
}
Wektor X_AXIS zostanie przekształcony do lokalnego systemu obiektu, dzięki czemu będziemy wiedzieli gdzie ma oś X w około której następnie dokonamy obrotu. W rezultacie jedna z krawędzi obracanego sześcianu będzie pozostawać nieruchomo, zależnie od wybranej osi:
Kod źródłowy: Tutorial3D_rot.zip

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

Skrypt generujący fajerwerki

Dzisiaj coś innego/zabawniejszego - fajerwerki! To zdaje się być całkiem popularny skrypt, żeby mieć pod ręką.
Mój generator fajerwerków można pobrać tutaj: foras.zip. Właściwa klasa to "foras.tools.ForFireworksGenerator". W paczce są jeszcze inne skrypty z których często korzystam i nie chciałem generatora oddzielać od reszty.

Ograniczanie dostępu do wybranych stron przez ActionScript

Nie ma co się oszukiwać, cokolwiek pojawi się w Internecie może być skopiowane i rozpowszechniane bez zgody właściciela i Flash nie jest tutaj wyjątkiem. Na szczęście w przeciwieństwie do zdjęcie czy muzyki, z odrobiną ActionScript można ograniczyć działanie aplikacji Flash tylko do wybranych stron.
Zadanie jest proste, pobierzemy adres z którego został uruchomiony nasz Flash i porównamy z adresem który chcielibyśmy aby był:
function checkDomain(...domains):Boolean {
	if(domains == null || domains.length==0) return false;
	
	var sList:String = ".*"+domains.join("|.*");
	var sURLswf:String = stage.loaderInfo.url;
	var sRegPattern:String = "^http(|s)://("+sList+")/";
	var regPattern:RegExp = new RegExp(sRegPattern,"i");
	
	return regPattern.test(sURLswf);
}
Funkcja checkDomain pobiera nazwy domen po przecinku i porównuje je z aktualnym adresem pliku SWF - jeśli któraś z podanych domen pasuje do URL, zwracane jest true. Do porównania wykorzystywane są wyrażenia regularne które nie są tematem tego artykuły, dlatego tylko pokrótce wyjaśnię, że zmienna "sURLswf" jest porównywana do schematu ze zmiennej "sRegPattern", a to z kolei została utworzona z listy dostarczonych domen.
Tyle w zasadzie wystarczy, ale istnieje jeszcze szansa, że nasz plik SWF zostanie wczytany przez zewnętrzny Loader, a wtedy "loaderInfo.url" zwróci oryginalni adres pliku pomimo że sam Loader jest na innej stronie. Rozwiązanie? Banalne, wystarczy do funkcji checkDomain dopisać:
var sURLLoader:String = stage.loaderInfo.loaderURL;
...
return regPattern.test(sURLswf) && regPattern.test(sURLLoader);
I gotowe, teraz już nikt nie uruchomi naszej aplikacji na niepowołanych serwerach.
Należy jednak pamiętać, że funkcja ta nie chroni nas przed tzw. hot-linkowaniem, czyli na wczytywaniu plików z naszego serwera, na zupełnie innej stronie WWW. To jednak już jest poza możliwościami Flash'a i powinno być zabezpieczone po stronie serwera - polecam poszukać informacji na temat hot-linkowania i "htaccess".
Co jednak jeśli nasz hosting po prostu nie oferuje takich zabezpieczeń?
Jest jeden trik który może nam pomóc, ale niestety nie gwarantuje 100% skuteczności - właściwie to nie jestem pewien czy w ogóle gwarantuje jakąkolwiek szansę na sukces. Za nim jednak wyjaśnię o co chodzi, upewnij się że podczas embedowania pliku SWF na swojej stronie ustawisz parametr allowScriptAccess na "sameDomain". Ten prosty zabieg pozwoli aplikacji Flash na wywoływanie poleceń JavaScript, a to z kolei pozwoli nam odczytać aktualny adres URL z paska przeglądarki. W tym celu należy do kodu ActionScript dopisać:
var  sURLTop:String;
if(ExternalInterface.available) {
	try {
		sURLTop = ExternalInterface.call("function(){return window.location.href}") as String;
	}catch(e:Error) {
		 sURLTop = "";
	}
} else {
	sURLTop = "";
}
W tym momencie osoby trzecie już nie będą w stanie zrobić wiele (jeśli wszystko poszło zgodnie z planem) - pozwalając na wykonanie JavaScript'u dostaniemy w naszym kodzie adres który od razu odrzucimy, a z kolei blokując go otrzymamy w kodzie Error który również potraktujemy jako brak dostępu (w końcu na naszej stronie JavaScript jest dostępny). To rozwiązanie byłoby idealne ale nie mamy gwarancji, że osoby przeglądające naszą stronę będą mieć JavaScript włączony...

Derpy's Story

And jakiegoś czasu pracowałem nad małą grą we Flash'u w końcu nadszedł czas by ją ukazać światu. Po 8 miesiącach pracy prezentuje wam: Derpy's Story.

Precyzyjne porównanie do typu klasy bez wykorzystywania 'is' i 'instanceof'

W ActionScript2 i 3 istnieje możliwość sprawdzenia czy dany obiekt należy do pewnej klasy - w AS2 jest to słowo instanceof a w AS3 is i oba spełniają swoja zadanie całkiem nieźle, jednak należy pamiętać, że oba zwrócą 'true' nawet jeśli docelowy obiekt nie jest dokładnie z tej samej klasy, tylko ją rozszerza. Innymi słowy porównanie obiektu MovieClip do Sprite zwróci 'true':
var mc:MovieClip = new MovieClip();
trace(mc is Sprite); //true
Tak więc co zrobić gdy chcemy sprawdzić, że wybrany obiekt faktycznie należy do docelowej klasy i nic po za tym? Rozwiązania są dwa:
1. Funkcja getQualifiedClassName.
Czyli najprostsze podejście - wyciągamy nazwę klasy w postaci String i porównujemy ją do nazwy której szukamy.
var mc:MovieClip = new MovieClip();
trace(getQualifiedClassName(mc) == "MovieClip"); //true
trace(getQualifiedClassName(mc) == "Sprite"); //false
2. Porównanie konstruktorów.
To już temat nieco bardziej zaawansowany, a do tego możliwy tylko w AS3. Nie jest to do końca jasno napisane, ale każdy obiekt w ActionScript3 ma pole o nazwie 'constructor' zawierające odniesienie do konstruktora użytego przy tworzeniu obiektu, a ponieważ w AS konstruktor w każdej klasie może być tylko jeden wystarczy je porównać:
var mc:MovieClip = new MovieClip();
var con:Object = MovieClip.prototype.constructor;
trace(mc.constructor == con); //true
Przy czym pamiętać tutaj należy o kilku rzeczach:
- ponieważ 'prototype' i 'constructor' są przydzielone dynamicznie ich czas dostępu będzie wolniejszy niż ten z wykorzystaniem funkcji getQualifiedClassName.
- aczkolwiek w odpowiednio dużej pętli, zrzucając uprzednio konstruktor do lokalnej zmiennej, czas wykonania może być nawet o połowę szybszy.
- mc.constructor w FlashDevelop (ale nie Flash Professional) najprawdopodobniej wyrzuci błąd, który można obejść rzutując mc do klasy Object:
var con:Object = MovieClip.prototype.constructor;
trace((mc as Object).constructor == con); //true
Niestety, rzutowanie spowolni czas wykonania o jakieś 10%.

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

5 powodów dla których ActionScript2 jest wciąż przydatny

ActionScript3 jest bez wątpienia bardziej zaawansowanym językiem niż ActionScript2 i już większość programistów Flash z niego korzysta, jednak to nie oznacza, że AS2 nie ma swojego prawa bytu - w końcu, musi być jakiś powód dla którego Adobe jeszcze go nie uśmierciło. Oto 5 powód dla których ActionScript2 jest wciąż przydatny:

5. Łatwiejszy w nauce.
Dla osób które zaczynają przygodę z Flash'em i dopiero zastanawiają się który język wybrać, AS2 będzie przyjemniejszym wprowadzeniem do programowania. Nie tylko pozwala pominąć trudniejsze koncepty programowania (choćby nadawanie typów), sam również idzie użytkownikowi na rękę robiąc wszystko byle tylko nie zatrzymać całej aplikacji w działaniu.

4. Skoncentrowane działanie.
ActionScript3 jest w stanie zrobić o wiele więcej niż ActionScript2, aczkolwiek w zamian może wymagać większego zaangażowania od strony programisty, czasem nawet wymagając wiedzy z poza standardowej dokumentacji (komunikacja na Socket'ach, akceleracja sprzętowa, grafika 3D). ActionScript2 potrafi mniej, ale to często wystarczy.

3. W prostych zastosowaniach prędkość nie jest problemem.
To chyba jeden z najczęściej przytaczanych argumentów wyższości AS3 nad AS2, jednak prawda jest taka, że szybszym sposobem na obciążenie procesora jest stosowanie nadmiernej ilości grafiki, niż starszego kodu. Generalnie jeśli nie wiesz czy szybszy kod Ci się przyda, to prawdopodobnie go nie potrzebujesz.

2. Stworzenie podstawowych elementów wymaga mniej czasu.
Powiedzmy, że chcemy stworzyć przycisk który przeniesie użytkownika na jakąś stronę WWW.
W ActionScript2 tyle w zupełności wystarcza:
on(press) {
	getURL("http://4as.pl");
}
Podczas gdy ActionScript3 wymaga już trochę więcej:
import flash.events.MouseEvent;
import flash.net.navigateToURL;
import flash.net.URLRequest;
buttonMode = true;
addEventListener(MouseEvent.CLICK,onMouseClick);
function onMouseClick(e:MouseEvent):void {
	navigateToURL(new URLRequest("http://4as.pl"));
}
Nie wspominając już o takich rzeczach jak duplikowanie MovieClip’ów

1. Większa kompatybilność ze starszymi urządzeniami
Czasem łatwo zapomnieć, że Flash nie jest dostępny tylko na komputerach - Z ActionScript2 łatwiej trafić do szerszego grona użytkowników, w tym posiadaczy telefonów komórkowych na których korzystanie z nowszych wersji Flash Player'a może być... kłopotliwe.

Ostatecznie jednak należy pamiętać, że mimo wszystko ActionScript3 jest lepszym językiem, którego warto się nauczyć jeśli mamy zamiar poważnie programować we Flash'u, szczególnie przy większych projektach, jak na przykład tworzeniu gier.

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


Starsze posty>>>