Maak een Neon Vector Shooter voor iOS deeltjeseffecten

In deze serie tutorials laat ik je zien hoe je een Geometry Wars-geïnspireerde twin-stick shooter maakt met neon graphics, gekke partikeleffecten en geweldige muziek, voor iOS met C ++ en OpenGL ES 2.0. In dit deel voegen we explosies en visuele flair toe.

Overzicht

In de serie tot nu toe hebben we de gameplay ingesteld en virtuele gamepad-besturingselementen toegevoegd. Vervolgens voegen we deeltjeseffecten toe.


Waarschuwing: Loud!

Effecten van deeltjes worden gecreëerd door een groot aantal kleine deeltjes te maken. Ze zijn zeer veelzijdig en kunnen worden gebruikt om flair toe te voegen aan bijna elk spel. In Shape Blaster zullen we explosies maken met deeltjeseffecten. We zullen ook deeltjeseffecten gebruiken om uitlaatgassen te creëren voor het schip van de speler en om visuele flair toe te voegen aan de zwarte gaten. Bovendien zullen we kijken hoe deeltjes kunnen interageren met de zwaartekracht van de zwarte gaten.

Wijzig om Build vrij te geven voor snelheidsvoordelen

Tot nu toe hebt u waarschijnlijk Shape Blaster gemaakt en gebruikt met alle standaardinstellingen debug bouw van het project. Hoewel dit oké en goed is als u uw code fout opspoort, schakelt debuggen de meeste snelheids- en wiskundige optimalisaties uit die kunnen worden uitgevoerd, en worden alle beweringen in de code ingeschakeld gehouden.

Als u de code vanaf hier in de foutopsporingsmodus uitvoert, ziet u dat de framesnelheid dramatisch daalt. Dit komt doordat we ons richten op een apparaat met een beperkte hoeveelheid RAM, CPU-kloksnelheid en kleinere 3D-hardware in vergelijking met een desktopcomputer of zelfs een laptop.

Dus op dit punt kun je optioneel het debuggen uitschakelen en de "release" -modus inschakelen. Release-modus geeft ons volledige compiler- en wiskundige optimalisatie, evenals het verwijderen van ongebruikte foutopsporingscode en beweringen.

Nadat u het project hebt geopend, kiest u de Artikel menu, schema, dan Bewerk schema ... .


Het volgende dialoogvenster verschijnt. Kiezen Rennen aan de linkerkant van het dialoogvenster en van Configuratie bouwen, verander het pop-upitem van debug naar vrijlating.


U zult de snelheidswinsten onmiddellijk opmerken. Het proces kan eenvoudig worden teruggedraaid als u het programma opnieuw moet debuggen: kies gewoon debug in plaats van vrijlating en je bent klaar.

Tip: Merk echter op dat voor elke schemawijziging zoals deze een volledige hercompilatie van het programma vereist is.

De klasse ParticleManager

We beginnen met het maken van een ParticleManager klasse die alle deeltjes opslaat, bijwerkt en tekent. We zullen deze klasse algemeen genoeg maken zodat deze gemakkelijk in andere projecten kan worden hergebruikt, maar nog steeds enige aanpassingen van project tot project vereisen. Om de ParticleManager zo algemeen mogelijk is het niet verantwoordelijk voor hoe de deeltjes er uitzien of bewegen; we zullen dat elders aan.

Deeltjes worden meestal snel en in grote aantallen gemaakt en vernietigd. We zullen een objectpool gebruiken om te voorkomen dat grote hoeveelheden afval ontstaan. Dit betekent dat we een groot aantal deeltjes vooraan zullen toewijzen en deze dezelfde deeltjes opnieuw zullen gebruiken.

We zullen ook maken ParticleManager hebben een vaste capaciteit. Dit zal het vereenvoudigen en ervoor zorgen dat onze prestaties of geheugenbeperkingen niet worden overschreden door te veel deeltjes te maken. Wanneer het maximale aantal deeltjes wordt overschreden, zullen we beginnen met het vervangen van de oudste deeltjes door nieuwe. We zullen het maken ParticleManager een generieke klasse. Hierdoor kunnen we aangepaste statusinformatie opslaan voor de deeltjes zonder deze hard te coderen in de
ParticleManager zelf.

