Faire un tireur de vecteur au néon pour iOS effets de particules

Dans cette série de didacticiels, je vais vous montrer comment créer un jeu de tir double-stick inspiré de Geometry Wars, avec des graphismes au néon, des effets de particules incroyables et une musique géniale, pour iOS avec C ++ et OpenGL ES 2.0. Dans cette partie, nous allons ajouter des explosions et un flair visuel.

Vue d'ensemble

Jusqu'à présent, dans la série, nous avons configuré le jeu et ajouté des commandes de manette de jeu virtuelles. Ensuite, nous allons ajouter des effets de particules.


Avertissement: fort!

Les effets de particules sont créés par la fabrication d’un grand nombre de petites particules. Ils sont très polyvalents et peuvent être utilisés pour ajouter du style à presque tous les jeux. Dans Shape Blaster, nous allons créer des explosions utilisant des effets de particules. Nous utiliserons également des effets de particules pour créer un tir d'échappement sur le vaisseau du joueur et pour ajouter une touche visuelle aux trous noirs. De plus, nous verrons comment faire en sorte que les particules interagissent avec la gravité provenant des trous noirs..

Changement pour libérer des versions pour des gains de vitesse

Jusqu'à maintenant, vous avez probablement construit et utilisé Shape Blaster en utilisant tous les paramètres par défaut. déboguer construction du projet. Bien que cela soit correct et pratique lorsque vous déboguez votre code, le débogage désactive la plupart des optimisations de vitesse et de calcul pouvant être effectuées, ainsi que de garder toutes les assertions activées dans le code..

En fait, si vous exécutez le code en mode débogage à partir de maintenant, vous remarquerez que la fréquence d'images commence à chuter de façon spectaculaire. Cela est dû au fait que nous ciblons un périphérique disposant d'une quantité de mémoire RAM réduite, d'une vitesse d'horloge du processeur et d'un matériel 3D plus petit par rapport à un ordinateur de bureau ou même un ordinateur portable..

Donc, à ce stade, vous pouvez éventuellement désactiver le débogage et activer le mode "release". Le mode Release nous donne une optimisation complète du compilateur et des mathématiques, ainsi que la suppression du code de débogage et des assertions non utilisés.

Une fois le projet ouvert, choisissez le Produit menu, Schème, puis Modifier le schéma… .


La fenêtre de dialogue suivante s'ouvrira. Choisir Courir sur le côté gauche du dialogue, et de Configuration de construction, changer l'élément pop-up de déboguer à Libération.


Vous remarquerez les gains de vitesse immédiatement. Le processus est facilement inversé si vous devez à nouveau déboguer le programme: il suffit de choisir déboguer au lieu de Libération et tu as fini.

Pointe: Notez cependant que tout changement de schéma comme celui-ci nécessite une recompilation complète du programme.

La classe ParticleManager

Nous allons commencer par créer un ParticleManager classe qui va stocker, mettre à jour et dessiner toutes les particules. Nous allons rendre cette classe assez générale pour qu'elle puisse être facilement réutilisée dans d'autres projets, mais nécessitera encore une personnalisation d'un projet à l'autre. Garder le ParticleManager aussi général que possible, il ne sera pas responsable de l'aspect ou du mouvement des particules; nous allons gérer cela ailleurs.

Les particules ont tendance à être créées et détruites rapidement et en grand nombre. Nous allons utiliser un pool d'objets pour éviter de créer de grandes quantités de déchets. Cela signifie que nous allons allouer un grand nombre de particules dès le départ, puis continuer à les réutiliser..

Nous ferons aussi ParticleManager avoir une capacité fixe. Cela simplifiera les choses et nous évitera de dépasser nos performances ou nos limites de mémoire en créant trop de particules. Lorsque le nombre maximal de particules est dépassé, nous allons commencer à remplacer les particules les plus anciennes par de nouvelles. Nous ferons le ParticleManager une classe générique. Cela nous permettra de stocker des informations d’état personnalisées pour les particules sans les coder en dur dans la mémoire.
ParticleManager lui-même.

