Manipulation du mouvement des particules avec le moteur à particules Stardust - Partie 1

Stardust Particle Engine propose deux approches majeures pour manipuler librement le mouvement des particules, à savoir les champs gravitationnels et les déflecteurs. Les champs gravitationnels sont des champs de vecteurs qui affectent l'accélération d'une particule. Les déflecteurs manipulent à la fois la position et la vitesse d'une particule..

La première partie de ce didacticiel couvre les bases du mouvement des particules et des champs gravitationnels. En outre, il montre comment créer vos propres champs gravitationnels personnalisés. La deuxième partie est consacrée aux déflecteurs et à la création de déflecteurs personnalisés..

Une connaissance préalable de l'utilisation de base de Stardust est nécessaire pour continuer à lire ce didacticiel. Si vous n'êtes pas familier avec Stardust, vous pouvez consulter mon tutoriel précédent sur le sujet, Tirer sur les étoiles avec le moteur de particules Stardust, avant de poursuivre..


Aperçu du résultat final

Jetons un coup d'œil au résultat final sur lequel nous allons travailler. Ceci est un exemple de champ gravitationnel de vortex personnalisé.


Notions de base sur le mouvement des particules

Il est temps de faire un retour en arrière sur la physique au lycée. Rappelez-vous la cinématique de base? Tout est une question de déplacement, qui est simplement une façon plus sophistiquée de dire «position» et sa relation avec le temps. Pour la portée de ce tutoriel, nous n’avons besoin que d’une compréhension assez simple du sujet..

Déplacement

Le déplacement signifie la position actuelle d'un objet. Dans ce tutoriel, les "objets" dont nous traitons principalement sont des particules dans un espace 2D. Dans l'espace 2D, le déplacement d'un objet est représenté par un vecteur 2D.

Rapidité

La vitesse d'un objet indique la rapidité avec laquelle sa position change et la direction du changement. La vitesse d'un objet dans l'espace 2D est également représentée par un vecteur 2D. Les composantes x et y du vecteur représentent la direction du changement de position, et la valeur absolue du vecteur indique la vitesse de l'objet, c'est-à-dire à quelle vitesse le déplacement de l'objet.

Accélération

L'accélération est à la vitesse comme la vitesse est au déplacement. L'accélération d'un objet indique à quelle vitesse la vitesse d'un objet change et la direction du changement. Tout comme la vitesse d'un objet dans un espace 2D, l'accélération d'un objet est représentée par un vecteur 2D.


Champs de vecteurs

Une autre chose à noter est le concept de champs de vecteurs. En gros, vous pouvez afficher un champ de vecteur sous la forme d'une fonction prenant un vecteur en entrée et en sortant un autre. Les champs gravitationnels sont une sorte de champs de vecteurs qui prennent des vecteurs de position en entrée et des vecteurs d’accélération en sortie. Par exemple, dans la simulation physique, une gravité uniforme dirigée vers le bas est appliquée à tous les objets. dans ce cas, le champ gravitationnel représentant la gravité est un champ de vecteurs qui génère un vecteur constant (pointant vers le bas), quels que soient les vecteurs de position en entrée..

C'est un graphique visualisé d'un champ de vecteurs. Le vecteur de sortie d'un vecteur d'entrée donné (1, 2) est (0.5, 0.5), tandis que la sortie d'une entrée (4, 3) est (-0.5, 0.5)..

le Champ La classe Stardust représente un champ vectoriel 2D et son équivalent en 3D, le Field3D La classe représente un champ vectoriel 3D. Dans ce tutoriel, nous nous concentrerons uniquement sur les champs de vecteurs 2D..

le Field.getMotionData () méthode prend un Particle2D objet, contenant les informations de position 2D et de vitesse d'une particule, en tant que paramètre. Cette méthode retourne un MotionData2D objet, un "objet de valeur" vectoriel 2D constitué de composantes x et y. Combiné avec le La gravité action, un Champ l'objet peut être utilisé comme champ gravitationnel, dont la sortie sert à manipuler la vitesse de la particule.

C'est tout pour notre récapitulation physique de lycée. Il est temps de passer à l'acte Stardust.


L'action de la gravité

Comme mentionné précédemment, le La gravité classe d'action utilise Champ objets en tant que champs gravitationnels pour manipuler la vitesse des particules. Voici comment créer un champ de vecteur uniforme, un champ de vecteur qui renvoie un vecteur constant, quelle que soit l'entrée, et l'envelopper dans un La gravité action.

 // crée un champ de vecteur uniforme pointant vers le bas (rappelez-vous que la coordonnée y positive signifie "bas" dans Flash) var field: Field = new UniformField (0, 1); // crée une action de gravité var gravity: Gravity = new Gravity (); // ajoute le champ à l'action de gravité gravity.addField (field);

