Gör en Neon Vector Shooter för IOS Partikel Effekter

I den här serien av handledningar visar jag dig hur man gör en Geometry Wars-inspirerad tvillingstickskytte med neongrafik, galen partikeleffekter och fantastisk musik, för iOS med C ++ och OpenGL ES 2.0. I denna del lägger vi till explosioner och visuell känsla.

Översikt

I serien hittills har vi satt upp gameplayen och lagt till virtuella gamepadkontroller. Därefter lägger vi till partikeleffekter.


Varning: Högt!

Partikeleffekter skapas genom att göra ett stort antal små partiklar. De är mycket mångsidiga och kan användas för att lägga till känsla för nästan vilket spel som helst. I Shape Blaster kommer vi att göra explosioner med hjälp av partikel effekter. Vi kommer också att använda partikeleffekter för att skapa avgasbrand för spelarens skepp och för att lägga till visuell känsla till de svarta hålen. Dessutom tittar vi på hur man får partiklar att interagera med gravitationen från de svarta hålen.

Byt till Släpp byggnader för snabb vinst

Fram till nu har du förmodligen byggt och kör Shape Blaster med alla standardinställningar debug bygga av projektet. Medan det här är okej och bra när du felsöker din kod, avaktiverar felsökning de flesta hastigheter och matteoptimeringar som kan göras, samt att du aktiverar alla påståenden i koden.

Faktum är att om du kör koden i debug-läge härifrån märker du att bildhastigheten börjar minska dramatiskt. Detta beror på att vi inriktar oss på en enhet som har en minskad mängd RAM, CPU-klockhastighet och mindre 3D-hårdvara jämfört med en stationär dator eller till och med en bärbar dator.

Så vid denna tidpunkt kan du valfritt stänga av felsökning och sätt på "release" -läget. Utlösningsläge ger oss fullständig kompilator och matteoptimering, såväl som att ta bort oanvänd debugging-kod och påståenden.

När du har öppnat projektet väljer du Produkt meny, Schema, sedan Redigera schema ... .


Följande dialogruta öppnas. Välja Springa på vänster sida av dialogrutan och från Bygg konfiguration, ändra popup-objektet från debug till släpp.


Du kommer att märka hastighetsvinsterna omedelbart. Processen går lätt om du behöver felsöka programmet igen: Välj bara debug istället för släpp och du är klar.

Tips: Observera att alla systemändringar som detta kräver en fullständig återställning av programmet.

ParticleManager Class

Vi börjar med att skapa en ParticleManager klass som kommer att lagra, uppdatera och rita alla partiklar. Vi gör den här klassen generellt nog så att den lätt kan återanvändas i andra projekt, men kommer fortfarande att behöva vissa anpassningar från projekt till projekt. För att hålla ParticleManager så generellt som möjligt kommer det inte att vara ansvarigt för hur partiklarna ser ut eller rör sig; Vi hanterar det på annat håll.

Partiklar tenderar att skapas och förstöras snabbt och i stort antal. Vi kommer att använda en objektpool för att undvika att skapa stora mängder skräp. Det innebär att vi kommer att fördela ett stort antal partiklar uppe och sedan fortsätta att återanvända samma partiklar.

Vi kommer också att göra ParticleManager ha en fast kapacitet. Detta kommer att förenkla det och hjälpa till att vi inte överskrider våra prestanda eller minnesbegränsningar genom att skapa för många partiklar. När det maximala antalet partiklar överskrids börjar vi ersätta de äldsta partiklarna med nya. Vi ska göra ParticleManager en generisk klass. Detta gör det möjligt för oss att lagra anpassad statlig information för partiklarna utan att det är svårt att koda det i
ParticleManager sig.

Vi skapar också en Partikel klass:

 Klasspartikel offentligt: ​​ParticleState mState; tColor4f mColor; tVector2f mPosition; tVector2f mScale; tTexture * mTexture; float mOrientation; float mDuration; float mPercentLife; allmänhet: Partikel (): mScale (1,1), mPercentLife (1.0f) ;

