Gör en Neon Vector Shooter i XNA Partikel Effects

I den här serien av handledningar visar jag dig hur man gör en neon tvillingskytt, som Geometry Wars, i XNA. Målet med dessa handledningar är att inte lämna dig en exakt kopia av Geometry Wars, utan snarare att gå över de nödvändiga elementen som gör att du kan skapa din egen högkvalitativa variant.


Översikt

I serien hittills har vi satt upp gameplayen och tillagt blomningen. 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.

De ParticleManager Klass

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. För att hålla ParticleManager generellt sett 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 objekt pool 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 kapslad Partikel klass.

 offentlig klass ParticleManager public class Particle public Texture2D Texture; allmän vektor2 position offentlig float Orientering; allmän Vector2 Scale = Vector2.One; offentlig färgfärg; offentlig float Varaktighet; offentlig float PercentLife = 1f; offentlig T-stat 

De Partikel klassen har all information som krävs för att visa en partikel och hantera sin livstid. Den generiska parametern, T-staten, 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 att hantera partiklarna behöver vi en klass som fungerar som en cirkulär grupp, vilket betyder att index som normalt skulle vara obegränsade kommer istället att slingras runt till början av arrayen. 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. Vi lägger till följande som en kapslad klass i ParticleManager.

 privat klass CircularParticleArray privat int start; public int Start get return start;  set start = värde% list.Length;  public int Count get; uppsättning;  public int Kapacitet get return list.Length;  privat partikel [] -lista; public CircularParticleArray (int kapacitet) list = new Particle [capacity];  offentligt Partikel detta [int i] get returlista [(start + i)% list.Length];  set list [(start + i)% list.Length] = value; 

Vi kan ställa in Start egenskap att justera var index noll i vår CircularParticleArray motsvarar i den underliggande matrisen, och Räkna 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 Start, 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 några medlemsvariabler och en konstruktör.

 // Denna delegat kommer att krävas för varje partikel. privat åtgärd updateParticle; privat CircularParticleArray partikellista; offentliga ParticleManager (int kapacitet, Action updateParticle) this.updateParticle = updateParticle; particleList = ny CircularParticleArray (kapacitet); // Fyll i listan med tomma partikelobjekt, för återanvändning. för (int i = 0; i < capacity; i++) particleList[i] = new Particle(); 

Den första variabeln deklarerades, updateParticle, kommer att vara en anpassad metod som uppdaterar partiklarna lämpligt för önskad effekt. Ett spel kan ha flera ParticleManagers Det uppdateras annorlunda om det behövs. Vi skapar också en CircularParticleList och fyll det med tomma partiklar. Konstruktören är den enda platsen som ParticleManager allokerar minne.

Nästa 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.

 public void CreateParticle (Texture2D konsistens, Vector2 position, Färgton, Float duration, Vector2 skala, T tillstånd, float theta = 0) Partikelpartikel; om (particleList.Count == particleList.Capacity) // om listan är full, skriv över den äldsta partikeln och rotera den cirkulära listan partikel = partikellista [0]; particleList.Start ++;  else particle = particleList [particleList.Count]; particleList.Count ++;  // Skapa partikelpartikeln. Textur = Struktur; partikel.Position = position; partikel.Tint = nyans; partikel.Duration = varaktighet; partikel.PercentLife = 1f; partikel.Skala = skala; partikel.Orientation = theta; partikel.State = tillstånd; 

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 Räkna 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.Update () hanterar uppdatering av varje partikel och avlägsnande av förstörda partiklar från listan.

 public void Update () int removalCount = 0; för (int i = 0; i < particleList.Count; i++)  var particle = particleList[i]; updateParticle(particle); particle.PercentLife -= 1f / particle.Duration; // sift deleted particles to the end of the list Swap(particleList, i - removalCount, i); // if the particle has expired, delete this particle if (particle.PercentLife < 0) removalCount++;  particleList.Count -= removalCount;  private static void Swap(CircularParticleArray list, int index1, int index2)  var temp = list[index1]; list[index1] = list[index2]; list[index2] = temp; 

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

 public void Draw (SpriteBatch spriteBatch) för (int i = 0; i < particleList.Count; i++)  var particle = particleList[i]; Vector2 origin = new Vector2(particle.Texture.Width / 2, particle.Texture.Height / 2); spriteBatch.Draw(particle.Texture, particle.Position, null, particle.Color, particle.Orientation, origin, particle.Scale, 0, 0);  