Ce La gravité l'action est maintenant prête à être utilisée de la même manière que toute autre action Stardust ordinaire.

 // ajoute l'action de gravité à un émetteur emitter.addAction (gravity);

Exemple: pluie venteuse

Nous allons créer un effet de pluie venteuse en utilisant le La gravité action.


Étape 1: Effet de base sur la pluie

Créez un document Flash, choisissez une couleur sombre pour l'arrière-plan, tracez une goutte de pluie sur la scène et convertissez-la en symbole de clip, exporté pour ActionScript sous le nom de classe "Raindrop". Supprimer l'instance goutte de pluie sur la scène après.

Nous allons maintenant créer un fichier AS pour la classe de document et un fichier AS pour l'émetteur de pluie. Je n’expliquerai pas le code ici, car j’ai déjà décrit l’utilisation de base de Stardust dans mon précédent tutoriel. Si vous n'êtes pas familier avec Stardust ou si vous avez besoin d'un rafraîchissement, je vous recommande fortement de lire mon tutoriel précédent avant de poursuivre..

C'est la classe de document.

 package import flash.display. *; importer flash.events. *; import idv.cjcat.stardust.common.emitters. *; importer idv.cjcat.stardust.common.renderers. *; importer idv.cjcat.stardust.twoD.renderers. *; Classe publique WindyRain étend Sprite private var emitter: Emitter; convertisseur de rendu privé: Renderer; fonction publique WindyRain () emitter = new RainEmitter (); renderer = new DisplayObjectRenderer (this); renderer.addEmitter (émetteur); addEventListener (Event.ENTER_FRAME, mainLoop);  fonction privée mainLoop (e: Event): void emitter.step (); 

Et ceci est la classe d'émetteur utilisée dans la classe de document.

 package import idv.cjcat.stardust.common.clocks. *; importer idv.cjcat.stardust.common.initializers. *; import idv.cjcat.stardust.common.math. *; import idv.cjcat.stardust.twoD.actions. *; importer idv.cjcat.stardust.twoD.emitters. *; importer idv.cjcat.stardust.twoD.initializers. *; import idv.cjcat.stardust.twoD.zones. *; Classe publique RainEmitter étend Emitter2D fonction publique RainEmitter () super (nouveau SteadyClock (1)); // initializer addInitializer (new DisplayObjectClass (Raindrop)); addInitializer (nouvelle position (nouvelle RectZone (-300, -40, 940, 20))); addInitializer (nouvelle vélocité (nouvelle RectZone (-0,5, 2, 1, 3))); addInitializer (nouvelle masse (nouvelle UniformRandom (2, 1))); addInitializer (nouvelle échelle (nouvelle UniformRandom (1, 0,2))); // actions addAction (new Move ()); addAction (new Oriented (1, 180)); addAction (nouvelle DeathZone (nouvelle RectZone (-300, -40, 960, 480), true)); 

Voici à quoi ressemblent nos progrès actuels.


Étape 2: Faites du vent

Nous allons maintenant créer un effet de pluie en ajoutant un champ gravitationnel uniforme qui «tire» les gouttes de pluie vers la droite. Ajoutez le code suivant dans le constructeur du Emetteur de pluie classe.

 // crée un champ uniforme qui renvoie toujours (0.5, 0) champ var: Champ = new UniformField (0.5, 0); // prend en compte la masse des particules field.massless = false; // crée une action de gravité et y ajoute le champ var gravity: Gravity = new Gravity (); gravité.addField (champ); // ajoute l'action de gravité à l'émetteur addAction (gravity);

Notez que nous définissons la Champ.massless propriété à false, ce qui est vrai par défaut. Lorsque cette propriété est définie sur true, les champs agissent comme des champs gravitationnels ordinaires, affectant tous les objets de la même manière, quelle que soit leur masse. Cependant, lorsque la propriété est définie sur false, la masse de particules est prise en compte: les particules de masse plus grande sont moins affectées par le champ, alors que les particules de masse plus faible sont plus affectées. C'est pourquoi nous avons utilisé le Masse initialiseur dans notre code d'émetteur précédent, pour ajouter un peu de hasard à l'effet de pluie.

Testez à nouveau le film et voici à quoi ressemble notre résultat. Les gouttes de pluie sont maintenant affectées par un champ gravitationnel et sont toutes "tirées" vers la droite.


Exemple: turbulence

