Faceți un Shooter Vector Neon pentru iOS Efecte cu particule

În această serie de tutoriale, vă vom arăta cum să faceți un shooter twin-stick inspirat de Geometry Wars, cu grafică neon, efecte particulare nebune și muzică minunată, pentru iOS folosind C ++ și OpenGL ES 2.0. În această parte, vom adăuga explozii și fler vizual.

Prezentare generală

În seria de până acum, am creat gameplay-ul și am adăugat controale virtuale gamepad. În continuare, vom adăuga efecte particulare.


Avertizare: Tare!

Efectele particulelor sunt create prin realizarea unui număr mare de particule mici. Ele sunt foarte versatile și pot fi folosite pentru a adăuga fler la aproape orice joc. În Shape Blaster vom face explozii folosind efectele particulelor. De asemenea, vom folosi efecte de particule pentru a crea focul de evacuare pentru nava jucătorului și pentru a adăuga un fler vizual la găurile negre. În plus, vom examina modul în care particulele interacționează cu gravitatea din găurile negre.

Schimbarea la Release Builds pentru câștigurile de viteză

Până acum, probabil că ați construit și a executat Shape Blaster utilizând toate setările implicite depanare construirea proiectului. În timp ce acest lucru este bine și excelent atunci când depanați codul, depanarea dezactivează cele mai multe opțiuni de viteză și matematică care pot fi efectuate, precum și păstrarea activată a tuturor afirmațiilor din cod.

De fapt, dacă rulați codul în modul de depanare de aici de pe aici, veți observa că ritmul de declanșare a cadrelor scade dramatic. Acest lucru se datorează faptului că ne-am vizat un dispozitiv care are o cantitate redusă de memorie RAM, procesor de viteză CPU și hardware 3D mai mic comparativ cu un computer desktop sau chiar un laptop.

Deci, în acest moment, puteți dezactiva opțiunea de depanare și puteți activa modul "eliberare". Modul de eliberare ne oferă o compilație completă și o optimizare matematică, precum și eliminarea codului și afirmațiilor de depanare neutilizate.

Odată ce deschideți proiectul, alegeți Produs meniul, Sistem, atunci Editați schema ... .


Se va deschide următoarea fereastră de dialog. Alege Alerga în partea stângă a dialogului și de la Construiți configurația, modificați elementul pop-up de la depanare la eliberare.


Veți observa câștigurile de viteză imediat. Procesul este ușor de inversat dacă trebuie să depanați din nou programul: alegeți depanare in loc de eliberare și ați terminat.

Bacsis: Rețineți totuși că orice schimbare a schemei de acest gen necesită o recompilare completă a programului.

Clasa ParticleManager

Vom începe prin crearea unui ParticleManager clasa care va stoca, actualiza și trage toate particulele. Vom face ca această clasă să fie destul de generală încât să poată fi refolosită cu ușurință în alte proiecte, dar va necesita în continuare unele particularizări de la proiect la proiect. Pentru a păstra ParticleManager cât mai general posibil, nu va fi responsabil pentru modul în care particulele arata sau se mișcă; vom rezolva asta în altă parte.

Particulele tind să fie create și distruse rapid și în număr mare. Vom folosi un bazin de obiecte pentru a evita crearea de cantități mari de gunoi. Aceasta înseamnă că vom aloca un număr mare de particule în față și apoi vom reuși să reutilizăm aceleași particule.

Vom face și noi ParticleManager au o capacitate fixă. Acest lucru îl va simplifica și ne va ajuta să nu depășim performanțele sau limitele noastre de memorie, creând prea multe particule. Când numărul maxim de particule este depășit, vom începe să înlocuim cele mai vechi particule cu altele noi. Vom face ParticleManager o clasă generică. Acest lucru ne va permite să stocăm informații personalizate de stare pentru particule, fără a fi greu să le codificăm
ParticleManager în sine.

Vom crea, de asemenea, o particulă clasă:

 Particule de clasă public: ParticleState mState; tColor4f mColor; tVector2f mPosition; tVector2f mScale; tText * mTexture; float mOrientation; float mDuration; float mPercentLife; public: Particule (): mScale (1,1), mPercentLife (1.0f) ;

