In questo tutorial ti presenterò lo Stardust Particle Engine. Per prima cosa ti mostrerò come impostare Stardust e poi parlerò delle responsabilità di base della classe Stardust e di come collaborano insieme per rendere Stardust un lavoro nel suo complesso.
Successivamente, esamineremo il flusso di lavoro generale di Stardust e passeremo alla creazione di un effetto particellare con stelle che escono dal cursore del mouse; le stelle rallenteranno gradualmente, diventeranno più grandi dopo la nascita e si restringeranno quando moriranno.
Infine, dimostrerò la flessibilità di Stardust creando diverse varianti dall'esempio già completo, incluso l'utilizzo di clip animati come particelle, la scala temporale della simulazione di particelle variabili e l'estrazione di oggetti di visualizzazione di classi diverse da un singolo emettitore.
Questo tutorial è rivolto a persone che hanno già familiarità con la programmazione orientata agli oggetti ActionScript 3.0 (OOP), quindi presumo che tu sappia già molto bene cosa significano classi, oggetti, ereditarietà e interfaccia. Nessun problema con OOP? Allora andiamo a sparare ad alcune stelle!
Come suggerisce il nome, Stardust è usato per creare effetti particellari. Se sei un ActionScripter esperto, potresti aver creato effetti particellari da zero molte volte, e dire "Sono assolutamente fantastico con la creazione di effetti particellari da zero, quindi perché dovrei comunque avere un motore particellare?" Bene, Stardust è qui per aiutarti a concentrarti maggiormente sulla progettazione del comportamento delle particelle reali piuttosto che preoccuparti del noioso contenuto di basso livello sottostante, come la gestione della memoria. Invece di scrivere codice per occuparsi dei dati delle particelle, inizializzare e smaltire le risorse, con Stardust, puoi saltare queste routine noiose e decidere come vuoi che le tue particelle si comportino.
La struttura di classe di Stardust è stata ispirata da FLiNT Particle System, un altro motore particellare ActionScript 3.0. Pertanto, condividono alcune caratteristiche di base simili.
Oltre a queste funzionalità di base, Stardust offre anche diverse funzionalità avanzate per utenti esperti.
Quando si tratta di effetti particellari, è molto importante gestire in modo efficiente enormi quantità di dati particellari. Stardust fa un uso pesante di pool di oggetti e elenchi collegati per migliorare le prestazioni:
Prima di passare alla vera codifica, dovremo prendere una copia di Stardust Particle Engine. È rilasciato sotto licenza MIT, il che significa che è totalmente gratuito, non importa se si desidera utilizzarlo in un progetto commerciale o non commerciale.
Ecco la homepage del progetto di Stardust: http://code.google.com/p/stardust-particle-engine/
Puoi scaricare Stardust qui: http://code.google.com/p/stardust-particle-engine/downloads/list
Al momento della scrittura, l'ultima versione che può essere scaricata dalla lista di download è la 1.1.132 Beta. Puoi sempre prendere l'ultima revisione dal repository SVN (che potrebbe non essere stabile, comunque).
Sulla home page del progetto, puoi trovare anche altri accessori come la documentazione dell'API e una copia del manuale in PDF. Ci sono anche video tutorial su YouTube.
Qui parlerò brevemente delle classi chiave di Stardust e delle loro responsabilità.
Questa classe è la superclasse di tutte le core classes, che definisce proprietà e metodi specialmente per la serializzazione XML.
In generale, gli effetti particellari riguardano il controllo di una quantità di entità con aspetto e comportamenti simili ma casuali. La classe Random serve a generare numeri casuali, che possono essere usati in Stardust per randomizzare le proprietà delle particelle. Per esempio, la classe UniformRandom è una sottoclasse della classe Random, e il suo nome dice tutto: il numero casuale generato da un oggetto UniformRandom è distribuito uniformemente, e userò questa classe in particolare per l'intero tutorial.
Ci sono momenti in cui un numero casuale unidimensionale non è sufficiente. A volte abbiamo bisogno di numeri casuali bidimensionali, che sono essenzialmente coppie di numeri casuali, per proprietà come la posizione e la velocità. La classe Zone serve a generare coppie di numeri casuali bidimensionali. Questa classe modella una coppia numerica casuale come un punto casuale in una zona 2D. Ad esempio, CircleZone genera coppie di numeri casuali (x, y) da punti casuali all'interno di una regione circolare. Le classi Random e Zone vengono principalmente utilizzate dalla classe Initializer, che verrà trattata in seguito. La classe Zone3D è la controparte 3D di questa classe, per effetti particellari 3D.
La classe Emitter è fondamentalmente in cui tutte le cose di basso livello sono incapsulate. Un emettitore inizializza le particelle appena create prima di essere aggiunte alla simulazione, aggiorna le proprietà delle particelle in ciascuna iterazione del ciclo principale e rimuove le particelle morte dalla simulazione. Il metodo Emitter.step () è ciò che si desidera richiamare ripetutamente per mantenere attivo e funzionante Stardust.
La classe Clock determina il tasso di creazione di nuove particelle per gli emettitori. Un oggetto Emettitore contiene esattamente un riferimento a un oggetto Orologio. All'inizio di ogni chiamata al metodo Emitter.step (), l'emettitore chiede all'oggetto orologio quante nuove particelle dovrebbe creare. Prendiamo ad esempio la classe SteadyClock, che dice agli emettitori di creare nuove particelle a una velocità costante.
Questa classe serve per inizializzare le particelle appena create. Un oggetto Initializer deve essere aggiunto a un emettitore affinché funzioni. Fondamentalmente, una sottoclasse Initializer inizializza solo una proprietà particella. Ad esempio, la classe di inizializzazione di massa inizializza la massa di nuove particelle. Alcuni inizializzatori accettano un oggetto casuale come parametro di costruzione per inizializzare particelle con valori randomizzati. Il codice seguente crea un inizializzatore Life che inizializza la vita delle particelle a valori centrati a 50 con variazione di 10, cioè nell'intervallo da 40 a 60.
nuova vita (nuovo UniformRandom (50, 10));
Gli oggetti azione aggiornano le proprietà delle particelle in ogni iterazione del ciclo principale (il metodo Emiter.step ()). Ad esempio, la classe di azione Sposta aggiorna le posizioni delle particelle in base alla velocità. Un oggetto Action deve essere aggiunto a un emettitore affinché funzioni.
Ora che sai come collaborano le classi principali, diamo un'occhiata a un flusso di lavoro generale per Stardust.
Si inizia creando un emettitore. Usa la classe Emitter2D per effetti particellari 2D e la classe Emitter3D per effetti 3D.
var emitter: Emitter = new Emitter2D ();
Per specificare il tasso di creazione di particelle, abbiamo bisogno di un orologio. Questo può essere impostato dalla proprietà Emitter.clock o passando un orologio come primo parametro al costruttore dell'emettitore.
// property approach emitter.clock = new SteadyClock (1); // constructor approach var emitter: Emitter = new Emitter2D (new SteadyClock (1));
Aggiungi gli inizializzatori all'emettitore tramite il metodo Emitter.addInitializer ().
emitter.addInitializer (new Life (new UniformRandom (50, 10))); emitter.addInitializer (new Scale (new UniformRandom (1, 0.2)));
Aggiungere azioni all'emettitore tramite il metodo Emitter.addAction ().
emitter.addAction (new Move ()); emitter.addAction (new Spin ());
Crea un renderer e aggiungi l'emettitore al renderer tramite il metodo Renderer.addEmitter ().
var renderer: Renderer = new DisplayObjectRenderer (container); // "container" è il nostro contenitore sprite renderer.addEmitter (emitter);
Infine, chiama ripetutamente il metodo Emitter.step () per mantenere attiva la simulazione delle particelle. Potresti voler usare l'evento enter-frame o un timer per farlo. In una singola chiamata del metodo Emitter.step (), l'orologio determina quante nuove particelle dovrebbero essere create, queste nuove particelle vengono inizializzate dagli inizializzatori, tutte le particelle vengono aggiornate dalle azioni, le particelle morte vengono rimosse e infine, il renderer esegue il rendering l'effetto particellare.
// approccio evento enter-frame addEventListener (Event.ENTER_FRAME, mainLoop); // timer approach timer.addEventListener (TimerEvent.TIMER, mainLoop); function mainLoop (e: Event): void emitter.step ();
Tutto apposto. Questo è praticamente tutto per Stardust Primer. Ora è il momento di aprire l'IDE Flash e sporcarsi le mani.
Crea un nuovo documento Flash con una dimensione di 640X400 con una frequenza fotogrammi di 60 fps e uno sfondo scuro. Qui ho creato uno sfondo sfumato blu scuro. A proposito, Stardust funziona bene con Flash Player 9 e 10, quindi va bene, non importa che tu stia usando Flash CS3 o CS4. In questo tutorial userò Flash CS3.
Stiamo creando un effetto particellare con le stelle, quindi dovremo disegnare una stella e convertirla in un simbolo, ovviamente esportato per ActionScript. Questo simbolo sarà usato in seguito per rendere il nostro effetto particellare. Assegna un nome al simbolo e alla classe esportata "Star".
Creare una nuova classe di documento e denominarla StarParticles.
pacchetto import flash.display.Sprite; classe pubblica StarParticles estende Sprite funzione pubblica StarParticles ()
Come menzionato nel flusso di lavoro generale, il primo passo è creare un emettitore. E il passo successivo è l'aggiunta di inizializzatori e azioni all'emettitore. Mentre questo può essere fatto nel costruttore della classe del documento, raccomando vivamente di farlo in una sottoclasse Emitter separata. È sempre meglio separare la progettazione del comportamento delle particelle dal programma principale; così facendo, il codice è molto più pulito e più facile da modificare in futuro, senza essere confuso con il programma principale.
Creeremo un effetto particellare 2D, quindi Emitter2D è la classe di emissione che stiamo per estendere. Estendi la classe Emitter2D e chiamala StarEmitter, dato che lo faremo sparare alle stelle in seguito. Il costruttore Emitter accetta un parametro Clock, quindi dichiareremo un parametro costruttore per passare un riferimento a oggetto Clock al costruttore della superclasse.
package import idv.cjcat.stardust.twoD.emitters.Emitter2D; classe pubblica StarEmitter estende Emitter2D funzione pubblica StarEmitter (clock: Clock) // passa l'oggetto orologio al super costruttore della superclasse (orologio);
Un approccio migliore per creare una sottoclasse di emettitore consiste nel dichiarare i parametri delle particelle come costanti statiche, raggruppate in un singolo punto. Quindi, nel caso in cui desideri modificare i parametri, saprai sempre dove trovare le dichiarazioni. Il significato di queste costanti verrà spiegato più avanti quando vengono utilizzate.
// durata media della vita privata const const LIFE_AVG: Number = 30; // variazione della durata della vita private static const LIFE_VAR: Number = 10; // scale statiche private cost const SCALE_AVG: Number = 1; // scale variation private static const SCALE_VAR: Number = 0.4; // scala crescente tempo statico privato const GROWING_TIME: Number = 5; // ridimensionamento della scala private static const SHRINKING_TIME: Number = 10; // velocità media statica privata const SPEED_AVG: Number = 10; // variazione della velocità private static const SPEED_VAR: Number = 8; // media omega (velocità angolare) const statico privato OMEGA_AVG: Number = 0; // variazione omega private static const OMEGA_VAR: Number = 5; // coefficiente di smorzamento const statico privato DAMPING: Number = 0.1;
Di quali inizializzatori abbiamo bisogno per creare il nostro effetto particellare? Diamo un'occhiata alla lista qui sotto:
Ed ecco il codice:
point = new SinglePoint (); addInitializer (new DisplayObjectClass (Star)); addInitializer (new Life (new UniformRandom (LIFE_AVG, LIFE_VAR))); addInitializer (new Scale (new UniformRandom (SCALE_AVG, SCALE_VAR))); addInitializer (new Position (point)); addInitializer (nuovo Velocity (nuovo LazySectorZone (SPEED_AVG, SPEED_VAR))); addInitializer (new Rotation (new UniformRandom (0, 180))); addInitializer (nuovo Omega (nuovo UniformRandom (OMEGA_AVG, OMEGA_VAR)));
Ok, abbiamo finito con gli inizializzatori. Ora è il momento di aggiungere azioni all'emettitore. Di seguito è riportato un elenco di azioni di cui abbiamo bisogno:
Questo è tutto. Il nostro emettitore è fatto. Ecco il codice per questo emettitore nella sua interezza, incluse le istruzioni di importazione necessarie.
package import idv.cjcat.stardust.common.actions.Age; import idv.cjcat.stardust.common.actions.DeathLife; import idv.cjcat.stardust.common.actions.ScaleCurve; import idv.cjcat.stardust.common.clocks.Clock; import idv.cjcat.stardust.common.initializers.Life; import idv.cjcat.stardust.common.initializers.Scale; import idv.cjcat.stardust.common.math.UniformRandom; import idv.cjcat.stardust.twoD.actions.Damping; import idv.cjcat.stardust.twoD.actions.Move; import idv.cjcat.stardust.twoD.actions.Spin; import idv.cjcat.stardust.twoD.emitters.Emitter2D; import idv.cjcat.stardust.twoD.initializers.DisplayObjectClass; import idv.cjcat.stardust.twoD.initializers.Omega; import idv.cjcat.stardust.twoD.initializers.Position; import idv.cjcat.stardust.twoD.initializers.Rotation; import idv.cjcat.stardust.twoD.initializers.Velocity; import idv.cjcat.stardust.twoD.zones.LazySectorZone; import idv.cjcat.stardust.twoD.zones.SinglePoint; la classe pubblica StarEmitter estende Emitter2D / ** * Costanti * / private static const LIFE_AVG: Number = 30; private static const LIFE_VAR: Number = 10; const statico privato SCALE_AVG: Number = 1; const statico privato SCALE_VAR: Number = 0.4; const statico privato GROWING_TIME: Number = 5; private static const SHRINKING_TIME: Number = 10; const statico privato SPEED_AVG: Number = 10; const statico privato SPEED_VAR: Number = 8; const statico privato OMEGA_AVG: Number = 0; const statico privato OMEGA_VAR: Number = 5; private static const DAMPING: Number = 0.1; punto var pubblico: SinglePoint; funzione pubblica StarEmitter (clock: Clock) super (orologio); point = new SinglePoint (); // inizializzatori addInitializer (new DisplayObjectClass (Star)); addInitializer (new Life (new UniformRandom (LIFE_AVG, LIFE_VAR))); addInitializer (new Scale (new UniformRandom (SCALE_AVG, SCALE_VAR))); addInitializer (new Position (point)); addInitializer (nuovo Velocity (nuovo LazySectorZone (SPEED_AVG, SPEED_VAR))); addInitializer (new Rotation (new UniformRandom (0, 180))); addInitializer (nuovo Omega (nuovo UniformRandom (OMEGA_AVG, OMEGA_VAR))); // azioni addAction (new Age ()); addAction (new DeathLife ()); addAction (new Move ()); addAction (new Spin ()); addAction (new Damping (DAMPING)); addAction (new ScaleCurve (GROWING_TIME, SHRINKING_TIME));
Ora è il momento di tornare alla classe del documento e finirla. Diamo un'occhiata alle attività rimanenti.
Di seguito è riportato il codice completo per la classe del documento, incluse le istruzioni di importazione necessarie.
pacchetto import flash.display.Sprite; import flash.display.StageScaleMode; import flash.events.Event; import flash.geom.Rectangle; import idv.cjcat.stardust.common.clocks.SteadyClock; import idv.cjcat.stardust.common.renderers.Renderer; import idv.cjcat.stardust.twoD.renderers.DisplayObjectRenderer; classe pubblica StarParticles estende Sprite emettitore private var: StarEmitter; funzione pubblica StarParticles () // istanzia l'emettitore StarEmitter = new StarEmitter (new SteadyClock (0.5)); // contenitore container sprite var: Sprite = new Sprite (); // il renderer che rende l'effetto particle var renderer: Renderer = new DisplayObjectRenderer (container); renderer.addEmitter (emettitore); // aggiungi il contenitore alla lista di visualizzazione, sopra lo sfondo addChildAt (container, 1); // usa l'evento enter-frame addEventListener (Event.ENTER_FRAME, mainLoop); private function mainLoop (e: Event): void // aggiorna la posizione SinglePoint sulla posizione del mouse emitter.point.x = mouseX; emitter.point.y = mouseY; // chiama il ciclo principale emitter.step ();
Finalmente, abbiamo finito! Ora diamo un'occhiata al risultato. Premi CTRL + INVIO in Flash per testare il film e vedrai il risultato.
Non abbiamo ancora finito! Facciamo qualche altra variazione. Il primo utilizza filmati animati per le nostre particelle.
Questa prima variazione è abbastanza semplice, non implica alcuna codifica aggiuntiva. È semplice come creare un'animazione temporale di base. Modifica il simbolo Stella in Flash IDE, crea un altro fotogramma chiave e cambia il colore della stella in questo riquadro in rosso. Questo essenzialmente fa sì che le stelle lampeggino tra il giallo e il rosso. Si consiglia di inserire alcuni fotogrammi più vuoti in mezzo, poiché una frequenza fotogrammi di 60fps è troppo veloce per un lampeggiamento a due fotogrammi.
Ora prova il film e controlla il risultato. L'effetto stella lampeggiante sembra fumoso; questo può essere usato per i classici effetti stordenti, che si vedono comunemente nei cartoni animati.
Come accennato in precedenza, una delle caratteristiche di Stardust è "tempo di simulazione regolabile", il che significa che la scala cronologica utilizzata da Stardust per la simulazione delle particelle può essere regolata dinamicamente. Tutto avviene cambiando la proprietà Emitter.stepTimeInterval, che è 1 per impostazione predefinita. Il seguente frammento di codice cambia questo valore in 2, con il risultato che particelle si muovono due volte più velocemente e emettitori che creano nuove particelle a doppia frequenza.
emitter.stepTimeInterval = 2;
In questa variante creeremo un cursore sullo stage e lo useremo per regolare dinamicamente la scala temporale della simulazione.
Trascina un componente Slider dal pannello Componenti allo stage. Chiamalo "slider".
Vorremmo far scorrere il cursore tra 0,5 e 2, il che significa che vogliamo che la nostra simulazione delle particelle sia almeno la metà della velocità normale e al massimo due volte veloce. Inoltre, imposta "liveDragging" su true in modo che possiamo vedere l'aggiornamento mentre sfogliamo