We zullen ook een maken Deeltje klasse:

 class Particle public: ParticleState mState; tColor4f mColor; tVector2f mPosition; tVector2f mScale; tTextuur * mTexture; zweefmOriëntatie; zwevende mDuratie; zweven mPercentLife; publiek: Particle (): mScale (1,1), mPercentLife (1.0f) ;

De Deeltje klasse heeft alle informatie die nodig is om een ​​deeltje weer te geven en de levensduur te beheren. ParticleState is er om aanvullende gegevens te bewaren die we nodig hebben voor onze deeltjes. Welke gegevens nodig zijn, hangt af van de gewenste deeltjeseffecten; het kan worden gebruikt om snelheid, versnelling, rotatiesnelheid of iets anders op te slaan.

Om de deeltjes te helpen beheren, hebben we een klasse nodig die fungeert als een cirkelvormige array, wat betekent dat indexen die normaal gesproken geen grenzen overschrijden, zich in plaats daarvan naar het begin van de array zullen verplaatsen. Dit maakt het gemakkelijk om de oudste deeltjes eerst te vervangen als er onvoldoende ruimte is voor nieuwe deeltjes in onze array. Hiervoor voegen we het volgende als een geneste klasse toe in ParticleManager:

 class CircularParticleArray protected: std :: vector mList; size_t mStart; size_t mCount; publiek: CircularParticleArray (int capaciteit) mList.hresize ((size_t) capaciteit);  size_t getStart () return mStart;  void setStart (size_t value) mStart = waarde% mList.size ();  size_t getCount () return mCount;  void setCount (size_t value) mCount = value;  size_t getCapacity () return mList.size ();  Deeltje en operator [] (const size_t i) return mList [(mStart + i)% mList.size ()];  const Deeltje en operator [] (const size_t i) const return mlist [(mStart + i)% mList.size ()]; ;

We kunnen de mSTART lid om aan te passen waar index nul in onze CircularParticleArray komt overeen met in de onderliggende array, en mCount wordt gebruikt om bij te houden hoeveel actieve deeltjes in de lijst voorkomen. We zullen ervoor zorgen dat het deeltje op index nul altijd het oudste deeltje is. Als we het oudste deeltje vervangen door een nieuw deeltje, gaan we gewoon verder mSTART, die in wezen de ronde matrix roteert.

Nu we onze helperlessen hebben, kunnen we beginnen met het invullen van de ParticleManager klasse. We hebben een nieuwe ledvariabele en een constructor nodig.

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

We creëren mParticleList en vul het met lege deeltjes. De constructeur is de enige plaats waar de ParticleManager wijst geheugen toe.