De Partikel klassen har all information som krävs för att visa en partikel och hantera sin livstid. ParticleState finns det för att hålla några ytterligare data vi kan behöva för våra partiklar. Vilka data som behövs varierar beroende på de önskade partikelffekterna. Det kan användas för att lagra hastighet, acceleration, rotationshastighet eller allt annat du behöver.

För att hjälpa till med hanteringen av partiklarna behöver vi en klass som fungerar som en cirkulär matris, vilket innebär att index som normalt skulle vara obegränsade kommer istället att vikas runt till början av matrisen. Detta kommer att göra det enkelt att ersätta de äldsta partiklarna först om vi går tom för utrymme för nya partiklar i vår matris. För detta lägger vi till följande som en kapslad klass i ParticleManager:

 klass CircularParticleArray skyddad: std :: vektor mList; size_t mStart; size_t mCount; allmänhet: CircularParticleArray (int kapacitet) mList.resize ((size_t) kapacitet);  size_t getStart () returnera mStart;  void setStart (size_t värde) mStart = värde% mList.size ();  size_t getCount () returnera mCount;  void setCount (size_t värde) mCount = value;  size_t getCapacity () return mList.size ();  Partikel och operatör [] (const size_t i) return mList [(mStart + i)% mList.size ()];  const Partikel och operatör [] (const size_t i) const return mList [(mStart + i)% mList.size ()]; ;

Vi kan ställa in mStart medlem för att justera var index noll i vår CircularParticleArray motsvarar i den underliggande matrisen, och mCount kommer att användas för att spåra hur många aktiva partiklar som finns i listan. Vi kommer att se till att partikeln vid index noll alltid är den äldsta partikeln. Om vi ​​ersätter den äldsta partikeln med en ny, kommer vi bara att öka mStart, vilken väsentligen roterar den cirkulära gruppen.

Nu när vi har våra hjälparklasser, kan vi börja fylla i ParticleManager klass. Vi behöver en ny medlemsvariabel och en konstruktör.

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

Vi skapar mParticleList och fyll det med tomma partiklar. Konstruktören är den enda platsen där ParticleManager allokerar minne.

Därefter lägger vi till createParticle () metod som skapar en ny partikel med hjälp av nästa oanvända partikel i poolen eller den äldsta partikeln om det inte finns några outnyttjade partiklar.

 void ParticleManager :: createParticle (tTexture * textur, const tVector2f & position, const tColor4f & tint, float duration, const tVector2f & skala, const ParticleState & state, float theta) size_t index; om (mParticleList.getCount () == mParticleList.getCapacity ()) index = 0; mParticleList.setStart (mParticleList.getStart () + 1);  annat index = mParticleList.getCount (); mParticleList.setCount (mParticleList.getCount () + 1);  Partikel & ref = mParticleList [index]; ref.mTexture = textur; ref.mPosition = position; ref.mColor = nyans; ref.mDuration = duration; ref.mPercentLife = 1.0f; ref.mScale = skala; ref.mOrientation = theta; ref.mState = state; 

Partiklar kan förstöras när som helst. Vi måste ta bort dessa partiklar samtidigt som de andra partiklarna förblir i samma ordning. Vi kan göra detta genom att iterera genom partikellistan och hålla reda på hur många har förstörts. När vi går flyttar vi varje aktiv partikel framför alla förstörda partiklar genom att byta den med den först förstörda partikeln. När alla förstörda partiklarna är i slutet av listan avaktiverar vi dem genom att ställa in listan mCount variabel till antalet aktiva partiklar. Förstörda partiklar kommer att förbli i den underliggande matrisen, men kommer inte att uppdateras eller dras.