Nous allons aussi créer un Particule classe:

 classe Particle public: ParticleState mState; tColor4f mColor; tVector2f mPosition; tVector2f mScale; tTexture * mTexture; flottement mOrientation; float mDuration; float mPercentLife; public: Particle (): mScale (1,1), mPercentLife (1.0f) ;

le Particule la classe a toutes les informations nécessaires pour afficher une particule et gérer sa durée de vie. ParticleState existe-t-il des données supplémentaires dont nous pourrions avoir besoin pour nos particules? Les données nécessaires varieront en fonction des effets de particules souhaités. il peut être utilisé pour stocker la vitesse, l'accélération, la vitesse de rotation ou toute autre chose dont vous pourriez avoir besoin.

Pour faciliter la gestion des particules, nous aurons besoin d'une classe fonctionnant comme un tableau circulaire, ce qui signifie que les index qui seraient normalement hors limites se replieront au début du tableau. Cela facilitera le remplacement des particules les plus anciennes en premier si nous manquons d’espace pour les nouvelles particules de notre réseau. Pour cela, nous ajoutons ce qui suit comme classe imbriquée dans ParticleManager:

 classe CircularParticleArray protected: std :: vector mList; size_t mStart; size_t mCount; public: CircularParticleArray (int capacité) mList.resize ((size_t) capacité));  size_t getStart () return mStart;  void setStart (valeur size_t) mStart = valeur% mList.size ();  size_t getCount () return mCount;  void setCount (size_t value) mCount = value;  size_t getCapacity () return mList.size ();  Particle & operator [] (const size_t i) return mList [(mStart + i)% mList.size ()];  const Particle & operator [] (const size_t i) const return mList [(mStart + i)% mList.size ()]; ;

Nous pouvons régler le mStart membre pour ajuster où l'indice zéro dans notre CircularParticleArray correspond à dans le tableau sous-jacent, et mCount sera utilisé pour suivre combien de particules actives sont dans la liste. Nous veillerons à ce que la particule d'indice zéro soit toujours la plus ancienne. Si nous remplaçons la particule la plus ancienne par une nouvelle, nous allons simplement incrémenter mStart, qui tourne essentiellement le réseau circulaire.

Maintenant que nous avons nos classes d’aide, nous pouvons commencer à remplir le ParticleManager classe. Nous aurons besoin d'une nouvelle variable membre et d'un constructeur.

 CircularParticleArray mParticleList; ParticleManager :: ParticleManager (capacité interne): mParticleList (capacité) 

Nous créons mParticleList et le remplir avec des particules vides. Le constructeur est le seul endroit où le ParticleManager alloue de la mémoire.

Ensuite, nous ajoutons le createParticle () méthode, qui crée une nouvelle particule en utilisant la particule suivante non utilisée du pool ou la particule la plus ancienne s'il n'y a pas de particules inutilisées.

 void ParticleManager :: createParticle (tTexture * texture, const tVector2f & position, const tColor4f & tint, durée de flottement, const tVector2f & scale, const ParticleState & state, float theta) size_t index; if (mParticleList.getCount () == mParticleList.getCapacity ()) index = 0; mParticleList.setStart (mParticleList.getStart () + 1);  else index = mParticleList.getCount (); mParticleList.setCount (mParticleList.getCount () + 1);  Particle & ref = mParticleList [index]; ref.mTexture = texture; ref.mPosition = position; ref.mColor = teinte; ref.mDuration = durée; ref.mPercentLife = 1.0f; ref.mScale = scale; ref.mOrientation = thêta; ref.mState = state; 

Les particules peuvent être détruites à tout moment. Nous devons éliminer ces particules tout en veillant à ce que les autres particules restent dans le même ordre. Nous pouvons le faire en parcourant la liste des particules tout en gardant une trace du nombre de celles qui ont été détruites. Au fur et à mesure, nous déplaçons chaque particule active devant toutes les particules détruites en les échangeant avec la première particule détruite. Une fois que toutes les particules détruites sont à la fin de la liste, nous les désactivons en définissant la liste. mCount variable en fonction du nombre de particules actives. Les particules détruites resteront dans le tableau sous-jacent, mais ne seront ni mises à jour ni dessinées..