De ParticleState Struct

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.

 allmänhet ParticleType None, Enemy, Bullet, IgnoreGravity offentliga struct ParticleState public Vector2 Velocity; allmän partikeltypstyp; offentlig float längdmultiplikator; 

Nu är vi redo att skriva partikelns uppdateringsmetod. Det är en bra idé att göra denna metod snabb eftersom det kan behöva kallas för ett stort antal partiklar.

Vi börjar enkelt. Lägg till följande metod till ParticleState.

 offentlig statisk tomt UpdateParticle (ParticleManager.Particle particle) varvel = partikel.State.Velocity; partikel.Position + = vel; partikel.Orientation = vel.ToAngle (); // denormaliserade flottören orsakar betydande prestanda problem om (Math.Abs ​​(vel.X) + Math.Abs ​​(vel.Y) < 0.00000000001f) vel = Vector2.Zero; vel *= 0.97f; // particles gradually slow down x.State.Velocity = vel; 

Enemy Explosions

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. I GameRoot, förklara en ny ParticleManager och ringa dess Uppdatering() och Dra() metoder.

 // i GameRoot public static ParticleManager ParticleManager get; privat uppsättning  // i GameRoot.Initialize () ParticleManager = ny ParticleManager (1024 * 20, ParticleState.UpdateParticle); // i GameRoot.Update () ParticleManager.Update (); // i GameRoot.Draw () spriteBatch.Begin (SpriteSortMode.Deferred, BlendState.Additive); ParticleManager.Draw (); spriteBatch.End ();

Också, förklara en ny Texture2D kallad LineParticle för partikelns konsistens i Konst klass, och ladda texturen som vi gjorde för de andra spritesna.

Låt oss nu göra fiender explodera. Ändra Enemy.WasShot () metod enligt följande.

 public void WasShot () IsExpired = true; för (int i = 0; i < 120; i++)  float speed = 18f * (1f - 1 / rand.NextFloat(1f, 10f)); var state = new ParticleState()  Velocity = rand.NextVector2(speed, speed), Type = ParticleType.Enemy, LengthMultiplier = 1f ; GameRoot.ParticleManager.CreateParticle(Art.LineParticle, Position, Color.LightGreen, 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.

Den första frågan ä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 få 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).

 offentlig statisk tomt UpdateParticle (ParticleManager.Particle particle) varvel = partikel.State.Velocity; partikel.Position + = vel; partikel.Orientation = vel.ToAngle (); flythastighet = vel.Length (); float alfa = Math.Min (1, Math.Min (particle.PercentLife * 2, hastighet * 1f)); alfa * = alfa; partikel.Kolor.A = (byte) (255 * alfa); particle.Scale.X = particle.State.LengthMultiplier * Math.Min (Math.Min (1f, 0.2f * hastighet + 0.1f), alfa); om (Math.Abs ​​(vel.X) + Math.Abs ​​(vel.Y) < 0.00000000001f) // denormalized floats cause significant performance issues vel = Vector2.Zero; vel *= 0.97f; // particles gradually slow down x.State.Velocity = vel; 

Explosionerna ser mycket bättre ut nu, men de har samma färg. 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.

 statisk klass ColorUtil offentlig statisk färg HSVToColor (float h, float s, float v) if (h == 0 && s == 0) returnera ny färg (v, v, v); float c = s * v; float x = c * (1 - Math.Abs ​​(h% 2 - 1)); float m = v-c; om (h < 1) return new Color(c + m, x + m, m); else if (h < 2) return new Color(x + m, c + m, m); else if (h < 3) return new Color(m, c + m, x + m); else if (h < 4) return new Color(m, x + m, c + m); else if (h < 5) return new Color(x + m, m, c + m); else return new Color(c + m, m, x + m);  