ParticleManager :: uppdatering () hanterar uppdatering av varje partikel och avlägsnande av förstörda partiklar från listan:

 void ParticleManager :: uppdatering () size_t removalCount = 0; för (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; 

Den sista saken att genomföra i ParticleManager ritar partiklarna:

 void ParticleManager :: draw (tSpriteBatch * spriteBatch) för (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) partikel.mPosition.x, (int) partikel.mPosition.y), tOptional(), partikel.mKolor, partikel.m.Organisering, ursprung, partikel.mSkala); 

ParticleState Class

Nästa sak att göra är att skapa en anpassad klass eller struktur för att anpassa hur partiklarna kommer att se i Shape Blaster. Det kommer att finnas flera olika typer av partiklar i Shape Blaster som beter sig lite annorlunda, så vi börjar med att skapa en enum för partikeltypen. Vi behöver också variabler för partikelns hastighet och initial längd.

 klass ParticleState public: enum ParticleType kNone = 0, kEnemy, kBullet, kIgnoreGravity; allmänhet: tVector2f mVelocity; PartikelTyp mType; float mLengthMultiplier; allmänhet: ParticleState (); ParticleState (const tVector2f & hastighet, ParticleType typ, float lengthMultiplier = 1.0f); ParticleState getRandom (float minVel, float maxVel); void updateParticle (Particle & particle); ;

Nu är vi redo att skriva partikelns uppdatering() metod. Det är en bra idé att göra den här metoden snabb, eftersom det kan behöva krävas ett stort antal partiklar.

Vi börjar enkelt. Låt oss lägga till följande metod till ParticleState:

 void ParticleState :: updateParticle (Particle & particle) tVector2f vel = partikel.mState.mVelocity; partikel.mPosition + = vel; partikel.mOrientation = Extensions :: toAngle (vel); // denormaliserade flottören orsakar betydande prestanda problem om (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Vi kommer tillbaka och förbättrar den här metoden på ett ögonblick. Låt oss först skapa några partikeleffekter så att vi faktiskt kan testa våra förändringar.

Enemy Explosions

I GameRoot, förklara en ny ParticleManager och ringa dess uppdatering() och dra() metoder:

 // i GameRoot skyddad: ParticleManager mParticleManager; allmän: ParticleManager * getParticleManager () return & mParticleManager;  // i GameRoots konstruktör GameRoot :: GameRoot (): mParticleManager (1024 * 20), mViewportSize (800, 600), mSpriteBatch (NULL)  // i GameRoot :: onRedrawView () mParticleManager.update (); mParticleManager.draw (mSpriteBatch);

Vi kommer också att förklara en ny instans av tTexture klass i Konst klass kallas mLineParticle för partikelns konsistens. Vi laddar det som om vi gör det andra spelet sprites:

 // I konstruktörens mlinParticle = ny tTexture (tSurface ("laser.png"));

Låt oss nu göra fiender explodera. Vi ändrar Fiende :: wasShot () metod enligt följande:

 void Enemy :: wasShot () mIsExpired = true; för (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, färg, 190, 1.5f, state); 

Detta skapar 120 partiklar som kommer att skjuta utåt med olika hastigheter i alla riktningar. Slumphastigheten viktas så att partiklar är mer benägna att röra sig nära maxhastigheten. Detta kommer att leda till att fler partiklar befinner sig vid kanten av explosionen när den expanderar. Partiklarna varar 190 bilder, eller drygt tre sekunder.

Du kan nu köra spelet och titta på fiender explodera. Det finns emellertid fortfarande några förbättringar som görs för partikeleffekterna.

Det första problemet är att partiklarna försvinna plötsligt när deras varaktighet löper ut. Det skulle vara trevligare om de kunde smidigt blekna ut, men låt oss gå lite längre än detta och göra partiklarna ljusare när de rör sig snabbt. Det ser också trevligt ut om vi förlänger snabbrörliga partiklar och förkortar långsamma rörelser.