ParticleManager :: update () gère la mise à jour de chaque particule et le retrait des particules détruites de la liste:

 void ParticleManager :: update () size_t removalCount = 0; pour (size_t i = 0; i < mParticleList.getCount(); i++)  Particle& ref = mParticleList[i]; ref.mState.updateParticle(ref); ref.mPercentLife -= 1.0f / ref.mDuration; Swap(mParticleList, i - removalCount, i); if (ref.mPercentLife < 0)  removalCount++;   mParticleList.setCount(mParticleList.getCount() - removalCount);  void ParticleManager::Swap(typename ParticleManager::CircularParticleArray& list, size_t index1, size_t index2) const  Particle temp = list[index1]; list[index1] = list[index2]; list[index2] = temp; 

La dernière chose à mettre en œuvre dans ParticleManager dessine les particules:

 Annuler ParticleManager :: draw (tSpriteBatch * spriteBatch) pour (size_t i = 0; i < mParticleList.getCount(); i++)  Particle particle = mParticleList[(size_t)i]; tPoint2f origin = particle.mTexture->getSurfaceSize () / 2; spriteBatch-> draw (2, particule.mTexture, tPoint2f ((int) particule.mPosition.x, (int) particule.mPosition.y), tOptional(), particule.mCouleur, particule.m.Orientation, origine, particule.m.échelle); 

La classe ParticleState

La prochaine chose à faire est de créer une classe ou une structure personnalisée pour personnaliser l'apparence des particules dans Shape Blaster. Shape Blaster comportera différents types de particules se comportant légèrement différemment, nous allons donc commencer par créer un enum pour le type de particule. Nous aurons également besoin de variables pour la vitesse de la particule et sa longueur initiale.

 classe ParticleState public: enum ParticleType kNone = 0, kEnemy, kBullet, kIgnoreGravity; public: tVector2f mVelocity; ParticleType mType; float mLengthMultiplier; public: ParticleState (); ParticleState (const tVector2f & vélocité, type ParticleType, float lengthMultiplier = 1.0f); ParticleState getRandom (float minVel, float maxVel); mise à jour videParticule (particule et particule); ;

Maintenant, nous sommes prêts à écrire la particule mettre à jour() méthode. C'est une bonne idée de faire cette méthode rapide, car il pourrait être appelé pour un grand nombre de particules.

Nous allons commencer simple. Ajoutons la méthode suivante à ParticleState:

 void ParticleState :: updateParticle (Particule et particule) tVector2f vel = particle.mState.mVelocity; particule.mPosition + = vel; particle.mOrientation = Extensions :: toAngle (vel); // Les flottants dénormalisés posent d'importants problèmes de performances si (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Nous reviendrons et améliorerons cette méthode dans un instant. Tout d'abord, créons des effets de particules pour pouvoir tester nos modifications..

Explosions ennemies

Dans GameRoot, déclarer un nouveau ParticleManager et appeler son mettre à jour() et dessiner() méthodes:

 // dans GameRoot protected: ParticleManager mParticleManager; public: ParticleManager * getParticleManager () return & mParticleManager;  // dans le constructeur GameRoot GameRoot :: GameRoot (): mParticleManager (1024 * 20), mViewportSize (800, 600), mSpriteBatch (NULL)  // dans GameRoot :: onRedrawView () mParticleManager.update (); mParticleManager.draw (mSpriteBatch);

En outre, nous allons déclarer une nouvelle instance du texture classe dans le Art classe appelée mLineParticule pour la texture de la particule. Nous allons le charger comme nous faisons les sprites de l'autre jeu:

 // Dans le constructeur de l'Art mLineParticle = new tTexture (tSurface ("laser.png"));