Maintenant, nous allons échanger le UniformField objet avec un BitmapField objet, renvoyant des champs de vecteurs basés sur les canaux de couleur d’un bitmap, pour créer un effet de turbulence. Si vous travaillez avec ActionScript depuis un certain temps, vous pouvez vous attendre à utiliser le BitmapData.perlinNoise () méthode en pensant à la turbulence, et c'est exactement ce que nous allons faire.

Voici à quoi ressemble un exemple de bitmap de bruit Perlin. Le bruit Perlin est un excellent algorithme pour générer du bruit afin de simuler la turbulence, les vagues, les nuages, etc. Vous trouverez plus d'informations sur le bruit Perlin ici..

Champs bitmap

Vous vous demandez peut-être, si nous allons utiliser le BitmapData.perlinNoise () méthode pour générer un bitmap de bruit perlin, comment allons-nous utiliser ce bitmap en tant que champ de vecteur? Eh bien, c'est ce que le BitmapField la classe est pour. Il faut un bitmap et le convertit en champ vectoriel.

Disons que nous avons un champ bitmap dont Canal X est réglé sur rouge et Canal Y est vert. Cela signifie que lorsque le champ prend un vecteur d'entrée, disons (2, 3), il recherche le pixel du bitmap situé en (2, 3) et la composante X du vecteur de sortie est déterminée à partir du canal rouge du pixel, et la composante Y est déterminé à partir du canal vert. Lorsque les composantes d’un vecteur d’entrée ne sont pas des entiers, elles sont arrondies en premier..

La valeur d'un canal de couleur est comprise entre 0 et 255, 127 étant la moyenne. Une valeur inférieure à 127 est considérée comme négative par le champ bitmap, tandis qu'une valeur supérieure à 127 est considérée comme positive. Zéro est le le plus négatif nombre et 255 est le le plus positif un. Par exemple, si nous avons un pixel avec une couleur 0xFF0000 en représentation hexadécimale, ce qui signifie un canal rouge avec une valeur de 255 et un canal vert avec 0, la sortie du champ bitmap pour ce pixel serait un vecteur avec la composante X d'un le plus positif nombre possible et composante Y d'un le plus négatif nombre possible, où cela nombre le plus positif / négatif possible, ou nombre maximum, est spécifique au champ bitmap. Pour être plus précis, voici la formule de la conversion pixel en vecteur.


Étape 1: Flèches volantes de base

Créez un nouveau document Flash. Dessinez une flèche sur la scène, convertissez-la en un symbole nommé "Flèche" et exportez-la pour ActionScript.

Créez un fichier AS pour la classe de document. C'est presque la même chose que dans l'exemple précédent.

 package import flash.display. *; importer flash.events. *; import idv.cjcat.stardust.common.emitters. *; importer idv.cjcat.stardust.common.renderers. *; importer idv.cjcat.stardust.twoD.renderers. *; classe publique Turbulence étend Sprite private var emitter: Emitter; convertisseur de rendu privé: Renderer; fonction publique Turbulence () emitter = new ArrowEmitter (); renderer = new DisplayObjectRenderer (this); renderer.addEmitter (émetteur); addEventListener (Event.ENTER_FRAME, mainLoop);  fonction privée mainLoop (e: Event): void emitter.step (); 

Créer un autre fichier AS pour notre classe d'émetteur.

 package import idv.cjcat.stardust.common.actions. *; import idv.cjcat.stardust.common.clocks. *; importer idv.cjcat.stardust.common.initializers. *; import idv.cjcat.stardust.common.math. *; import idv.cjcat.stardust.twoD.actions. *; importer idv.cjcat.stardust.twoD.emitters. *; importer idv.cjcat.stardust.twoD.initializers. *; import idv.cjcat.stardust.twoD.zones. *; Classe publique ArrowEmitter étend Emitter2D fonction publique ArrowEmitter () super (new SteadyClock (1)); // initializer addInitializer (new DisplayObjectClass (Arrow)); addInitializer (nouvelle vie (nouvelle UniformRandom (50, 10))); addInitializer (nouvelle position (nouveau SinglePoint (320, 200))); addInitializer (new Velocity (new LazySectorZone (3, 2))); addInitializer (nouvelle masse (nouvelle UniformRandom (2, 1))); addInitializer (nouvelle échelle (nouvelle UniformRandom (1, 0,2))); // actions addAction (new Age ()); addAction (new DeathLife ()); addAction (new Move ()); addAction (new Oriented ()); addAction (nouvelle ScaleCurve (10, 10)); 

La progression actuelle ressemble à ceci.


Étape 2: Faites en sorte qu'il soit turbulent