particulă clasa are toate informațiile necesare pentru a afișa o particulă și a gestiona durata sa de viață. ParticleState este acolo să deținem orice date suplimentare de care avem nevoie pentru particulele noastre. Ce date sunt necesare vor varia în funcție de efectele de particule dorite; ar putea fi folosit pentru a stoca viteza, accelerația, viteza de rotație sau orice altceva de care ai nevoie.

Pentru a ajuta la gestionarea particulelor, vom avea nevoie de o clasă care să funcționeze ca o matrice circulară, ceea ce înseamnă că indicii care ar fi în mod normal în afara limitelor vor fi împachetați în jurul valorii de la începutul matricei. Acest lucru va face mai ușor să înlocuiți mai întâi cele mai vechi particule, dacă nu avem spațiu pentru noi particule din matricea noastră. Pentru aceasta, adăugăm următoarele ca o clasă imbricată în ParticleManager:

 class CircularParticleArray protejat: std :: vector mList; size_t mStart; size_t mCount; public: CircularParticleArray (capacitatea int) mList.resize ((size_t) capacitate);  size_t getStart () retur mStart;  void setStart (valoare_valoare_mărimea) mStart = valoare% mList.size ();  size_t getCount () retur mCount;  void setCount (valoarea_valoarea_importantă) mCount = valoare;  size_t getCapacitate () returnați mList.size ();  Particle & operator [] (const dimensiune_t i) retur mList [(mStart + i)% mList.size ()];  const Particle & operator [] (const const_m) i const return mlist [(mStart + i)% mList.size ()]; ;

Putem seta mStart membru pentru a regla unde index zero în nostru CircularParticleArray corespunde cu matricea de bază, și mCount va fi folosit pentru a urmări numărul de particule active din listă. Vom asigura că particula la indicele zero este întotdeauna cea mai veche particulă. Dacă vom înlocui cea mai veche particulă cu una nouă, vom crește pur și simplu mStart, care în esență rotește matricea circulară.

Acum că avem cursurile ajutoarelor noastre, putem începe să completați ParticleManager clasă. Avem nevoie de o nouă variabilă de membru și de un constructor.

 CircularParticleArray mParticleList; ParticleManager :: ParticleManager (capacitate int): mParticleList (capacitate) 

Noi creăm mParticleList și umpleți-o cu particule goale. Constructorul este singurul loc unde ParticleManager alocă memoria.

Apoi, adăugăm createParticle () care creează o nouă particulă utilizând următoarea particulă neutilizată din piscină sau cea mai veche particulă dacă nu există particule neutilizate.

 void ParticleManager :: createParticle (tText * textura, const tVector2f & pozitie, const tColor4f & tint, durata flotorului, const tVector2f & scara, const ParticleState & state, float theta) size_t index; dacă (mParticleList.getCount () == mParticleList.getCapacitate ()) index = 0; mParticleList.setStart (mParticleList.getStart () + 1);  altceva index = mParticleList.getCount (); mParticleList.setCount (mParticleList.getCount () + 1);  Particule & ref = mParticleList [index]; ref.mTexture = textură; ref.mPosition = poziție; ref.mColor = tint; ref.mDuration = duration; ref.mPercentLife = 1.0f; ref.mScale = scară; ref.mOrientation = theta; ref.mState = state; 

Particulele pot fi distruse oricând. Trebuie să îndepărtăm aceste particule, asigurându-ne că celelalte particule rămân în aceeași ordine. Putem face acest lucru prin iterarea prin lista de particule, urmărind în același timp câte au fost distruse. Pe măsură ce mergem, mutăm fiecare particulă activă în fața tuturor particulelor distruse, schimbând-o cu prima particulă distrusă. Odată ce toate particulele distruse sunt la sfârșitul listei, le dezactivează prin setarea listei mCount variabilă la numărul de particule active. Particulele distruse vor rămâne în matricea de bază, dar nu vor fi actualizate sau desenate.