Maintenant, faisons exploser les ennemis. Nous allons modifier le Ennemi :: wasShot () méthode comme suit:

 void Enemy :: wasShot () mIsExpired = true; pour (int i = 0; i < 120; i++)  float speed = 18.0f * (1.0f - 1 / Extensions::nextFloat(1, 10)); ParticleState state(Extensions::nextVector2(speed, speed), ParticleState::kEnemy, 1); tColor4f color(0.56f, 0.93f, 0.56f, 1.0f); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, color, 190, 1.5f, state); 

Cela crée 120 particules qui vont sortir vers l'extérieur avec des vitesses différentes dans toutes les directions. La vitesse aléatoire est pondérée de telle sorte que les particules sont plus susceptibles de se déplacer près de la vitesse maximale. Cela entraînera plus de particules au bord de l'explosion lors de son expansion. Les particules durent 190 images, soit un peu plus de trois secondes.

Vous pouvez maintenant lancer le jeu et regarder les ennemis exploser. Cependant, il reste quelques améliorations à apporter aux effets de particules.

Le premier problème est que les particules disparaissent brusquement une fois leur durée écoulée. Ce serait mieux s'ils pouvaient disparaître en douceur, mais allons un peu plus loin et rendons les particules plus brillantes lorsqu'elles se déplacent rapidement. En outre, il est intéressant d’allonger les particules rapides et les particules lentes.

Modifier le ParticleState.UpdateParticle () méthode comme suit (les modifications sont mises en évidence).

 void ParticleState :: updateParticle (Particule et particule) tVector2f vel = particle.mState.mVelocity; particule.mPosition + = vel; particle.mOrientation = Extensions :: toAngle (vel); vitesse de flottement = vel.length (); float alpha = tMath :: min (1.0f, tMath :: min (particule.mPercentLife * 2, vitesse * 1.0f)); alpha * = alpha; particule.mCouleur.a = alpha; particle.mScale.x = particle.mState.mLengthMultiplier * tMath :: min (tMath :: min (1,0f, 0,2f * vitesse + 0,1f), alpha); // Les flottants dénormalisés posent d'importants problèmes de performances si (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Les explosions ont l'air beaucoup mieux maintenant, mais elles ont toutes la même couleur.

Les explosions monochromatiques sont un bon début, mais pouvons-nous faire mieux??

Nous pouvons leur donner plus de variété en choisissant des couleurs aléatoires. Une méthode pour produire des couleurs aléatoires consiste à choisir les composants rouge, bleu et vert de manière aléatoire, mais cela produira beaucoup de couleurs ternes et nous aimerions que nos particules aient une apparence de lumière néon. Nous pouvons avoir plus de contrôle sur nos couleurs en les spécifiant dans l'espace colorimétrique HSV. HSV signifie teinte, saturation et valeur. Nous aimerions choisir des couleurs avec une teinte aléatoire, mais une saturation et une valeur fixes. Nous avons besoin d'une fonction d'assistance capable de produire une couleur à partir des valeurs HSV.

 tColor4f ColorUtil :: HSVToColor (float h, float s, float v) if (h == 0 && s == 0) retourne tColor4f (v, v, v, 1.0f);  float c = s * v; float x = c * (1 - abs (int32_t (h)% 2 - 1)); float m = v - c; si (h < 1) return tColor4f(c + m, x + m, m, 1.0f); else if (h < 2) return tColor4f(x + m, c + m, m, 1.0f); else if (h < 3) return tColor4f(m, c + m, x + m, 1.0f); else if (h < 4) return tColor4f(m, x + m, c + m, 1.0f); else if (h < 5) return tColor4f(x + m, m, c + m, 1.0f); else return tColor4f(c + m, m, x + m, 1.0f); 

Maintenant on peut modifier Ennemi :: wasShot () utiliser des couleurs aléatoires. Pour rendre la couleur de l'explosion moins monotone, nous allons sélectionner deux couleurs de clé proches pour chaque explosion et les interpoler linéairement entre elles d'une quantité aléatoire pour chaque particule:

 void Enemy :: wasShot () mIsExpired = true; float hue1 = Extensions :: nextFloat (0, 6); float hue2 = fmodf (hue1 + Extensions :: nextFloat (0, 2), 6.0f); tColor4f color1 = ColorUtil :: HSVToColor (hue1, 0.5f, 1); tColor4f color2 = ColorUtil :: HSVToColor (hue2, 0.5f, 1); pour (int i = 0; i < 120; i++)  float speed = 18.0f * (1.0f - 1 / Extensions::nextFloat(1, 10)); ParticleState state(Extensions::nextVector2(speed, speed), ParticleState::kEnemy, 1); tColor4f color = Extensions::colorLerp(color1, color2, Extensions::nextFloat(0, 1)); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, color, 190, 1.5f, state); 