Nu kan vi ändra Enemy.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.

 public void WasShot () IsExpired = true; float hue1 = rand.NextFloat (0, 6); float hue2 = (hue1 + rand.NextFloat (0, 2))% 6f; Färgfärg1 = ColorUtil.HSVToColor (färgton, 0,5f, 1); Färgfärg2 = ColorUtil.HSVToColor (hue2, 0.5f, 1); för (int i = 0; i < 120; i++)  float speed = 18f * (1f - 1 / rand.NextFloat(1f, 10f)); var state = new ParticleState()  Velocity = rand.NextVector2(speed, speed), Type = ParticleType.Enemy, LengthMultiplier = 1 ; Color color = Color.Lerp(color1, color2, rand.NextFloat(0, 1)); GameRoot.ParticleManager.CreateParticle(Art.LineParticle, Position, color, 190, 1.5f, state);  

Explosionerna ska se ut som animationen nedan.

Du kan leka med färggenerering som passar dina önskemål. En alternativ teknik som fungerar bra är att välja ett antal färgmönster för explosioner och välja slumpmässigt bland 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ägg till en statisk Slumpmässig medlem till Kula klass.

 privat statisk Random rand = ny slumpmässig ();

Ändra sedan Bullet.Update () som följer.

 // radera kulor som går utanför skärmen om (! GameRoot.Viewport.Bounds.Contains (Position.ToPoint ())) IsExpired = true; för (int i = 0; i < 30; i++) GameRoot.ParticleManager.CreateParticle(Art.LineParticle, Position, Color.LightBlue, 50, 1, new ParticleState()  Velocity = rand.NextVector2(0, 9), Type = ParticleType.Bullet, LengthMultiplier = 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. Eventuella 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.

 var pos = x.Position; int bredd = (int) GameRoot.ScreenSize.X; int height = (int) GameRoot.ScreenSize.Y; // kollidera med skärmens kanter om (pos.X < 0) vel.X = Math.Abs(vel.X); else if (pos.X > bredd) vel.X = -Math.Abs ​​(vel.X); om (pos.Y < 0) vel.Y = Math.Abs(vel.Y); else if (pos.Y > höjd) vel.Y = -Math.Abs ​​(vel.Y);