ParticleManager :: actualizare () se ocupă de actualizarea fiecărei particule și de eliminarea particulelor distruse din listă:

 void ParticleManager :: actualizare () size_t removeCount = 0; pentru (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; 

Ultimul lucru pe care trebuie să-l implementăm ParticleManager desenează particulele:

 void ParticleManager :: trage (tSpriteBatch * spriteBatch) pentru (size_t i = 0; i < mParticleList.getCount(); i++)  Particle particle = mParticleList[(size_t)i]; tPoint2f origin = particle.mTexture->getSurfaceSize () / 2; spriteBatch-> remiză (2, particle.mTexture, tPoint2f ((int) particle.mPosition.x, (int) particle.mPosition.y), tOptional(), particulă.Color, particulă, Origine, origine, particulă.Scala); 

Clasa ParticleState

Următorul lucru pe care trebuie să-l faceți este să creați o clasă sau structură personalizată pentru a personaliza modul în care particulele vor arăta în Shape Blaster. Vor exista mai multe tipuri diferite de particule în Shape Blaster care se comportă ușor diferit, așa că vom începe prin crearea unui enum pentru tipul de particule. Vom avea nevoie și de variabile pentru viteza particulei și lungimea inițială.

 clasa ParticleState public: enum ParticleType kNone = 0, kEnemy, kBullet, kIgnoreGravity; public: tVector2f mVelocity; Tipul de mărime; plutiți mai mult; public: ParticleState (); ParticleState (const tVector2f & viteza, tipul de tip particule, lungimea flotoruluiMultiplier = 1.0f); Starea particulelor getRandom (float minVel, float maxVel); void updateParticle (Particule & particule); ;

Acum suntem gata să scriem particulele Actualizați() metodă. Este o idee bună să faceți această metodă rapidă, deoarece ar putea fi necesară numirea unui număr mare de particule.

Vom începe simplu. Să adăugăm următoarea metodă la ParticleState:

 void ParticleState :: updateParticle (Particule & particule) tVector2f vel = particle.mState.mVelocity; particle.mPoziția + = vel; particle.mOrientation = Extensii :: to Anghel (vel); // flotoarele denormalizate produc probleme semnificative de performanță dacă (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Vom reveni și vom îmbunătăți această metodă într-un moment. În primul rând, să creăm anumite efecte de particule, astfel încât să putem testa efectiv schimbările noastre.

Exploziile de inamici

În GameRoot, declara un nou ParticleManager și numiți-o Actualizați() și a desena() metode:

 // în GameRoot protejat: ParticleManager mParticleManager; public: ParticleManager * getParticleManager () return & mParticleManager;  // în constructorul GameRoot GameRoot :: GameRoot (): mParticleManager (1024 * 20), mViewportSize (800, 600), mSpriteBatch (NULL)  // în GameRoot :: onRedrawView () mParticleManager.update (); mParticleManager.draw (mSpriteBatch);

De asemenea, vom declara o nouă instanță a tTexture clasa în Artă clasa numită mLineParticle pentru textura particulei. O vom încărca ca și cum vom face spritele celorlalți jucători:

 // În constructorul lui ArtLineParticle = tTexture nou (tSurface ("laser.png"));

Acum hai să-i facem pe dusmani să explodeze. Vom modifica Enemy :: wasShot () după cum urmează:

 void Enemy :: wasShot () mIsExpired = true; pentru (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 (), mPoziție, culoare, 190, 1.5f, stare); 

Acest lucru creează 120 de particule care vor trage spre exterior cu viteze diferite în toate direcțiile. Viteza aleatorie este ponderată astfel încât particulele sunt mai susceptibile de a se deplasa în apropierea vitezei maxime. Acest lucru va determina mai multe particule să fie la marginea exploziei pe măsură ce se extinde. Particulele au 190 de cadre, sau doar peste trei secunde.

Acum puteți rula jocul și vă puteți uita că inamicii explodează. Cu toate acestea, există încă unele îmbunătățiri care trebuie făcute pentru efectele particulelor.

Prima problemă este că particulele dispar brusc odată ce durata acestora se scurge. Ar fi mai bine dacă s-ar putea să se estompeze cu ușurință, dar să mergem puțin mai departe și să facem ca particulele să strălucească mai repede când se mișcă repede. De asemenea, ar fi frumos să prelungim particulele care se mișcă rapid și să scurteze cele lentătoare.

Modificați ParticleState.UpdateParticle () după cum urmează (schimbările sunt evidențiate).

 void ParticleState :: updateParticle (Particule & particule) tVector2f vel = particle.mState.mVelocity; particle.mPoziția + = vel; particle.mOrientation = Extensii :: to Anghel (vel); viteza flotorului = lungimea (); float alfa = tMath :: min (1.0f, tMath :: min (particle.mPercentLife * 2, viteza * 1.0f)); alfa * = alfa; particulă.Color.a = alfa; particle.mScale.x = particule.mState.mLungimeMultiplier * tMath :: min (tMath :: min (1.0f, 0.2f * viteza + 0.1f), alfa); // flotoarele denormalizate produc probleme semnificative de performanță dacă (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Exploziile arată mult mai bine acum, dar toate au aceeași culoare.

Exploziile monocromatice sunt un început bun, dar putem face mai bine?

Le putem oferi mai multă varietate alegând culori aleatorii. O metodă de a produce culori aleatorii este alegerea aleatorie a componentelor roșii, albastre și verzi, dar acest lucru va produce o mulțime de culori plictisitoare și dorim ca particulele noastre să aibă un aspect de lumină neon. Putem avea mai mult control asupra culorilor noastre specificându-le în spațiul de culoare HSV. HSV înseamnă nuanță, saturație și valoare. Am dori să alegem culori cu o nuanță aleatorie, dar cu o saturație și o valoare fixă. Avem nevoie de o funcție de ajutor care poate produce o culoare din valorile HSV.

 tColor4f ColorUtil :: HSVToColor (float h, float s, float v) if (h == 0 && s == 0) retur tColor4f (v, v, v, 1.0f);  float c = s * v; float x = c * (1 - abs (int32_t (h)% 2 - 1)); float m = v - c; dacă (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); 

Acum putem modifica Enemy :: wasShot () pentru a utiliza culori aleatorii. Pentru a face culoarea exploziei mai puțin monotonă, vom alege două culori cheie învecinate pentru fiecare explozie și vom interpola liniar între ele cu o valoare aleatorie pentru fiecare particulă:

 void Enemy :: wasShot () mIsExpired = true; float hue1 = extensii :: nextFloat (0, 6); float hue2 = fmodf (hue1 + extensii :: nextFloat (0, 2), 6.0f); tColor4f color1 = CuloareUtil :: HSVToColor (hue1, 0.5f, 1); tColor4f color2 = CuloareUtil :: HSVToColor (hue2, 0.5f, 1); pentru (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 (), mPoziție, culoare, 190, 1.5f, stare); 

Exploziile ar trebui să arate ca animația de mai jos:


Puteți juca cu generația de culori pentru a se potrivi preferințelor dvs. O tehnică alternativă care funcționează bine este de a alege o serie de modele de culori pentru explozii și de a alege aleatoriu din schemele de culori alese în prealabil.

Exploziile cu bullet

De asemenea, putem face gloanțele să explodeze când ajung la marginea ecranului. Vom face în mod esențial același lucru pe care l-am făcut pentru explozii inamice.

Să modificăm Bullet :: actualizare () după cum urmează:

 dacă tRectf (0, 0, GameRoot :: getInstance () -> getViewportSize ()) conține (tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y))) mIsExpired = true; pentru (int i = 0; i < 30; i++)  GameRoot::getInstance()->getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), mPoziție, tColor4f (0.67f, 0.85f, 0.90f, 1), 50, 1 ParticleState (extensions :: nextVector2 , ParticleState :: kBullet, 1)); 