Les explosions devraient ressembler à l'animation ci-dessous:


Vous pouvez jouer avec la génération de couleurs en fonction de vos préférences. Une autre technique qui fonctionne bien consiste à sélectionner à la main un certain nombre de motifs de couleur pour les explosions et à choisir au hasard parmi les couleurs que vous avez choisies.

Bullet Explosions

Nous pouvons également faire exploser les balles lorsqu'elles atteignent le bord de l'écran. Nous ferons essentiellement la même chose que nous avons faite pour les explosions ennemies.

Modifions Bullet :: update () comme suit:

 if (! tRectf (0, 0, GameRoot :: getInstance () -> getViewportSize ()).) contient (tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y))) mIsExpired = true; pour (int i = 0; i < 30; i++)  GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, tColor4f (0.67f, 0.85f, 0.90f, 1), 50, 1, ParticleState (Extensions :: nextVector2 (0, 9) , ParticleState :: kBullet, 1)); 

Vous remarquerez peut-être que donner une direction aléatoire aux particules est une perte de temps, car au moins la moitié des particules quitteront immédiatement l'écran (davantage si la balle explose dans un coin). Nous pourrions faire un travail supplémentaire pour nous assurer que les particules ne reçoivent que des vitesses opposées au mur auquel elles font face. Cependant, nous allons nous inspirer de Geometry Wars et faire en sorte que toutes les particules rebondissent sur les murs, de sorte que toutes les particules sortant de l'écran seront renvoyées..

Ajoutez les lignes suivantes à ParticleState.UpdateParticle () n'importe où entre la première et la dernière ligne:

 tVector2f pos = particle.mPosition; int width = (int) GameRoot :: getInstance () -> getViewportSize (). width; int height = (int) GameRoot :: getInstance () -> getViewportSize (). height; // entrer en collision avec les bords de l'écran if (pos.x < 0)  vel.x = (float)fabs(vel.x);  else if (pos.x > largeur) vel.x = (float) -fabs (vel.x);  if (pos.y < 0)  vel.y = (float)fabs(vel.y);  else if (pos.y > hauteur) vel.y = (float) -fabs (vel.y); 

Explosion du navire du joueur

Nous allons faire une très grosse explosion quand le joueur est tué. Modifier PlayerShip :: kill () ainsi:

 void PlayerShip :: kill () PlayerStatus :: getInstance () -> removeLife (); mFramesUntilRespawn = PlayerStatus :: getInstance () -> getIsGameOver ()? 300: 120; tColor4f explosionColor = tColor4f (0.8f, 0.8f, 0.4f, 1.0f); pour (int i = 0; i < 1200; i++)  float speed = 18.0f * (1.0f - 1 / Extensions::nextFloat(1.0f, 10.0f)); tColor4f color = Extensions::colorLerp(tColor4f(1,1,1,1), explosionColor, Extensions::nextFloat(0, 1)); ParticleState state(Extensions::nextVector2(speed, speed), ParticleState::kNone, 1); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPosition, color, 190, 1.5f, state); 

Cela ressemble aux explosions ennemies, mais nous utilisons plus de particules et utilisons toujours le même jeu de couleurs. Le type de particule est également défini sur ParticleState :: kNone.

Dans la démo, les particules provenant d'explosions ennemies ralentissent plus rapidement que celles du vaisseau du joueur en train d'exploser. Cela rend l'explosion du joueur un peu plus longue et un peu plus épique.

Trous noirs revisités

Maintenant que nous avons des effets de particules, revisitons les trous noirs et faisons-les interagir avec les particules.