Vervolgens voegen we de createParticle () methode, die een nieuw deeltje maakt met het volgende ongebruikte deeltje in de pool, of het oudste deeltje als er geen ongebruikte deeltjes zijn.

 void ParticleManager :: createParticle (tTexture * texture, const tVector2f & position, const tColor4f & tint, float duration, 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 = m ParticleList [index]; ref.mTexture = textuur; ref.mPosition = positie; ref.mColor = tint; ref.mDuration = duur; ref.mPercentLife = 1.0f; ref.mScale = schaal; ref.mOrientation = theta; ref.mState = state; 

Deeltjes kunnen op elk moment worden vernietigd. We moeten deze deeltjes verwijderen en ervoor zorgen dat de andere deeltjes in dezelfde volgorde blijven. We kunnen dit doen door de lijst met deeltjes te herhalen, terwijl we bijhouden hoeveel er zijn vernietigd. Terwijl we gaan, verplaatsen we elk actief deeltje voor alle vernietigde deeltjes door het te verwisselen met het eerste vernietigde deeltje. Zodra alle vernietigde deeltjes aan het einde van de lijst staan, deactiveren we ze door de lijst in te stellen mCount veranderlijk naar het aantal actieve deeltjes. Vernietigde deeltjes zullen in de onderliggende array blijven, maar zullen niet worden bijgewerkt of getekend.

ParticleManager :: update () verwerkt elk deeltje en verwijdert vernietigde deeltjes uit de lijst:

 void ParticleManager :: update () size_t removalCount = 0; for (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; 

Het laatste ding om in te implementeren ParticleManager trekt de deeltjes:

 void ParticleManager :: draw (tSpriteBatch * spriteBatch) for (size_t i = 0; i < mParticleList.getCount(); i++)  Particle particle = mParticleList[(size_t)i]; tPoint2f origin = particle.mTexture->getSurfaceSize () / 2; spriteBatch-> draw (2, particle.mTexture, tPoint2f ((int) particle.mPosition.x, (int) particle.mPosition.y), tOptional(), particle.mColor, particle.mOrientation, origin, particle.mScale); 

De klasse ParticleState

Het volgende dat u moet doen, is het maken van een aangepaste klasse of struct om aan te passen hoe de deeltjes eruit zullen zien in Shape Blaster. Er zijn verschillende soorten deeltjes in Shape Blaster die zich enigszins anders gedragen, dus we beginnen met het maken van een enum voor het deeltjestype. We hebben ook variabelen nodig voor de snelheid van het deeltje en de beginlengte.

 class ParticleState public: enum ParticleType kNone = 0, kEnemy, kBullet, kIgnoreGravity; publiek: tVector2f mVelocity; ParticleType mType; float mlengthMultiplier; publiek: ParticleState (); ParticleState (const tVector2f & velocity, ParticleType type, float lengthMultiplier = 1.0f); ParticleState getRandom (float minVel, float maxVel); void update Particle (Particle & particle); ;

Nu zijn we klaar om de deeltjes te schrijven bijwerken() methode. Het is een goed idee om deze methode snel te maken, omdat er mogelijk een groot aantal deeltjes moet worden gebruikt.

We beginnen eenvoudig. Laten we de volgende methode toevoegen aan ParticleState:

 void ParticleState :: updateParticle (Particle & particle) tVector2f vel = particle.mState.mVelocity; particle.mPosition + = vel; particle.mOrientation = Extensies :: toAngle (vel); // gedenormaliseerde drijvers veroorzaken significante prestatieproblemen als (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

We komen terug en verbeteren deze methode in een oogwenk. Laten we eerst een aantal partikeleffecten maken, zodat we onze wijzigingen daadwerkelijk kunnen uittesten.

Vijandelijke explosies

In GameRoot, een nieuwe verklaren ParticleManager en noem het bijwerken() en trek() methoden:

 // in GameRoot beschermd: ParticleManager mParticleManager; publiek: ParticleManager * getParticleManager () return & mParticleManager;  // in GameRoot's constructor GameRoot :: GameRoot (): mParticleManager (1024 * 20), mViewportSize (800, 600), mSpriteBatch (NULL)  // in GameRoot :: onRedrawView () mParticleManager.update (); mParticleManager.draw (mSpriteBatch);

We zullen ook een nieuw exemplaar van de tTexture klasse in de Kunst klas genoemd mLineParticle voor de textuur van het deeltje. We laden het zoals we de sprites van de andere game doen:

 // In Art's constructor mLineParticle = new tTexture (tSurface ("laser.png"));

Laten we nu vijanden laten ontploffen. We zullen de Enemy :: wasShot () methode als volgt:

 void Enemy :: wasShot () mIsExpired = true; voor (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); 

Dit creëert 120 deeltjes die naar buiten schieten met verschillende snelheden in alle richtingen. De willekeurige snelheid wordt zodanig gewogen dat deeltjes eerder in de buurt van de maximumsnelheid bewegen. Dit zorgt ervoor dat er meer deeltjes aan de rand van de explosie komen als deze uitzet. De deeltjes duren 190 frames, of iets meer dan drie seconden.

Je kunt nu het spel uitvoeren en vijanden zien exploderen. Er zijn echter nog steeds enkele verbeteringen aan te brengen voor de deeltjeseffecten.

Het eerste probleem is dat de deeltjes abrupt verdwijnen zodra de duur ervan op is. Het zou leuker zijn als ze vloeiend zouden verdwijnen, maar laten we een beetje verder gaan en de deeltjes helderder laten gloeien als ze snel bewegen. Ook ziet het er goed uit als we snel bewegende deeltjes verlengen en langzaam bewegende deeltjes verkorten.

Wijzig de ParticleState.UpdateParticle () methode als volgt (wijzigingen worden gemarkeerd).

 void ParticleState :: updateParticle (Particle & particle) tVector2f vel = particle.mState.mVelocity; particle.mPosition + = vel; particle.mOrientation = Extensies :: toAngle (vel); drijfsnelheid = vel.lengte (); zweven alpha = tMath :: min (1.0f, tMath :: min (particle.mPercentLife * 2, speed * 1.0f)); alpha * = alpha; particle.mColor.a = alpha; particle.mScale.x = particle.mState.mLengthMultiplier * tMath :: min (tMath :: min (1.0f, 0.2f * speed + 0.1f), alpha); // gedenormaliseerde drijvers veroorzaken significante prestatieproblemen als (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

De explosies zien er nu veel beter uit, maar ze hebben allemaal dezelfde kleur.

Monochromatische explosies zijn een goed begin, maar kunnen we het beter doen?

We kunnen ze meer afwisseling bieden door willekeurige kleuren te kiezen. Een methode om willekeurige kleuren te produceren, is door willekeurig de rode, blauwe en groene componenten te kiezen, maar dit levert veel saaie kleuren op en we willen dat onze deeltjes een neonlicht-uiterlijk hebben. We kunnen meer controle krijgen over onze kleuren door ze op te geven in de HSV-kleurenruimte. HSV staat voor tint, verzadiging en waarde. We willen kleuren kiezen met een willekeurige tint, maar een vaste verzadiging en waarde. We hebben een hulpfunctie nodig die een kleur uit HSV-waarden kan produceren.

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

Nu kunnen we wijzigen Enemy :: wasShot () om willekeurige kleuren te gebruiken. Om de explosiekleur minder monotoon te maken, kiezen we voor elke explosie twee nabijgelegen sleutelkleuren en lineair interpoleren we ertussen met een willekeurige hoeveelheid voor elk deeltje:

 void Enemy :: wasShot () mIsExpired = true; float hue1 = Extensies :: nextFloat (0, 6); floate 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); voor (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); 

De explosies zouden op de onderstaande animatie moeten lijken:


Je kunt spelen met de kleurgeneratie om aan je voorkeuren te voldoen. Een alternatieve techniek die goed werkt, is door een aantal kleurpatronen met de hand te plukken voor explosies en willekeurig te kiezen uit uw vooraf gekozen kleurenschema's.

Bullet Explosions

We kunnen ook de kogels laten ontploffen wanneer ze de rand van het scherm bereiken. We zullen in wezen hetzelfde doen als vijandelijke explosies.

Laten we aanpassen Bullet :: update () als volgt:

 if (! tRectf (0, 0, GameRoot :: getInstance () -> getViewportSize ()). bevat (tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y))) mIsExpired = true; voor (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)); 