S-ar putea observa că dând particulelor o direcție aleatorie este risipă, pentru că cel puțin jumătate din particule se vor deplasa imediat în afara ecranului (mai mult dacă gloanțele explodează într-un colț). Am putea face ceva efort pentru a ne asigura că particulele sunt date doar cu viteze opuse față de peretele cu care se confruntă. În schimb, vom lua un indiciu de la Geometry Wars și vom face ca toate particulele să sări de pe pereți, astfel încât eventualele particule care se află în afara ecranului vor fi refăcute.

Adăugați următoarele linii la ParticleState.UpdateParticle () oriunde între prima și ultima linie:

 tVector2f pos = particle.mPoziție; int lățime = (int) GameRoot :: getInstance () -> getViewportSize () width; int înălțime = (int) GameRoot :: getInstance () -> getViewportSize () înălțime; // se ciocnesc cu marginile ecranului dacă (poz.x. < 0)  vel.x = (float)fabs(vel.x);  else if (pos.x > lățime) vel.x = (float) -fabs (vel.x);  dacă (pos.y < 0)  vel.y = (float)fabs(vel.y);  else if (pos.y > înălțime) vel.y = (float) -fabs (vel.y); 

Explosiunea navei jucătorului

Vom face o explozie foarte mare când jucătorul va fi ucis. Modifica PlayerShip :: ucide () ca astfel:

 void PlayerShip :: kill () PlayerStatus :: getInstance () -> removeLife (); mFramesUntilRespawn = PlayerStatus :: getInstance () -> getIsGameOver ()? 300: 120; tColor4f exploionColor = tColor4f (0,8f, 0,8f, 0,4f, 1,0f); pentru (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 (), mPoziție, culoare, 190, 1.5f, stare); 