Ändra ParticleState.UpdateParticle () metod enligt följande (ändringar är markerade).

 void ParticleState :: updateParticle (Particle & particle) tVector2f vel = partikel.mState.mVelocity; partikel.mPosition + = vel; partikel.mOrientation = Extensions :: toAngle (vel); float speed = vel.length (); float alfa = tMath :: min (1.0f, tMath :: min (partikel.mPercentLife * 2, hastighet * 1.0f)); alfa * = alfa; partikel.m.Color.a = alfa; partikel.mScale.x = partikel.mState.mLengthMultiplier * tMath :: min (tMath :: min (1.0f, 0.2f * hastighet + 0.1f), alfa); // denormaliserade flottören orsakar betydande prestanda problem om (fabs (vel.x) + fabs (vel.y) < 0.00000000001f)  vel = tVector2f(0,0);  vel *= 0.97f; // Particles gradually slow down particle.mState.mVelocity = vel; 

Explosionerna ser mycket bättre ut nu, men de har samma färg.

Monokromatiska explosioner är en bra start, men kan vi göra det bättre?

Vi kan ge dem mer variation genom att välja slumpmässiga färger. Ett sätt att producera slumpmässiga färger är att välja de röda, blåa och gröna komponenterna slumpmässigt, men det kommer att producera en massa tråkiga färger och vi skulle vilja att våra partiklar har ett neonljusutseende. Vi kan ha mer kontroll över våra färger genom att specificera dem i HSV-färgutrymmet. HSV står för nyans, mättnad och värde. Vi skulle vilja välja färger med en slumpmässig nyans men en fast mättnad och värde. Vi behöver en hjälparfunktion som kan producera en färg från HSV-värden.

 tColor4f ColorUtil :: HSVToColor (float h, float s, float v) if (h == 0 && s == 0) returnera tColor4f (v, v, v, 1.0f);  float c = s * v; float x = c * (1 - abs (int32_t (h)% 2 - 1)); float m = v-c; om (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 kan vi ändra Fiende :: wasShot () att använda slumpmässiga färger. För att göra explosionsfärgen mindre monotont kommer vi att välja två närliggande nyckelfärger för varje explosion och linjärt interpolera mellan dem med en slumpmässig mängd för varje partikel:

 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 (färgton, 0,5f, 1); tColor4f color2 = ColorUtil :: HSVToColor (hue2, 0,5f, 1); för (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, färg, 190, 1.5f, state); 

Explosionerna ska se ut som animeringen nedan:


Du kan leka med färggenerering som passar dina önskemål. En alternativ teknik som fungerar bra är att hämta ett antal färgmönster för explosioner och välj slumpmässigt från dina förinställda färgscheman.

Bullet Explosions

Vi kan också få kulorna att explodera när de når skärmens kant. Vi gör i stort sett samma sak som vi gjorde för fiendens explosioner.

Låt oss ändra Bullet :: update () som följer:

 Om (! tRectf (0, 0, GameRoot :: getInstance () -> getViewportSize ()) innehåller (tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y))) mIsExpired = true; för (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)); 

Du kanske märker att partiklarna är slumpmässiga, eftersom åtminstone hälften av partiklarna omedelbart kommer från skärmen (mer om kulan exploderar i ett hörn). Vi kan göra lite extra arbete för att säkerställa att partiklar endast ges hastigheter motsatt väggen de står inför. Istället kommer vi dock att ta en signal från Geometry Wars och få alla partiklar att studsa av väggarna, så några partiklar som går utanför skärmen kommer studsas tillbaka.

Lägg till följande rader till ParticleState.UpdateParticle () var som helst mellan de första och sista linjerna:

 tVector2f pos = partikel.mPosition; int bredd = (int) GameRoot :: getInstance () -> getViewportSize (). bredd; int height = (int) GameRoot :: getInstance () -> getViewportSize (). höjd; // kollidera med skärmens kanter om (pos.x < 0)  vel.x = (float)fabs(vel.x);  else if (pos.x > bredd) vel.x = (float) -fabs (vel.x);  om (pos.y < 0)  vel.y = (float)fabs(vel.y);  else if (pos.y > höjd) vel.y = (float) -fabs (vel.y); 

