In deze tutorial zal ik de Stardust deeltjesmotor introduceren. Eerst zal ik je laten zien hoe je Stardust opzet, en dan zal ik de basisverantwoordelijkheden van Stardust class bespreken en hoe ze samenwerken om Stardust als een geheel te laten werken.
Vervolgens zullen we kijken naar de algemene workflow van een Stardust en gaan werken aan het creëren van een partikeleffect met sterren die uit de muiscursor schieten; de sterren vertragen geleidelijk, worden groter na de geboorte en krimpen bij het sterven.
Ten slotte zal ik de flexibiliteit van Stardust demonstreren door verschillende variaties van het al complete voorbeeld te maken, waaronder het gebruik van geanimeerde filmclips als deeltjes, tijdsschaal met variabele deeltjessimulatie en het afschieten van weergaveobjecten van verschillende klassen van een enkele zender.
Deze zelfstudie is bedoeld voor mensen die al bekend zijn met ActionScript 3.0-objectgeoriënteerd programmeren (OOP), dus ik ga ervan uit dat je al heel goed weet wat klassen, objecten, overerving en interface betekenen. Geen probleem met OOP? Laten we dan een paar sterren gaan schieten!
Zoals de naam doet vermoeden, wordt Stardust gebruikt voor het creëren van deeltjeseffecten. Als je een ervaren ActionScripter bent, kun je vaak partikelingeffecten van de grond af hebben gemaakt en zeggen "Ik ben helemaal cool met het helemaal opnieuw creëren van deeltjeseffecten, dus waarom zou ik toch een deeltjesmotor nodig hebben?" Welnu, Stardust is er om u te helpen meer te focussen op daadwerkelijk ontwerp van deeltjesgedrag dan u zorgen te maken over de vervelende onderliggende dingen op een laag niveau, zoals geheugenbeheer. In plaats van code te schrijven voor het verwerken van deeltjesgegevens, het initialiseren en verwijderen van bronnen, met Stardust, sla je deze saaie routines over en beslis gewoon hoe je wilt dat je deeltjes zich gedragen.
De klassenstructuur van Stardust werd geïnspireerd door FLiNT Particle System, een andere ActionScript 3.0-deeltjesmotor. Daarom delen ze enkele vergelijkbare basisfuncties.
Naast deze basisfuncties biedt Stardust ook verschillende geavanceerde functies voor ervaren gebruikers.
Als het gaat om deeltjeseffecten, is het erg belangrijk om massieve deeltjesgegevens efficiënt te verwerken. Stardust maakt zwaar gebruik van objectgroepen en gekoppelde lijsten om de prestaties te verbeteren:
Voordat we echt kunnen coderen, moeten we een kopie van Stardust Particle Engine nemen. Het is vrijgegeven onder MIT-licentie, wat betekent dat het volledig gratis is, ongeacht of u het in een commercieel of niet-commercieel project wilt gebruiken.
Dit is de startpagina van de Stardust: http://code.google.com/p/stardust-particle-engine/
U kunt Stardust hier downloaden: http://code.google.com/p/stardust-particle-engine/downloads/list
Op het moment van schrijven is de nieuwste versie die kan worden gedownload van de downloadlijst 1.1.132 Beta. Je kunt altijd de laatste revisie pakken uit de SVN-repository (die misschien niet stabiel is).
Op de startpagina van het project vindt u ook meer accessoires zoals API-documentatie en een PDF-handleiding. Er zijn zelfs videotutorials op YouTube.
Hier ga ik kort ingaan op de kernklassen van Stardust en hun verantwoordelijkheden.
Deze klasse is de superklasse van alle hoofdklassen, die eigenschappen en methoden definieert, met name voor XML-serialisatie.
Over het algemeen gaat het bij deeltjeseffecten om het beheersen van een hoeveelheid entiteiten met een vergelijkbare maar gerandomiseerde verschijning en gedrag. De Random-klasse is voor het genereren van willekeurige getallen, die in Stardust kunnen worden gebruikt voor het randomiseren van deeltjeseigenschappen. De UniformRandom-klasse is bijvoorbeeld een subklasse van de klasse Random en de naam zegt alles: het willekeurige nummer dat door een UniformRandom-object wordt gegenereerd, wordt uniform verspreid en ik zal deze klasse in het bijzonder gebruiken voor de hele tutorial.
Er zijn tijden dat een eendimensionaal willekeurig getal niet genoeg is. Soms hebben we tweedimensionale willekeurige getallen nodig, die in wezen paren van willekeurige getallen zijn, voor eigenschappen zoals positie en snelheid. De zoneklasse is voor het genereren van tweedimensionale willekeurige nummerparen. Deze klasse modelleert een willekeurig getalpaar als een willekeurig punt in een 2D-zone. De CircleZone genereert bijvoorbeeld willekeurige nummerparen (x, y) van willekeurige punten in een cirkelvormig gebied. De klassen Random en Zone worden hoofdzakelijk gebruikt door de klasse Initializer, die later worden behandeld. De Zone3D-klasse is de 3D-tegenhanger van deze klasse, voor 3D-deeltjeseffecten.
De klasse Emitter is eigenlijk waar alle low-level dingen ingekapseld zijn. Een emitter initialiseert nieuw gecreëerde deeltjes voordat ze worden toegevoegd aan de simulatie, werkt deeltjeseigenschappen bij in elke hoofdlus-iteratie en verwijdert dode deeltjes uit de simulatie. De methode Emitter.step () is wat u herhaaldelijk wilt aanroepen om Stardust in de lucht te houden.
De klokklasse bepaalt de snelheid waarmee nieuwe deeltjes worden aangemaakt voor emitters. Eén emitter-object bevat precies één verwijzing naar een Clock-object. Aan het begin van elke methode-aanroep van de methode Emitter.step () vraagt de emitter aan het object van de klok hoeveel nieuwe deeltjes het moet maken. Neem bijvoorbeeld de SteadyClock-klasse, het vertelt emitters om nieuwe deeltjes met een constante snelheid te creëren.
Deze klasse is voor het initialiseren van nieuw gemaakte deeltjes. Een Initializer-object moet aan een emitter worden toegevoegd om te kunnen werken. Kort gezegd initialiseert een Initializer-subklasse slechts één deeltjeseigenschap. De initialisatieklasse van Mass initialiseert bijvoorbeeld de massa nieuwe deeltjes. Sommige initializers accepteren een willekeurig object als een constructorparameter voor het initialiseren van deeltjes met willekeurige waarden. De volgende code creëert een Life-initialisator die deeltijdlevens initialiseert naar waarden gecentreerd op 50 met een variatie van 10, namelijk tussen het bereik van 40 tot 60.
nieuw leven (nieuwe UniformRandom (50, 10));
Actieobjecten bijwerken deeltjeseigenschappen in elke iteratie van de hoofdlus (de methode Emiter.step ()). Met de actieklasse Move worden bijvoorbeeld de deeltjesposities volgens de snelheid bijgewerkt. Een Action-object moet aan een emitter worden toegevoegd om te kunnen werken.
Nu je weet hoe de kernklassen samenwerken, laten we eens kijken naar een algemene workflow voor Stardust.
U begint met het maken van een emitter. Gebruik de klasse Emitter2D voor 2D-deeltjeseffecten en de klasse Emitter3D voor 3D-effecten.
var emitter: Emitter = nieuwe Emitter2D ();
Om de snelheid van het aanmaken van deeltjes te specificeren, hebben we een klok nodig. Dit kan worden ingesteld door de eigenschap Emitter.clock of door een klok door te geven als de eerste parameter voor de constructor van de emitter.
// property approach emitter.clock = new SteadyClock (1); // constructor-aanpak var-emitter: Emitter = nieuwe Emitter2D (nieuwe SteadyClock (1));
Voeg initializers toe aan de emitter via de methode Emitter.addInitializer ().
emitter.addInitializer (nieuw leven (nieuwe UniformRandom (50, 10))); emitter.addInitializer (nieuwe schaal (nieuwe UniformRandom (1, 0.2)));
Acties aan de emitter toevoegen via de methode Emitter.addAction ().
emitter.addAction (nieuwe verplaatsing ()); emitter.addAction (nieuwe Spin ());
Maak een renderer en voeg de emitter toe aan de renderer via de methode Renderer.addEmitter ().
var renderer: Renderer = nieuw DisplayObjectRenderer (container); // "container" is onze containersprite renderer.addEmitter (emitter);
Tot slot roept u herhaaldelijk de methode Emitter.step () op om de deeltjessimulatie in stand te houden. Misschien wil je de enter-frame-gebeurtenis of een timer gebruiken om dit te doen. In een enkele aanroep van de methode Emitter.step () bepaalt de klok hoeveel nieuwe deeltjes moeten worden gemaakt, deze nieuwe deeltjes worden geïnitialiseerd door initializers, alle deeltjes worden bijgewerkt door acties, dode deeltjes worden verwijderd en ten slotte rendert de renderer het partikeleffect.
// enter-frame event-aanpak addEventListener (Event.ENTER_FRAME, mainLoop); // timer approach timer.addEventListener (TimerEvent.TIMER, mainLoop); function mainLoop (e: Event): void emitter.step ();
Alright. Dat is vrijwel alles voor de Stardust-primer. Nu is het tijd om de Flash IDE te openen en je handen vuil te maken.
Maak een nieuw Flash-document met een dimensie van 640x400 een framesnelheid van 60 fps en een donkere achtergrond. Hier heb ik een donkerblauwe gradient achtergrond gemaakt. Trouwens, Stardust werkt goed met zowel Flash Player 9 als 10, dus het is goed, ongeacht of je Flash CS3 of CS4 gebruikt. In deze tutorial gebruik ik Flash CS3.
We maken een partikeleffect met sterren, dus we moeten een ster tekenen en deze omzetten in een symbool, uiteraard geëxporteerd voor ActionScript. Dit symbool zal later worden gebruikt om ons partikeleffect te maken. Geef het symbool een naam en de geëxporteerde klasse "Star".
Maak een nieuwe documentklasse en noem deze StarParticles.
pakket import flash.display.Sprite; public class StarParticles breidt uit met Sprite openbare functie StarParticles ()
Zoals vermeld in de algemene workflow, is de eerste stap het maken van een emitter. En de volgende stap is het toevoegen van initializers en acties aan de zender. Hoewel dit kan worden gedaan in de constructor van de documentklasse, raad ik u ten zeerste aan om dit in een afzonderlijke subklasse Emitter te doen. Het is altijd beter om het ontwerp van het deeltjesgedrag van het hoofdprogramma te scheiden; hierdoor is de code in de toekomst veel schoner en gemakkelijker aan te passen, zonder in de war te raken met het hoofdprogramma.
We gaan een 2D-deeltjeseffect creëren, dus de Emitter2D is de emitterklasse die we gaan uitbreiden. Breid de klasse Emitter2D uit en noem hem StarEmitter, want we gaan het later op sterren laten schieten. De Emitter-constructor accepteert een Clock-parameter, dus we zullen een constructorfactor declareren om een Clock-objectreferentie door te geven aan de constructor van de superklasse.
pakket import idv.cjcat.stardust.twoD.emitters.Emitter2D; openbare klasse StarEmitter breidt Emitter2D uit openbare functie StarEmitter (klok: klok) // geeft het klokobject door aan de constructorsuper van de superklasse (klok);
Een betere benadering om een emittersubklasse te maken, is om deeltjeparameters te verklaren als statische constanten, gegroepeerd op één plaats. Dus als u de parameters wilt aanpassen, weet u altijd waar u de aangiften kunt vinden. De betekenis van deze constanten zal later worden uitgelegd wanneer ze worden gebruikt.
// gemiddelde levensduur persoonlijke statische const LIFE_AVG: Number = 30; // levensduur variantie privé statische const LIFE_VAR: Number = 10; // average scale private static const SCALE_AVG: Number = 1; // schaalvariatie private static const SCALE_VAR: Number = 0.4; // scale growing time private static const GROWING_TIME: Number = 5; // schaal inkrimpingstijd private static const SHRINKING_TIME: Number = 10; // gemiddelde snelheid private static const SPEED_AVG: Number = 10; // snelheidsvariatie private static const SPEED_VAR: Number = 8; // gemiddelde omega (hoeksnelheid) privé-statische const OMEGA_AVG: Number = 0; // omegavariatie private static const OMEGA_VAR: Number = 5; // dempingscoëfficiënt privé statische const DAMPING: aantal = 0,1;
Welke initializers hebben we nodig om ons partikeleffect te creëren? Laten we de onderstaande lijst eens bekijken:
En hier is de code:
punt = nieuw SinglePoint (); addInitializer (nieuwe DisplayObjectClass (Star)); addInitializer (nieuwe Life (nieuwe UniformRandom (LIFE_AVG, LIFE_VAR))); addInitializer (nieuwe schaal (nieuwe UniformRandom (SCALE_AVG, SCALE_VAR))); addInitializer (nieuwe positie (punt)); addInitializer (nieuwe Velocity (nieuwe LazySectorZone (SPEED_AVG, SPEED_VAR))); addInitializer (nieuwe rotatie (nieuwe UniformRandom (0, 180))); addInitializer (nieuwe Omega (nieuwe UniformRandom (OMEGA_AVG, OMEGA_VAR)));
Oké, we zijn klaar met de initializers. Nu is het tijd om acties aan de zender toe te voegen. Hieronder volgt een lijst met acties die we nodig hebben:
Dat is het. Onze zender is klaar. Hier is de code voor deze zender in zijn geheel, noodzakelijke importstatements inbegrepen.
pakket 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; public class StarEmitter breidt Emitter2D uit / ** * Constants * / private static const LIFE_AVG: Number = 30; private static const LIFE_VAR: Number = 10; private static const SCALE_AVG: Number = 1; private static const SCALE_VAR: Number = 0.4; privé statische const GROWING_TIME: Number = 5; private static const SHRINKING_TIME: Number = 10; private static const SPEED_AVG: Number = 10; private static const SPEED_VAR: Number = 8; private static const OMEGA_AVG: Number = 0; private static const OMEGA_VAR: Number = 5; privé statische const DAMPING: Number = 0.1; public var point: SinglePoint; openbare functie StarEmitter (klok: klok) super (klok); punt = nieuw SinglePoint (); // initializers addInitializer (nieuwe DisplayObjectClass (Star)); addInitializer (nieuwe Life (nieuwe UniformRandom (LIFE_AVG, LIFE_VAR))); addInitializer (nieuwe schaal (nieuwe UniformRandom (SCALE_AVG, SCALE_VAR))); addInitializer (nieuwe positie (punt)); addInitializer (nieuwe Velocity (nieuwe LazySectorZone (SPEED_AVG, SPEED_VAR))); addInitializer (nieuwe rotatie (nieuwe UniformRandom (0, 180))); addInitializer (nieuwe Omega (nieuwe UniformRandom (OMEGA_AVG, OMEGA_VAR))); // actions addAction (new Age ()); addAction (nieuwe DeathLife ()); addAction (nieuwe verplaatsing ()); addAction (nieuwe Spin ()); addAction (nieuwe Demping (DAMPING)); addAction (nieuwe ScaleCurve (GROWING_TIME, SHRINKING_TIME));
Nu is het tijd om terug te gaan naar de documentklasse en deze af te maken. Laten we de resterende taken bekijken.
Hieronder vindt u de volledige code voor de documentklasse, inclusief noodzakelijke importstatements.
pakket 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; public class StarParticles breidt Sprite uit private var emitter: StarEmitter; public function StarParticles () // instantiate the StarEmitter emitter = new StarEmitter (nieuwe SteadyClock (0.5)); // container container sprite var: Sprite = nieuwe Sprite (); // de renderer die het partikeleffect renderer render: Renderer = nieuw DisplayObjectRenderer (container); renderer.addEmitter (emitter); // voeg de container toe aan de weergavelijst, boven de achtergrond addChildAt (container, 1); // maak gebruik van de enter-frame event addEventListener (Event.ENTER_FRAME, mainLoop); private function mainLoop (e: Event): void // update de SinglePoint-positie naar de muispositie emitter.point.x = mouseX; emitter.point.y = mouseY; // noem de hoofdlus emitter.step ();
Eindelijk zijn we klaar! Laten we nu eens kijken naar de uitkomst. Druk op CTRL + ENTER in Flash om de film te testen en je ziet het resultaat.
We zijn nog niet klaar! Laten we nog een paar variaties doen. De eerste maakt gebruik van geanimeerde filmclips voor onze deeltjes.
Deze eerste variatie is vrij eenvoudig, zonder extra codering. Het is net zo eenvoudig als het maken van een eenvoudige tijdlijnanimatie. Bewerk het stersymbool in Flash IDE, maak nog een keyframe en verander de kleur van de ster in dit frame in rood. Dit zorgt er in feite voor dat de sterren tussen geel en rood knipperen. Misschien wil je wat meer lege frames invoegen, omdat een framesnelheid van 60 fps te snel is voor een knipperend tweerichtingsframe.
Test nu de film en controleer het resultaat. Het knipperende stereffect ziet er cartoonachtig uit; dit kan worden gebruikt voor klassieke duizelige stereffecten, wat vaak wordt gezien in tekenfilms.
Zoals ik eerder al zei, is een van de Stardust-functies "instelbare simulatieschema's", wat betekent dat de tijdschaal die Stardust gebruikt voor deeltjessimulatie dynamisch kan worden aangepast. Alles wordt gedaan door de eigenschap Emitter.stepTimeInterval te wijzigen, die standaard 1 is. Het volgende codefragment verandert deze waarde naar 2, waardoor deeltjes twee keer zo snel bewegen en emitter nieuwe deeltjes met dubbele snelheid maken.
emitter.stepTimeInterval = 2;
In deze variant maken we een schuifregelaar op het podium en gebruiken we deze om de tijdschaal van de simulatie dynamisch aan te passen.
Sleep een schuifregelaarcomponent uit het componentenpaneel naar het werkgebied. Noem het "slider".
We willen graag dat de schuif tussen 0,5 en 2 schuift, wat betekent dat we willen dat onze deeltjessimulatie minstens de helft sneller is dan normaal en hooguit twee keer zo snel. Stel ook 'liveDragging' in op true, zodat we de update kunnen zien terwijl we de duim van de schuifregelaar scrubben.
Nu moeten we luisteren naar de veranderingsgebeurtenis van de schuifregelaar om de tijdschaal van de simulatie dynamisch te wijzigen. Importeer eerst de SliderEvent-klasse in de documentklasse.
import fl.events.SliderEvent;
En luister dan naar de gebeurtenis van de schuifregelaar in de constructor van de documentklasse.
slider.addEventListener (SliderEvent.CHANGE, changeTimescale);
Wijzig ten slotte de simulatie-tijdschaal in de luisteraar. Voeg de volgende listener toe aan de documentklasse.