Acest lucru este similar cu exploziile inamice, dar folosim mai multe particule și folosim întotdeauna aceeași schemă de culoare. Tipul de particule este, de asemenea, setat la ParticleState :: kNone.

În demo, particulele din explozii inamice încetinesc mai repede decât particulele de pe nava jucătorului care explodează. Acest lucru face ca explozia jucătorului să dureze un pic mai mult și să pară un pic mai epic.

Găurile negre revizuite

Acum, că avem efecte particulare, să revizuim gaurile negre și să le facem să interacționeze cu particulele.

Efectul asupra particulelor

Găurile negre ar trebui să afecteze particule în plus față de alte entități, deci trebuie să le modificăm ParticleState :: updateParticle (). Să adăugăm următoarele rânduri:

 dacă (particle.mState.mType! = kIgnoreGravity) pentru (std :: lista:: iterator j = EntityManager :: getInstance () -> mBlackHoles.begin (); j! = EntityManager :: getInstance () -> mBlackHoles.end (); j ++) tVector2f dPos = (* j) -> getPozi () - pos; distanța flotantă = dPos.length (); tVector2f n = dPos / distanță; vel + = 10000.0f * n / (distanța * distanța + 10000.0f); // adăugați accelerația tangențială pentru particulele din apropiere dacă (distanța < 400)  vel += 45.0f * tVector2f(n.y, -n.x) / (distance + 100.0f);   

Aici, n este vectorul unității care indică spre gaura neagră. Forța atractivă este o versiune modificată a funcției inverse pătrate:

  • Prima modificare este că numitorul este distanța ^ 2 + 10.000; acest lucru determină forța atrăgătoare să se apropie de o valoare maximă în loc să se tindă către infinit, deoarece distanța devine foarte mică.
    • Atunci când distanța este mult mai mare de 100 de pixeli, distanța ^ 2 devine mult mai mare decât 10.000. Prin urmare, adăugând 10.000 la distanța ^ 2 are un efect foarte mic, iar funcția aproximează o funcție pătrată inversă normală.
    • Cu toate acestea, atunci când distanța este mult mai mică de 100 de pixeli, distanța are un efect mic asupra valorii numitorului, iar ecuația devine aproximativ egală cu: vel + = n
  • A doua modificare este adăugarea unei componente laterale la viteza atunci când particulele se apropie suficient de gaura neagră. Aceasta are două scopuri:
    1. Ea face particulele spirală în sensul acelor de ceasornic spre gaura neagră.
    2. Atunci când particulele se apropie destul de mult, vor ajunge la echilibru și vor forma un cerc stralucitor în jurul gaurii negre.

Bacsis: Pentru a roti un vector, V, 90 ° în sensul acelor de ceasornic, luați (V.Y, -V.X). În mod similar, pentru ao roti în sens invers acelor de ceasornic, luați-o (-V.Y, V.X).

Producerea de particule

O gaură neagră va produce două tipuri de particule. În primul rând, va pulveriza periodic particule care vor orbita în jurul acestuia. În al doilea rând, când o gaură neagră este împușcată, acesta va pulveriza particule speciale care nu sunt afectate de gravitatea sa.

Adăugați următorul cod la Blackhole :: WasShot () metodă:

 float hue = fmodf (3.0f / 1000.0f * tTimer :: getTimeMS (), 6); tColor4f culoare = ColorUtil :: HSVToColor (nuanță, 0,25f, 1); const int numParticles = 150; float startOffset = extensii :: nextFloat (0, tMath :: PI * 2.0f / numParticles); pentru (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, culoare, 90, 1.5f, stare); 

Acest lucru funcționează în cea mai mare parte în același mod ca și celelalte explozii de particule. O diferență este că alegem nuanța culorii pe baza duratei totale de joc scurs. Dacă trageți gaura neagră de mai multe ori în succesiune rapidă, veți vedea că nuanța exploziilor se rotește treptat. Acest lucru pare mai puțin murdar decât utilizarea culorilor aleatoare, permițând totuși o variație.