Spelarens fartygs explosion

Vi gör en riktigt stor explosion när spelaren dödas. Ändra PlayerShip :: kill () såhär:

 void PlayerShip :: kill () PlayerStatus :: getInstance () -> removeLife (); mFramesUntilRespawn = PlayerStatus :: getInstance () -> getIsGameOver ()? 300: 120; tColor4f explosionColor = tColor4f (0,8f, 0,8f, 0,4f, 1,0f); för (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, färg, 190, 1.5f, state); 

Detta liknar fiendens explosioner, men vi använder fler partiklar och använder alltid samma färgschema. Partikeltypen är också inställd på ParticleState :: kNone.

I demonstrationen saktar partiklar från fiendens explosioner sig snabbare än partiklar från spelarens skepp som exploderar. Detta gör spelarens explosion sist lite längre och ser lite mer episk ut.

Black Holes Revisited

Nu när vi har partikeleffekter, låt oss se över de svarta hålen och få dem att interagera med partiklar.

Effekt på partiklar

Svarta hål ska påverka partiklar utöver andra enheter, så vi behöver ändra ParticleState :: updateParticle (). Låt oss lägga till följande rader:

 om (partikel.mState.mType! = kIgnoreGravity) för (std :: list:: iterator j = EntityManager :: getInstance () -> mBlackHoles.begin (); j! = EntityManager :: getInstance () -> mBlackHoles.end (); j ++) tVector2f dPos = (* j) -> getPosition () - pos; float distance = dPos.length (); tVector2f n = dPos / avstånd; vel + = 10000.0f * n / (avstånd * avstånd + 10000,0f); // lägga tangentiell acceleration för närliggande partiklar om (avstånd < 400)  vel += 45.0f * tVector2f(n.y, -n.x) / (distance + 100.0f);   

Här, n är enhetsvektorn som pekar mot det svarta hålet. Den attraktiva kraften är en modifierad version av den inverse kvadratfunktionen:

  • Den första ändringen är att nämnaren är avstånd ^ 2 + 10 000; Detta medför att den attraktiva kraften närmar sig ett maximivärde istället för att sträcka sig mot oändlighet då avståndet blir mycket litet.
    • När avståndet är mycket större än 100 pixlar, avståndet ^ 2 blir mycket större än 10 000. Därför lägger 10 000 till avståndet ^ 2 har en mycket liten effekt, och funktionen approximerar en normal invers kvadratfunktion.
    • Men när avståndet är mycket mindre än 100 pixlar har avståndet en liten effekt på nämnarens värde och ekvationen blir ungefär lika med: vel + = n
  • Den andra modifieringen lägger till en sidoväggen komponent till hastigheten när partiklarna blir tillräckligt nära det svarta hålet. Detta tjänar två syften:
    1. Det gör partiklarna spiral medurs mot det svarta hålet.
    2. När partiklarna blir tillräckligt nära kommer de att nå jämvikt och bilda en glödande cirkel runt det svarta hålet.

Tips: Att rotera en vektor, V, 90 ° medurs, ta (V.Y, -V.X). På samma sätt, för att rotera den 90 ° moturs, ta (-V.Y, V.X).

Produktion av partiklar

Ett svart hål producerar två typer av partiklar. För det första sprutar det med jämna mellanrum partiklar som kommer att bana runt det. För det andra, när ett svart hål är skott, kommer det att spruta ut speciella partiklar som inte påverkas av dess tyngdkraft.

Lägg till följande kod till Blackhole :: WasShot () metod:

 float hue = fmodf (3.0f / 1000.0f * tTimer :: getTimeMS (), 6); tColor4f color = ColorUtil :: HSVToColor (färgton, 0,25f, 1); const int numParticles = 150; float startOffset = Extensions :: nextFloat (0, tMath :: PI * 2.0f / numParticles); för (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, färg, 90, 1.5f, state); 