Effet sur les particules

Les trous noirs devraient affecter les particules en plus des autres entités, nous devons donc modifier ParticleState :: updateParticle (). Ajoutons les lignes suivantes:

 if (particle.mState.mType! = kIgnoreGravity) pour (std :: list:: itérateur j = EntityManager :: getInstance () -> mBlackHoles.begin (); j! = EntityManager :: getInstance () -> mBlackHoles.end (); j ++) tVector2f dPos = (* j) -> getPosition () - pos; distance de flottement = dPos.length (); tVector2f n = dPos / distance; vel + = 10000.0f * n / (distance * distance + 10000.0f); // ajoute une accélération tangentielle pour les particules proches si (distance < 400)  vel += 45.0f * tVector2f(n.y, -n.x) / (distance + 100.0f);   

Ici, n est le vecteur unitaire pointant vers le trou noir. La force d'attraction est une version modifiée de la fonction carrée inverse:

  • La première modification est que le dénominateur est distance ^ 2 + 10 000; cela force la force d'attraction à s'approcher d'une valeur maximale au lieu de tendre vers l'infini car la distance devient très petite.
    • Lorsque la distance est beaucoup plus grande que 100 pixels, distance ^ 2 devient beaucoup plus grand que 10.000. Par conséquent, en ajoutant 10 000 à distance ^ 2 a un très petit effet, et la fonction se rapproche d'une fonction carrée inverse normale.
    • Toutefois, lorsque la distance est beaucoup plus petite que 100 pixels, la distance a un faible effet sur la valeur du dénominateur et l'équation devient approximativement égale à: vel + = n
  • La deuxième modification consiste à ajouter une composante latérale à la vitesse lorsque les particules se rapprochent suffisamment du trou noir. Cela sert à deux fins:
    1. Il fait tourner les particules en spirale dans le sens des aiguilles d'une montre vers le trou noir.
    2. Lorsque les particules se rapprochent suffisamment, elles atteignent l'équilibre et forment un cercle rougeoyant autour du trou noir..

Pointe: Faire pivoter un vecteur, V, 90 ° dans le sens des aiguilles d'une montre, prenez (V.Y, -V.X). De même, pour le faire pivoter de 90 ° dans le sens anti-horaire, prenez (-V.Y, V.X).

Produire des particules

Un trou noir va produire deux types de particules. Premièrement, il pulvérisera périodiquement les particules qui graviteront autour de lui. Deuxièmement, quand un trou noir est tiré, il pulvérisera des particules spéciales qui ne sont pas affectées par sa gravité.

Ajoutez le code suivant au BlackHole :: WasShot () méthode:

 float hue = fmodf (3.0f / 1000.0f * tTimer :: getTimeMS (), 6); tColor4f color = ColorUtil :: HSVToColor (teinte, 0.25f, 1); const int numParticles = 150; float startOffset = Extensions :: nextFloat (0, tMath :: PI * 2.0f / numParticles); pour (int i = 0; i < numParticles; i++)  tVector2f sprayVel = MathUtil::fromPolar(tMath::PI * 2.0f * i / numParticles + startOffset, Extensions::nextFloat(8, 16)); tVector2f pos = mPosition + 2.0f * sprayVel; ParticleState state(sprayVel, ParticleState::kIgnoreGravity, 1.0f); GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, color, 90, 1.5f, state); 

Cela fonctionne essentiellement de la même manière que les autres explosions de particules. Une différence est que nous sélectionnons la teinte de la couleur en fonction du temps total écoulé. Si vous tirez plusieurs fois de suite dans le trou noir, vous constaterez une rotation progressive de la teinte des explosions. Cela semble moins compliqué que d'utiliser des couleurs aléatoires, tout en permettant la variation.

Pour le spray de particules en orbite, nous devons ajouter une variable à la Trou noir classe pour suivre la direction dans laquelle nous pulvérisons actuellement des particules:

 protected: int points mHitPoints; float mSprayAngle; BlackHole :: BlackHole (const tVector2f & position): mSprayAngle (0) …