Je zult misschien opmerken dat het een verspilling is om de deeltjes een willekeurige richting te geven, omdat minstens de helft van de deeltjes direct van het scherm af zal gaan (meer als de kogel in een hoek explodeert). We zouden wat extra werk kunnen doen om ervoor te zorgen dat deeltjes alleen snelheden krijgen tegenover de muur waarmee ze worden geconfronteerd. In plaats daarvan nemen we echter een signaal uit Geometry Wars en laten alle deeltjes van de muren afketsen, zodat alle deeltjes die buiten het scherm worden geplaatst, worden teruggestuurd.

Voeg de volgende regels toe aan ParticleState.UpdateParticle () ergens tussen de eerste en laatste regels:

 tVector2f pos = particle.mPosition; int width = (int) GameRoot :: getInstance () -> getViewportSize (). width; int height = (int) GameRoot :: getInstance () -> getViewportSize (). height; // botst tegen de randen van het scherm als (pos.x. < 0)  vel.x = (float)fabs(vel.x);  else if (pos.x > breedte) vel.x = (float) -fabs (vel.x);  if (pos.y < 0)  vel.y = (float)fabs(vel.y);  else if (pos.y > hoogte) vel.y = (float) -fabs (vel.y); 

Scheepsexplosie van een speler

We maken een hele grote explosie wanneer de speler wordt gedood. Wijzigen PlayerShip :: kill () zoals zo:

 void PlayerShip :: kill () PlayerStatus :: getInstance () -> removeLife (); mFramesUntilRespawn = PlayerStatus :: getInstance () -> getIsGameOver ()? 300: 120; tColor4f explosionColor = tColor4f (0.8f, 0.8f, 0.4f, 1.0f); voor (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); 