Ajoutez le code suivant qui crée des données bitmap de bruit Perlin 640 sur 480 dans le constructeur de l'émetteur. Pour des explications détaillées sur chaque paramètre de la BitmapData.perlinNoise () méthode, vous pouvez vous référer à cette documentation. Pour simplifier les choses, le code suivant crée un bitmap de bruit Perlin avec des "octaves" approximativement de la taille 50X50, le bruit étant composé de canaux de couleur rouge et verte..

 // crée une donnée bitmap de bruit Perlin var noise: BitmapData = new BitmapData (640, 400); noise.perlinNoise (50, 50, 1, 0, vrai, vrai, 1 | 2);

Ensuite, créez un BitmapField objet et attribuer les données bitmap à travers le mettre à jour() méthode. Ensuite, le reste du code concernant la La gravité l'action est exactement la même que dans l'exemple précédent.

 // crée un champ uniforme qui renvoie toujours (0.5, 0) champ var: BitmapField = new BitmapField (); field.channelX = 1; // définit le canal X sur red field.channelY = 2; // définit le canal Y sur green field.max = 1; // définit la valeur absolue de la composante vectorielle max. field.update (noise); // met à jour le champ avec le bitmap de bruit // prend la masse des particules dans le compte field.massless = false; // crée une action de gravité et y ajoute le champ var gravity: Gravity = new Gravity (); gravité.addField (champ); // ajoute l'action de gravité à l'émetteur addAction (gravity);

Maintenant, testez à nouveau le film et vous verrez que nos flèches volantes connaissent maintenant des turbulences..


Les champs personnalisés

Nous avons utilisé le UniformField et BitmapField fourni par Stardust, et maintenant nous allons créer nos propres champs personnalisés en étendant la Champ classe et dépassant la getMotionData2D () méthode.

le getMotionData2D prend un Particle2D paramètre en entrée, qui contient les informations de vecteur de position de la particule, et c’est l’entrée du champ. La méthode retourne un MotionData2D objet, représentant la sortie du champ. C'est tout ce que vous devez savoir pour créer un champ personnalisé. Créons un champ de vortex personnalisé.

Vous trouverez ci-dessous le graphique visualisé de notre champ vortex. C'est assez explicite pourquoi on l'appelle un champ de vortex.

Voici la formule pour notre champ vortex.

Et la classe de champ vortex est aussi simple que le code ci-dessous. Nous avons utilisé le Vec2D classe de faire tout le sale boulot de faire pivoter un vecteur de 90 degrés dans le sens des aiguilles d’une montre et de définir la valeur absolue du vecteur. Ensuite, nous vidons les composantes x et y du vecteur dans un MotionData2D objet, qui est du type d'objet à retourner.

 package import idv.cjcat.stardust.twoD.fields. *; importer idv.cjcat.stardust.twoD.geom. *; importer idv.cjcat.stardust.twoD.particles. *; Classe publique VortexField. Field public var centerX: Number; public var centerY: Number; public var force: Number; fonction publique VortexField (centerX: Number = 0, centerY: Number = 0, force: Number = 1) this.centerX = centerX; this.centerY = centerY; this.force = force;  remplacer la fonction protégée CalculateMotionData2D (particule: Particle2D): MotionData2D var dx: Number = particule.x - centerX; var dy: Nombre = particule.y - centreY; var vec: Vec2D = new Vec2D (dx, dy); vec.length = force; vec.rotateThis (90); retourne un nouveau MotionData2D (vec.x, vec.y); 

Maintenant que nous avons notre champ personnalisé, testons-le dans l'exemple suivant.


Exemple: Vortex

Cet exemple sert d’essai pour notre champ de vecteurs vortex. C'est aussi simple que d'échanger un champ avec un nouveau. Changer le code suivant de l'exemple précédent

 // crée un champ uniforme qui renvoie toujours (0.5, 0) champ var: BitmapField = new BitmapField (); field.channelX = 1; // définit le canal X sur red field.channelY = 2; // définit le canal Y sur green field.max = 1; // définit la longueur maximale de champ vecotr.update (noise); // met à jour le champ avec le bitmap de bruit

pour ça

 // crée un champ de vortex centré sur (320, 200) avec un champ de force 1: VortexField = new VortexField (); field.centerX = 320; field.centerY = 200; field.strength = 1;

Et nous avons fini. Vous pouvez tester le film et voir l'effet vortex. Sucré!


Conclusion

Vous avez vu comment utiliser le La gravité action et Champ classe pour influer sur la vitesse des particules. Et vous avez appris à créer vos propres champs de vecteurs personnalisés à utiliser comme champs gravitationnels. La prochaine partie de ce didacticiel vous montrera comment utiliser les déflecteurs pour manipuler à la fois la position et la vitesse des particules..

Merci beaucoup d'avoir lu!