Nous allons maintenant ajouter ce qui suit au BlackHole :: update () méthode.

 // Les trous noirs vaporisent des particules en orbite. La pulvérisation bascule et se désactive tous les quarts de seconde. if ((tTimer :: getTimeMS () / 250)% 2 == 0) tVector2f sprayVel = MathUtil :: fromPolar (mSprayAngle, Extensions :: nextFloat (12, 15)); tColor4f color = ColorUtil :: HSVToColor (5, 0.5f, 0.8f); tVector2f pos = mPosition + 2.0f * tVector2f (sprayVel.y, -sprayVel.x) + extensions :: nextVector2 (4, 8); État ParticleState (sprayVel, ParticleState :: kEnemy, 1.0f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, color, 190, 1.5f, state);  // fait pivoter la direction de pulvérisation mSprayAngle - = tMath :: PI * 2.0f / 50.0f;

Les trous noirs pulvériseront des particules violettes qui formeront un anneau en orbite autour du trou noir, comme suit:

Incendie des navires

Comme le dictent les lois de la physique géométrique et du néon, le vaisseau du joueur se propulse en projetant un flot de particules de feu sur son tuyau d'échappement. Avec notre moteur de particules en place, cet effet est facile à créer et ajoute une touche visuelle au mouvement du navire..

Au fur et à mesure que le navire se déplace, nous créons trois flux de particules: un flux central qui se propage directement à l'arrière du navire et deux flux latéraux dont les angles pivotent d'avant en arrière par rapport au navire. Les deux courants latéraux pivotent dans des directions opposées pour former un motif entrecroisé. Les flux latéraux ont une couleur plus rouge tandis que le flux central a une couleur plus chaude, jaune-blanc. L'animation ci-dessous montre l'effet:


Pour rendre le feu plus lumineux, nous allons faire en sorte que le navire émette des particules supplémentaires ressemblant à ceci:


Ces particules seront teintées et mélangées aux particules régulières. Le code de l’effet complet est présenté ci-dessous:

 void PlayerShip :: MakeExhaustFire () if (mVelocity.lengthSquared ()> 0.1f) mOrientation = Extensions :: toAngle (mVelocity); float cosA = cosf (mOrientation); float sinA = sinf (mOrientation); tMatrix2x2f rot (tVector2f (cosA, sinA), tVector2f (-sinA, cosA)); float t = tTimer :: getTimeMS () / 1000.0f; tVector2f baseVel = Extensions :: scaleTo (mVelocity, -3); tVector2f perpVel = tVector2f (baseVel.y, -baseVel.x) * (0.6f * (float) sinf (t * 10.0f)); tColor4f sideColor (0,78f, 0,15f, 0,04f, 1); tColor4f midColor (1,0f, 0,73f, 0,12f, 1); tVector2f pos = mPosition + rot * tVector2f (-25, 0); // position du tuyau d'échappement du navire. alpha de const float = 0.7f; // flux de particules moyen tVector2f velMid = baseVel + Extensions :: nextVector2 (0, 1); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (velMid, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, midColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (velMid, ParticleState: : kEnemy)); // flux de particules latérales tVector2f vel1 = baseVel + perpVel + Extensions :: nextVector2 (0, 0.3f); tVector2f vel2 = baseVel - perpVel + Extensions :: nextVector2 (0, 0.3f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel1, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel2, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel1, ParticleState: : kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel2, ParticleState: : kEnemy)); 

Il n'y a rien de sournois dans ce code. Nous utilisons une fonction sinus pour produire l’effet de pivotement dans les courants latéraux en faisant varier leur vitesse latérale dans le temps. Pour chaque flux, nous créons deux particules superposées par image: une semi-transparente, blanche LineParticle, et une particule de lueur colorée derrière elle. Appel
MakeExhaustFire () au bout du PlayerShip.Update (), immédiatement avant de régler la vitesse du navire à zéro.

Conclusion

Avec tous ces effets de particules, Shape Blaster commence à avoir l'air cool. Dans la dernière partie de cette série, nous ajouterons un autre effet impressionnant: la grille de fond de déformation.