Dit is vergelijkbaar met de vijandelijke explosies, maar we gebruiken meer deeltjes en gebruiken altijd hetzelfde kleurenschema. Het deeltjestype is ook ingesteld op ParticleState :: kNone.

In de demo vertragen deeltjes van vijandige explosies sneller dan deeltjes van het schip van de speler exploderen. Dit zorgt ervoor dat de explosie van de speler iets langer duurt en een beetje epischer lijkt.

Black Holes Revisited

Nu we partikelingseffecten hebben, laten we de zwarte gaten opnieuw bekijken en ze laten interageren met deeltjes.

Effect op deeltjes

Zwarte gaten moeten behalve andere entiteiten ook invloed hebben op deeltjes, dus we moeten dit aanpassen ParticleState :: updateParticle (). Laten we de volgende regels toevoegen:

 if (particle.mState.mType! = kIgnoreGravity) for (std :: lijst:: iterator j = EntityManager :: getInstance () -> mBlackHoles.begin (); j! = EntityManager :: getInstance () -> mBlackHoles.end (); j ++) tVector2f dPos = (* j) -> getPosition () - pos; vlotterafstand = dPos.length (); tVector2f n = dPos / afstand; vel + = 10000.0f * n / (afstand * afstand + 10000.0f); // voeg tangentiële versnelling toe voor nabijgelegen deeltjes als (afstand < 400)  vel += 45.0f * tVector2f(n.y, -n.x) / (distance + 100.0f);   

Hier, n is de eenheidsvector die naar het zwarte gat wijst. De aantrekkingskracht is een aangepaste versie van de inverse vierkante functie:

  • De eerste wijziging is die van de noemer afstand ^ 2 + 10.000; dit zorgt ervoor dat de aantrekkende kracht een maximale waarde nadert in plaats van naar oneindig te neigen naarmate de afstand erg klein wordt.
    • Wanneer de afstand veel groter is dan 100 pixels, Afstand ^ 2 wordt veel groter dan 10.000. Daarom 10.000 toe te voegen aan Afstand ^ 2 heeft een heel klein effect en de functie benadert een normale inverse vierkante functie.
    • Wanneer de afstand echter veel kleiner is dan 100 pixels, heeft de afstand een klein effect op de waarde van de noemer en wordt de vergelijking ongeveer gelijk aan: vel + = n
  • De tweede wijziging is het toevoegen van een zijwaartse component aan de snelheid wanneer de deeltjes dicht genoeg bij het zwarte gat komen. Dit heeft twee doelen:
    1. Het maakt de deeltjes spiraalvormig met de klok mee in de richting van het zwarte gat.
    2. Wanneer de deeltjes dicht genoeg bij elkaar komen, zullen ze een evenwicht bereiken en een gloeiende cirkel rondom het zwarte gat vormen.

Tip: Een vector roteren, V, 90 ° met de klok mee, nemen (V.Y, -V.X). Evenzo, nemen om het 90 ° tegen de klok in te draaien (-V.Y, V.X).

Deeltjes produceren