Detta fungerar i huvudsak på samma sätt som de andra partikelexplosionerna. En skillnad är att vi väljer färgtonen baserat på den totala förflutna speltiden. Om du skjuter det svarta hålet flera gånger i snabb följd så kommer du att se explosionsens nyans rotera gradvis. Det här ser mindre rotigt ut än att använda slumpmässiga färger, samtidigt som det tillåter variation.

För den bollande partikelsprayen måste vi lägga till en variabel till Svart hål klass för att spåra den riktning som vi för närvarande sprutar partiklar på:

 skyddad: int mHitPoints; float mSprayAngle; BlackHole :: BlackHole (const tVector2f & position): mSprayAngle (0) ...

Nu lägger vi till följande i Blackhole :: uppdatering () metod.

 // De svarta hålen spraya några kretsande partiklar. Spruten växlar på och av varje kvart sekund. om ((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); ParticleState State (sprayVel, ParticleState :: kEnemy, 1.0f); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, färg, 190, 1.5f, state);  // rotera sprutriktningen mSprayAngle - = tMath :: PI * 2.0f / 50.0f;

Detta kommer att leda till att de svarta hålen spruter sprutor av lila partiklar som kommer att bilda en ring som kretsar kring det svarta hålet, så här:

Ship Exhaust Fire

Som dikteras av lagar av geometrisk-neonfysik, driver spelarens skepp sig genom att jetting en ström av eldiga partiklar ut ur avgasröret. Med vår partikelmotor på plats är denna effekt lätt att göra och lägger till visuell känsla för skeppets rörelse.

När fartyget rör sig skapar vi tre strömmar av partiklar: en centrumström som brinner rakt ut på fartygets baksida och två sidströmmar vars vinklar svänger fram och tillbaka i förhållande till skeppet. De två sidströmmarna svänger i motsatta riktningar för att göra ett kors-övergångsmönster. Sidoströmmarna har en rödare färg, medan mittflödet har en varmare gulvita färg. Animeringen nedan visar effekten:


För att elden ska lysa mer ljust, kommer vi att få fartyget att avge ytterligare partiklar som ser ut så här:


Dessa partiklar kommer att färgas och blandas med de vanliga partiklarna. Koden för hela effekten visas nedan:

 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, -basVel.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); // Placering av fartygets avgasrör. const float alfa = 0,7f; // mellannpartikelström tVector2f velMid = baseVel + Extensions :: nextVector2 (0, 1); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alfa, 60,0f, tVector2f (0,5f, 1), ParticleState (velMid, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, midColor * alfa, 60.0f, tVector2f (0.5f, 1), ParticleState (velMid, ParticleState: : kEnemy)); // sidpartikelflöden tVector2f vel1 = basVel + 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) * alfa, 60,0f, tVector2f (0,5f, 1), ParticleState (vel1, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getLineParticle (), pos, tColor4f (1,1,1,1) * alfa, 60,0f, tVector2f (0,5f, 1), ParticleState (vel2, ParticleState :: kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alfa, 60.0f, tVector2f (0.5f, 1), ParticleState (vel1, ParticleState: : kEnemy)); GameRoot :: getInstance () -> getParticleManager () -> createParticle (Art :: getInstance () -> getGlow (), pos, sideColor * alfa, 60.0f, tVector2f (0.5f, 1), ParticleState (vel2, ParticleState: : kEnemy)); 

Det finns inget lurigt pågår i den här koden. Vi använder en sinusfunktion för att producera svängningseffekten i sidströmmarna genom att ändra sin sidledshastighet över tiden. För varje ström skapar vi två överlappande partiklar per ram: en halvtransparent, vit LineParticle, och en färgad glödpartikel bakom den. Ring upp
MakeExhaustFire () i slutet av PlayerShip.Update (), omedelbart innan fartygets hastighet ställs till noll.

Slutsats

Med alla dessa partikeleffekter börjar Shape Blaster se ganska cool ut. I den sista delen av den här serien kommer vi att lägga till ytterligare en fantastisk effekt: det klingande bakgrundsnätet.