Spelarens fartygs explosion

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

 public void Kill () framesUntilRespawn = 60; Färg gul = ny färg (0,8f, 0,8f, 0,4f); för (int i = 0; i < 1200; i++)  float speed = 18f * (1f - 1 / rand.NextFloat(1f, 10f)); Color color = Color.Lerp(Color.White, yellow, rand.NextFloat(0, 1)); var state = new ParticleState()  Velocity = rand.NextVector2(speed, speed), Type = ParticleType.None, LengthMultiplier = 1 ; GameRoot.ParticleManager.CreateParticle(Art.LineParticle, Position, color, 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å ParticleType.None.

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ägg till följande rader.

 om (x.State.Type! = ParticleType.IgnoreGravity) foreach (var blackHole i EntityManager.BlackHoles) var dPos = blackHole.Position - pos; float distance = dPos.Length (); var n = dPos / avstånd; vel + = 10000 * n / (avstånd * avstånd + 10000); // lägga tangentiell acceleration för närliggande partiklar om (avstånd < 400) vel += 45 * new Vector2(n.Y, -n.X) / (distance + 100);  

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 tändas mot oändlighet då avståndet blir mycket litet. När avståndet är mycket större än 100 pixlar blir \ (avstånd ^ 2 \) mycket större än 10 000. Därför har adderingen 10 000 till \ (avstånd ^ 2 \) 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. För det första gör partiklarna spiral medurs mot det svarta hålet. För det andra, när partiklarna kommer nära nog, når de jämvikt och bildar en glödande cirkel runt det svarta hålet.

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

Produktion av partiklar

Svarta hål kommer att producera två typer av partiklar. Först kommer de regelbundet att spruta ut partiklar som kommer att cirkulera runt dem. 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 = (float) ((3 * GameRoot.GameTime.TotalGameTime.TotalSeconds)% 6); Färgfärg = ColorUtil.HSVToColor (färgton, 0,25f, 1); const int numParticles = 150; float startOffset = rand.NextFloat (0, MathHelper.TwoPi / numParticles); för (int i = 0; i < numParticles; i++)  Vector2 sprayVel = MathUtil.FromPolar(MathHelper.TwoPi * i / numParticles + startOffset, rand.NextFloat(8, 16)); Vector2 pos = Position + 2f * sprayVel; var state = new ParticleState()  Velocity = sprayVel, LengthMultiplier = 1, Type = ParticleType.IgnoreGravity ; GameRoot.ParticleManager.CreateParticle(Art.LineParticle, pos, color, 90, 1.5f, state); 

Detta fungerar mestadels på samma sätt som de andra partikel explosionerna. 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 den 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 där vi för närvarande sprutar partiklar.

 privat float sprayAngle = 0;

Lägg nu till följande i BlackHole.Update () metod.

 // De svarta hålen spraya några kretsande partiklar. Spruten växlar på och av varje kvart sekund. om ((GameRoot.GameTime.TotalGameTime.Milliseconds / 250)% 2 == 0) Vector2 sprayVel = MathUtil.FromPolar (sprayAngle, rand.NextFloat (12, 15)); Färgfärg = ColorUtil.HSVToColor (5, 0,5f, 0,8f); // ljuslila Vector2 pos = Position + 2f * ny Vector2 (sprayVel.Y, -sprayVel.X) + rand.NextVector2 (4, 8); var state = new ParticleState () Velocity = sprayVel, LengthMultiplier = 1, Type = ParticleType.Enemy; GameRoot.ParticleManager.CreateParticle (Art.LineParticle, pos, färg, 190, 1.5f, state);  // rotera sprutriktningen sprayAngle - = MathHelper.TwoPi / 50f;

Detta kommer att orsaka att de svarta hålen sprutar spetsar av lila partiklar som kommer att bilda en kall glödande ring som kretsar kring det svarta hålet, som så:



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 få elden att glöda mer ljus än det skulle vara av blomning ensam, 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.

 privat tomt MakeExhaustFire () if (Velocity.LengthSquared ()> 0.1f) // ställa in några variabler Orientering = Velocity.ToAngle (); Quaternion rot = Quaternion.CreateFromYawPitchRoll (0f, 0f, Orientering); double t = GameRoot.GameTime.TotalGameTime.TotalSeconds; // Partikelns primära hastighet är 3 pixlar / ram i motsatt riktning mot vilket fartyget reser. Vector2 baseVel = Velocity.ScaleTo (-3); // Beräkna sidledshastigheten för de två sidströmmarna. Riktningen är vinkelrätt mot fartygets hastighet och // magnitud varierar sinusformigt. Vector2 perpVel = ny Vector2 (baseVel.Y, -basVel.X) * (0,6f * (float) Math.Sin (t * 10)); Color sideColor = ny färg (200, 38, 9); // djupröd Färg midColor = ny Färg (255, 187, 30); // orange-gul Vector2 pos = Position + Vector2.Transform (ny Vector2 (-25, 0), rot); // Placering av fartygets avgasrör. const float alfa = 0,7f; // medelpartikelströmmen Vector2 velMid = baseVel + rand.NextVector2 (0, 1); GameRoot.ParticleManager.CreateParticle (Art.LineParticle, pos, Color.White * alfa, 60f, ny Vector2 (0.5f, 1), ny ParticleState (velMid, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.Glow, pos, midColor * alfa, 60f, ny Vector2 (0.5f, 1), ny ParticleState (velMid, ParticleType.Enemy)); // sidpartikelströmmar Vector2 vel1 = basVel + perpVel + rand.NextVector2 (0, 0.3f); Vector2 vel2 = basVel - perpVel + rand.NextVector2 (0, 0.3f); GameRoot.ParticleManager.CreateParticle (Art.LineParticle, pos, Color.White * alfa, 60f, ny Vector2 (0.5f, 1), ny ParticleState (vel1, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.LineParticle, pos, Color.White * alfa, 60f, ny Vector2 (0.5f, 1), ny ParticleState (vel2, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.Glow, pos, sideColor * alfa, 60f, ny Vector2 (0.5f, 1), ny ParticleState (vel1, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.Glow, pos, sideColor * alfa, 60f, ny Vector2 (0.5f, 1), ny ParticleState (vel2, ParticleType.Enemy)); 

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.