Pentru pulverizarea orbită a particulelor, trebuie să adăugăm o variabilă la Gaură neagră clasa pentru a urmări direcția în care în prezent pulverizăm particule:

 protejate: int mHitPoints; float mSprayAngle; BlackHole :: BlackHole (const tVector2f & poziție): mSprayAngle (0) ...

Acum vom adăuga următoarele la Blackhole :: actualizare () metodă.

 Găurile negre pulverizează particule care orbitează. Pulverizatorul pornește și se oprește la fiecare trimestru al doilea. dacă ((tTimer :: getTimeMS () / 250)% 2 == 0) tVector2f sprayVel = MathUtil :: din polar (mSprayAngle, Extensions :: nextFloat (12, 15)); tColor4f culoare = ColorUtil :: HSVToColor (5, 0.5f, 0.8f); tVector2f pos = mPoziția + 2.0f * tVector2f (sprayVel.y, -sprayVel.x) + Extensii :: nextVector2 (4, 8); Starea particulelor (sprayVel, ParticleState :: kEnemy, 1.0f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, culoare, 190, 1,5f, stat);  // rotiți direcția pulverizării mSprayAngle - = tMath :: PI * 2.0f / 50.0f;

Acest lucru va face gaurile negre să pulverizeze spurturi de particule purpurii care vor forma un inel ce orbitează în jurul gaurii negre, după cum urmează:

Foc de evacuare a navei

Așa cum este impus de legile fizicii geometrice-neon, nava jucătorului se propulsează prin jettarea unui flux de particule de foc în conducta de eșapament. Cu motorul nostru cu particule în loc, acest efect este ușor de realizat și adaugă fler vizual la mișcarea navei.

Pe măsură ce se deplasează nava, creează trei fluxuri de particule: un curs central care se declanșează direct pe spatele navei și două fluxuri laterale ale căror unghiuri se rotesc înapoi și în raport cu nava. Cele două fluxuri laterale se rotesc în direcții opuse pentru a realiza un model de traversare. Fluxurile laterale au o culoare mai roșie, în timp ce fluxul central are o culoare mai caldă, galben-albă. Animația de mai jos arată efectul:


Pentru a face incendiul mai strălucitor, vom avea nava emite particule suplimentare care arată astfel:


Aceste particule vor fi colorate și amestecate cu particulele obișnuite. Codul pentru întregul efect este prezentat mai jos:

 void PlayerShip :: MakeExhaustFire () if (mVelocity.lengthSquared ()> 0.1f) mOrientation = Extensii :: toAngle (mVelocity); float cosA = cosf (mOrientation); flotare sinA = sinf (mOrientation); tMatrix2x2f rot (tVector2f (cosA, sinA), tVector2f (-sinA, cosA)); flotare t = tTimer :: getTimeMS () / 1000.0f; tVector2f baseVel = Extensii :: 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 = mPoziția + rot * tVector2f (-25, 0); poziția țevii de eșapament a navei. const float alfa = 0.7f; // fluxul de particule medii tVector2f velMid = baseVel + Extensii :: nextVector2 (0, 1); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, 1), Particule (velMid, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, midColor * alpha, 60.0f, : kEnemy)); // fluxuri particulare laterale tVector2f vel1 = baseVel + perpVel + extensii :: nextVector2 (0, 0.3f); tVector2f vel2 = baseVel - perpVel + Extensii :: nextVector2 (0, 0.3f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, 1), Particule (vel1, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alpha, 60.0f, 1), ParticleState (vel2, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel1, : kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alpha, 60.0f, tVector2f (0.5f, 1), ParticleState (vel2, : kEnemy)); 

Nu se întâmplă nimic în acest cod. Utilizăm o funcție sinusoidală pentru a produce efectul pivotant în fluxurile laterale prin modificarea vitezei lor lateral în timp. Pentru fiecare flux, creăm două particule care se suprapun pe cadru: unul semi-transparent, alb LineParticle, și o particulă strălucitoare colorată în spatele ei. Apel
MakeExhaustFire () la sfârșitul PlayerShip.Update (), imediat înainte de a seta viteza navei la zero.

Concluzie

Cu toate aceste efecte de particule, Shape Blaster începe să arate destul de rece. În partea finală a acestei serii, vom adăuga încă un efect extraordinar: grila de fond de bază.