Een zwart gat zal twee soorten deeltjes produceren. Ten eerste zal het periodiek deeltjes opsporen die eromheen draaien. Ten tweede, wanneer een zwart gat wordt geschoten, worden speciale deeltjes uitgespuwd die niet worden beïnvloed door de zwaartekracht.

Voeg de volgende code toe aan de BlackHole :: WasShot () methode:

 float hue = fmodf (3.0f / 1000.0f * tTimer :: getTimeMS (), 6); tColor4f color = ColorUtil :: HSVToColor (tint, 0.25f, 1); const int numParticles = 150; float startOffset = Extensions :: nextFloat (0, tMath :: PI * 2.0f / numParticles); voor (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); 

Dit werkt grotendeels op dezelfde manier als de andere deeltjesexplosies. Een verschil is dat we de tint van de kleur kiezen op basis van de totale verstreken speeltijd. Als je het zwarte gat meerdere keren snel na elkaar schiet, zie je de tint van de explosies geleidelijk roteren. Dit lijkt minder rommelig dan het gebruik van willekeurige kleuren, terwijl er nog steeds variatie mogelijk is.

Voor de ronddraaiende deeltjesnevel moeten we een variabele toevoegen aan de BlackHole klasse om de richting te volgen waarin we deeltjes spuiten:

 beschermd: int mHitPoints; zweven mSprayAngle; BlackHole :: BlackHole (const tVector2f & position): mSprayAngle (0) ...

Nu voegen we het volgende toe aan de BlackHole :: update () methode.

 // De zwarte gaten sproeien enkele ronddraaiende deeltjes. De spray schakelt elke kwart seconde aan en uit. 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) + Extensies :: nextVector2 (4, 8); ParticleState state (sprayVel, ParticleState :: kEnemy, 1.0f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, color, 190, 1.5f, state);  // roteer de spuitrichting mSprayAngle - = tMath :: PI * 2.0f / 50.0f;

Hierdoor zullen de zwarte gaten spatten van paarse deeltjes sproeien die een ring vormen die rond het zwarte gat draait, zoals:

Uitlaatbrand van het schip

Zoals voorgeschreven door de wetten van de geometrisch-neon fysica, stuwt het schip van de speler zichzelf door een stroom vurige deeltjes uit zijn uitlaatpijp te spuiten. Met onze deeltjesmotor op zijn plaats, is dit effect eenvoudig te maken en voegt het visuele flair toe aan de bewegingen van het schip.

Terwijl het schip beweegt, creëren we drie stromen deeltjes: een centrale stroom die recht uit de achterkant van het schip vuurt, en twee zijstromen waarvan de hoeken heen en weer zwenken ten opzichte van het schip. De twee zijstromen draaien in tegengestelde richtingen om een ​​kriskras patroon te maken. De zijstromen hebben een rodere kleur, terwijl de middenstroom een ​​heter, geel-witte kleur heeft. De onderstaande animatie toont het effect:


Om het vuur feller te laten gloeien, laten we het schip extra deeltjes uitstoten die er als volgt uitzien:


Deze deeltjes worden getint en gemengd met de reguliere deeltjes. De code voor het volledige effect wordt hieronder getoond:

 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)); zweven t = tTimer :: getTimeMS () / 1000.0f; tVector2f baseVel = Verlengingen :: 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); // positie van de uitlaatpijp van het schip. const float alpha = 0.7f; // middendeeltjesstroom 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)); // side particle streams 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)); 

Er is niets sneaky aan de hand in deze code. We gebruiken een sinusfunctie om het zwenkeffect in de zijstromen te produceren door hun zijwaartse snelheid in de loop van de tijd te variëren. Voor elke stream maken we twee overlappende deeltjes per frame: een semi-transparant, wit LineParticle, en een gekleurd glimdeeltje erachter. telefoontje
MakeExhaustFire () aan het einde van PlayerShip.Update (), onmiddellijk voordat de snelheid van het schip op nul wordt gezet.

Conclusie

Met al deze deeltjeseffecten begint Shape Blaster er behoorlijk gaaf uit te zien. In het laatste deel van deze serie voegen we nog een geweldig effect toe: het kromme achtergrondraster.