In questa serie di tutorial, ti mostrerò come realizzare uno sparatutto al neon con due gemelli, come Geometry Wars, in XNA. L'obiettivo di queste esercitazioni non è quello di lasciarti con una replica esatta di Geometry Wars, ma piuttosto di esaminare gli elementi necessari che ti permetteranno di creare la tua variante di alta qualità.
Nella serie finora, abbiamo impostato il gameplay e aggiunto bloom. Successivamente, aggiungeremo effetti particellari.
Avvertenza: forte!Gli effetti particellari vengono creati facendo un gran numero di piccole particelle. Sono molto versatili e possono essere usati per aggiungere stile a quasi tutti i giochi. In Shape Blaster creeremo esplosioni usando effetti particellari. Useremo anche effetti particellari per creare fuoco di scarico per la nave del giocatore e per aggiungere un tocco visivo ai buchi neri. Inoltre, vedremo come far interagire le particelle con la gravità dai buchi neri.
ParticleManager
ClasseInizieremo creando un ParticleManager
classe che memorizzerà, aggiornerà e disegnerà tutte le particelle. Renderemo questa classe abbastanza generale da poter essere facilmente riutilizzata in altri progetti. Per mantenere il ParticleManager
in generale, non sarà responsabile del modo in cui le particelle appaiono o si muovono; lo gestiremo altrove.
Le particelle tendono a essere create e distrutte rapidamente e in gran numero. Useremo un pool di oggetti per evitare di creare grandi quantità di spazzatura. Ciò significa che alloceremo un gran numero di particelle in primo piano e quindi continueremo a riutilizzare queste stesse particelle. Faremo anche ParticleManager
avere una capacità fissa. Ciò lo semplificherà e ci aiuterà a garantire che non superiamo le nostre prestazioni o limiti di memoria creando troppe particelle. Quando viene superato il numero massimo di particelle, inizieremo a sostituire le particelle più vecchie con quelle nuove.
Faremo il ParticleManager
una classe generica. Ciò ci consentirà di archiviare le informazioni di stato personalizzate per le particelle senza codificarle nel codice ParticleManager
si. Creeremo anche un nidificato particella
classe.
ParticleManager di classe pubblicaParticella di classe pubblica Texture Texture2D pubblica; posizione pubblica Vector2; orientamento del galleggiante pubblico; public Vector2 Scale = Vector2.One; colore colore pubblico; durata del flottante pubblico; float pubblico PercentLife = 1f; stato pubblico T;
Il particella
classe ha tutte le informazioni necessarie per visualizzare una particella e gestirne la durata. Il parametro generico, T Stato
, è lì per contenere tutti i dati aggiuntivi di cui potremmo aver bisogno per le nostre particelle. Quali dati sono necessari varieranno a seconda degli effetti particellari desiderati; potrebbe essere utilizzato per memorizzare velocità, accelerazione, velocità di rotazione o qualsiasi altra cosa che potrebbe essere necessario.
Per aiutare a gestire le particelle, avremo bisogno di una classe che funzioni come un matrice circolare, il che significa che gli indici che normalmente sarebbero fuori limite si avvolgeranno invece all'inizio della matrice. Ciò renderà facile sostituire prima le particelle più vecchie se esauriamo lo spazio per nuove particelle nel nostro array. Aggiungiamo il seguente come una classe annidata in ParticleManager
.
classe privata CircularParticleArray private int start; pubblico int Inizia get ritorno inizio; set start = value% list.Length; public int Count get; impostato; public int Capacity get return list.Length; private Particle [] list; public CircularParticleArray (int capacity) list = new Particle [capacità]; public Particle this [int i] get return list [(start + i)% list.Length]; set list [(start + i)% list.Length] = value;
Possiamo impostare il Inizio
proprietà per regolare dove indice zero nel nostro CircularParticleArray
corrisponde all'array sottostante, e Contare
verrà utilizzato per tenere traccia di quante particelle attive sono presenti nell'elenco. Faremo in modo che la particella a zero indice sia sempre la particella più vecchia. Se sostituiamo la particella più vecchia con una nuova, semplicemente aumenteremo Inizio
, che sostanzialmente ruota l'array circolare.
Ora che abbiamo le nostre classi di aiuto, possiamo iniziare a compilare il ParticleManager
classe. Avremo bisogno di alcune variabili membro e un costruttore.
// Questo delegato sarà chiamato per ogni particella. azione privataupdateParticle; particella privata CircularParticleArray; ParticleManager pubblico (int capacity, Action updateParticle) this.updateParticle = updateParticle; particleList = new CircularParticleArray (capacità); // Compila la lista con gli oggetti particella vuoti, per riutilizzarli. per (int i = 0; i < capacity; i++) particleList[i] = new Particle();
La prima variabile dichiarata, updateParticle
, sarà un metodo personalizzato che aggiorna le particelle in modo appropriato per l'effetto desiderato. Un gioco può avere multipli ParticleManagers
aggiorna in modo diverso se necessario. Creiamo anche a CircularParticleList
e riempirlo con particelle vuote. Il costruttore è l'unico posto il ParticleManager
alloca la memoria.
Successivamente aggiungiamo il CreateParticle ()
metodo, che crea una nuova particella usando la particella successiva non utilizzata nel pool, o la particella più vecchia se non ci sono particelle inutilizzate.
vuoto pubblico CreateParticle (trama Texture2D, posizione Vector2, colore, durata flottante, scala Vector2, stato T, float theta = 0) particella particellare; if (particleList.Count == particleList.Capacity) // se la lista è piena, sovrascrive la particella più vecchia e ruota la lista circolare particle = particleList [0]; particleList.Start ++; else particle = particleList [particleList.Count]; particleList.Count ++; // Crea la particella particella. Texture = trama; particle.Position = posizione; particle.Tint = tinta; particle.Duration = duration; particle.PercentLife = 1f; particle.Scale = scala; particle.Orientation = theta; particle.State = state;
Le particelle possono essere distrutte in qualsiasi momento. Dobbiamo rimuovere queste particelle mentre assicuriamo che le altre particelle rimangano nello stesso ordine. Possiamo farlo ripetendo l'elenco delle particelle tenendo traccia di quante sono state distrutte. Mentre andiamo, spostiamo ogni particella attiva davanti a tutte le particelle distrutte scambiandola con la prima particella distrutta. Una volta che tutte le particelle distrutte sono alla fine della lista, le disattiviamo impostando la lista Contare
variabile al numero di particelle attive. Le particelle distrutte rimarranno nell'array sottostante, ma non verranno aggiornate o disegnate.
ParticleManager.Update ()
gestisce l'aggiornamento di ogni particella e la rimozione di particelle distrutte dall'elenco.
public void Update () int removalCount = 0; per (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;
L'ultima cosa da implementare in ParticleManager
sta disegnando le particelle.
public public Draw (SpriteBatch spriteBatch) for (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);
ParticleState
structLa prossima cosa da fare è creare una classe o una struttura personalizzata per personalizzare l'aspetto delle particelle in Shape Blaster. Ci saranno diversi tipi di particelle in Shape Blaster che si comportano in modo leggermente diverso, quindi inizieremo creando un enum
per il tipo di particella. Avremo anche bisogno di variabili per la velocità della particella e la lunghezza iniziale.
public enum ParticleType None, Enemy, Bullet, IgnoreGravity public struct ParticleState public Vector2 Velocity; tipo ParticleType pubblico; float pubblico LengthMultiplier;
Ora siamo pronti per scrivere il metodo di aggiornamento delle particelle. È una buona idea rendere questo metodo veloce poiché potrebbe dover essere chiamato per un gran numero di particelle.
Inizieremo alla semplicità. Aggiungere il seguente metodo a ParticleState
.
public static void UpdateParticle (ParticleManager.Particle particle) var vel = particle.State.Velocity; particle.Position + = vel; particle.Orientation = vel.ToAngle (); // i float denormalizzati causano problemi di prestazioni significativi se (Math.Abs (vel.X) + Math.Abs (vel.Y) < 0.00000000001f) vel = Vector2.Zero; vel *= 0.97f; // particles gradually slow down x.State.Velocity = vel;
Torneremo e miglioreremo questo metodo tra un momento. Innanzitutto, creiamo alcuni effetti particellari in modo che possiamo testare realmente le nostre modifiche. Nel GameRoot
, dichiarare un nuovo ParticleManager
e chiamatelo Aggiornare()
e Disegnare()
metodi.
// in GameRoot public static ParticleManager ParticleManager get; set privato; // in GameRoot.Initialize () ParticleManager = new ParticleManager (1024 * 20, ParticleState.UpdateParticle); // in GameRoot.Update () ParticleManager.Update (); // in GameRoot.Draw () spriteBatch.Begin (SpriteSortMode.Deferred, BlendState.Additive); ParticleManager.Draw (); spriteBatch.End ();
Inoltre, dichiarare un nuovo Texture2D
chiamato LineParticle
per la consistenza della particella nel Arte
classe, e caricare la trama come abbiamo fatto per gli altri sprite.
Ora facciamo esplodere i nemici. Modifica il Enemy.WasShot ()
metodo come segue.
public void WasShot () IsExpired = true; per (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);
Questo crea 120 particelle che spareranno verso l'esterno con velocità diverse in tutte le direzioni. La velocità casuale è ponderata in modo tale che le particelle abbiano maggiori probabilità di spostarsi vicino alla velocità massima. Questo farà sì che più particelle si trovino sul bordo dell'esplosione mentre si espande. Le particelle durano 190 frame, o poco più di tre secondi.
Ora puoi giocare e vedere i nemici esplodere. Tuttavia, ci sono ancora alcuni miglioramenti da apportare agli effetti particellari.
Il primo problema è che le particelle scompaiono bruscamente una volta esaurita la loro durata. Sarebbe più bello se potessero svanire gradualmente. Ma andiamo un po 'oltre e facciamo brillare le particelle quando si muovono velocemente. Inoltre, è bello se allunghiamo le particelle in rapido movimento e accorciamo quelle lente.
Modifica il ParticleState.UpdateParticle ()
metodo come segue (le modifiche sono evidenziate).
public static void UpdateParticle (ParticleManager.Particle particle) var vel = particle.State.Velocity; particle.Position + = vel; particle.Orientation = vel.ToAngle (); velocità di galleggiamento = vel.Length (); float alpha = Math.Min (1, Math.Min (particle.PercentLife * 2, speed * 1f)); alfa * = alfa; particle.Color.A = (byte) (255 * alfa); particle.Scale.X = particle.State.LengthMultiplier * Math.Min (Math.Min (1f, 0.2f * velocità + 0.1f), alfa); if (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;
Le esplosioni sembrano molto meglio ora, ma sono tutte dello stesso colore. Possiamo dare loro più varietà scegliendo colori casuali. Un metodo per produrre colori casuali consiste nel scegliere casualmente i componenti rosso, blu e verde, ma questo produrrà un sacco di colori opachi e vorremmo che le nostre particelle avessero un aspetto di luce al neon. Possiamo avere maggiore controllo sui nostri colori specificandoli nello spazio colore HSV. HSV sta per tonalità, saturazione e valore. Vorremmo scegliere i colori con una tonalità casuale ma con una saturazione e un valore fissi. Abbiamo bisogno di una funzione di supporto che possa produrre un colore dai valori HSV.
classe statica ColorUtil public static Color HSVToColor (float h, float s, float v) if (h == 0 && s == 0) restituisce il nuovo colore (v, v, v); float c = s * v; float x = c * (1 - Math.Abs (h% 2 - 1)); float m = v - c; se (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);
Ora possiamo modificare Enemy.WasShot ()
usare colori casuali. Per rendere il colore dell'esplosione meno monotono, selezioneremo due colori chiave vicini per ogni esplosione e interpoleremo linearmente tra di loro di una quantità casuale per ogni particella.
public void WasShot () IsExpired = true; float hue1 = rand.NextFloat (0, 6); float hue2 = (hue1 + rand.NextFloat (0, 2))% 6f; Color color1 = ColorUtil.HSVToColor (hue1, 0.5f, 1); Color color2 = ColorUtil.HSVToColor (hue2, 0.5f, 1); per (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);
Le esplosioni dovrebbero assomigliare all'animazione sottostante.
Puoi giocare con la generazione del colore per soddisfare le tue preferenze. Una tecnica alternativa che funziona bene è scegliere una serie di modelli di colori per le esplosioni e scegliere casualmente tra i tuoi schemi di colori preselezionati.
Possiamo anche far esplodere i proiettili quando raggiungono il bordo dello schermo. Faremo essenzialmente la stessa cosa che abbiamo fatto per le esplosioni nemiche.
Aggiungi una statica Casuale
membro al proiettile
classe.
private static Random = new Random ();
Quindi modificare Bullet.Update ()
come segue.
// cancella i proiettili fuori schermo se (! GameRoot.Viewport.Bounds.Contains (Position.ToPoint ())) IsExpired = true; per (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 );
Potresti notare che dare alle particelle una direzione casuale è dispendioso, perché almeno la metà delle particelle andrà immediatamente fuori dallo schermo (di più se il proiettile esplode in un angolo). Potremmo fare un po 'di lavoro extra per garantire che le particelle abbiano solo velocità opposte al muro che stanno affrontando. Invece, prenderemo spunto da Geometry Wars e faremo rimbalzare tutte le particelle dai muri. Tutte le particelle che si dirigono fuori dallo schermo verranno rimbalzate indietro.
Aggiungi le seguenti linee a ParticleState.UpdateParticle ()
ovunque tra la prima e l'ultima riga.
var pos = x.Position; int width = (int) GameRoot.ScreenSize.X; int height = (int) GameRoot.ScreenSize.Y; // collide con i bordi dello schermo se (pos.X < 0) vel.X = Math.Abs(vel.X); else if (pos.X > larghezza) vel.X = -Math.Abs (vel.X); se (pos.Y < 0) vel.Y = Math.Abs(vel.Y); else if (pos.Y > altezza) vel.Y = -Math.Abs (vel.Y);
Faremo una grande esplosione quando il giocatore viene ucciso. Modificare PlayerShip.Kill ()
così:
public void Kill () framesUntilRespawn = 60; Colore giallo = nuovo Colore (0.8f, 0.8f, 0.4f); per (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);
Questo è simile alle esplosioni nemiche, ma usiamo più particelle e usiamo sempre la stessa combinazione di colori. Anche il tipo di particella è impostato su ParticleType.None
.
Nella demo, le particelle provenienti dalle esplosioni nemiche rallentano più velocemente rispetto alle particelle esplosive della nave del giocatore. Ciò rende l'esplosione del giocatore durare un po 'più a lungo e sembrare un po' più epica.
Ora che abbiamo effetti particellari, rivisitiamo i buchi neri e li facciamo interagire con le particelle.
I buchi neri dovrebbero influenzare le particelle in aggiunta ad altre entità, quindi dobbiamo modificarle ParticleState.UpdateParticle ()
. Aggiungi le seguenti linee.
if (x.State.Type! = ParticleType.IgnoreGravity) foreach (var blackHole in EntityManager.BlackHoles) var dPos = blackHole.Position - pos; float distance = dPos.Length (); var n = dPos / distance; vel + = 10000 * n / (distanza * distanza + 10000); // aggiungi l'accelerazione tangenziale per le particelle vicine se (distanza < 400) vel += 45 * new Vector2(n.Y, -n.X) / (distance + 100);
Qui, n
è il vettore dell'unità che punta verso il buco nero. La forza attrattiva è una versione modificata della funzione inversa quadrata. La prima modifica è che il denominatore è \ (distanza ^ 2 + 10.000 \). Questo fa sì che la forza attrattiva si avvicini a un valore massimo invece di tendere verso l'infinito quando la distanza diventa molto piccola. Quando la distanza è molto maggiore di 100 pixel, \ (distanza ^ 2 \) diventa molto maggiore di 10.000. Pertanto, l'aggiunta di 10.000 a \ (distanza ^ 2 \) ha un effetto molto piccolo e la funzione si avvicina a una normale funzione inversa quadrata. Tuttavia, quando la distanza è molto inferiore a 100 pixel, la distanza ha un piccolo effetto sul valore del denominatore e l'equazione diventa approssimativamente uguale a:
vel + = n;
La seconda modifica è l'aggiunta di un componente laterale alla velocità quando le particelle si avvicinano abbastanza al buco nero. Questo ha due scopi. Innanzitutto, fa girare le particelle in senso orario verso il buco nero. Secondo, quando le particelle si avvicinano abbastanza, raggiungeranno l'equilibrio e formeranno un cerchio luminoso attorno al buco nero.
Mancia: Per ruotare un vettore, V, 90 ° in senso orario, prendi(V.Y, -V.X)
. Allo stesso modo, per ruotare di 90 ° in senso antiorario, prendere (-V.Y, V.X)
. I buchi neri produrranno due tipi di particelle. Innanzitutto, spruzzano periodicamente particelle che orbitano intorno a loro. Secondo, quando un buco nero viene sparato, spruzza particelle speciali che non sono influenzate dalla sua gravità.
Aggiungi il seguente codice al BlackHole.WasShot ()
metodo.
float hue = (float) ((3 * GameRoot.GameTime.TotalGameTime.TotalSeconds)% 6); Colore colore = ColorUtil.HSVToColor (hue, 0.25f, 1); const int numParticles = 150; float startOffset = rand.NextFloat (0, MathHelper.TwoPi / numParticles); per (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);
Funziona principalmente allo stesso modo delle altre esplosioni di particelle. Una differenza è che selezioniamo la tonalità del colore in base al tempo di gioco totale trascorso. Se spari il buco nero più volte in rapida successione, vedrai la tonalità delle esplosioni gradualmente ruotare. Questo sembra meno disordinato rispetto all'utilizzo di colori casuali pur consentendo variazioni.
Per lo spruzzo di particelle orbitanti, dobbiamo aggiungere una variabile al Buco nero
classe per tracciare la direzione in cui attualmente stiamo spruzzando particelle.
private float sprayAngle = 0;
Ora aggiungi il seguente al BlackHole.Update ()
metodo.
// I buchi neri spruzzano alcune particelle orbitanti. Lo spray si accende e si spegne ogni quarto di secondo. if ((GameRoot.GameTime.TotalGameTime.Milliseconds / 250)% 2 == 0) Vector2 sprayVel = MathUtil.FromPolar (sprayAngle, rand.NextFloat (12, 15)); Colore colore = ColorUtil.HSVToColor (5, 0.5f, 0.8f); // light purple Vector2 pos = Position + 2f * new 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, color, 190, 1.5f, state); // ruota la direzione dello spray sprayAngle - = MathHelper.TwoPi / 50f;
Questo farà sì che i buchi neri spruzzino spruzzi di particelle viola che formeranno un anello luminoso che orbita attorno al buco nero, in questo modo:
Come dettato dalle leggi della fisica dei neon geometrici, la nave del giocatore si spinge facendo fuoriuscire un flusso di particelle infuocate fuori dal suo tubo di scarico. Con il nostro motore di particelle installato, questo effetto è facile da realizzare e aggiunge un tocco visivo al movimento della nave.
Mentre la nave si muove, creiamo tre flussi di particelle: un flusso centrale che spara direttamente sul retro della nave, e due flussi laterali i cui angoli ruotano avanti e indietro rispetto alla nave. I due flussi laterali si muovono in direzioni opposte per creare uno schema incrociato. I flussi laterali hanno un colore più rosso, mentre il flusso centrale ha un colore più caldo, giallo-bianco. L'animazione sotto mostra l'effetto.
Per fare in modo che il fuoco risplenda più intensamente di quanto non sarebbe dalla fioritura, avremo la nave emettere particelle aggiuntive che assomigliano a questo:
Queste particelle saranno colorate e mescolate con le particelle regolari. Il codice per l'intero effetto è mostrato sotto.
private void MakeExhaustFire () if (Velocity.LengthSquared ()> 0.1f) // imposta alcune variabili Orientation = Velocity.ToAngle (); Quaternion rot = Quaternion.CreateFromYawPitchRoll (0f, 0f, Orientation); double t = GameRoot.GameTime.TotalGameTime.TotalSeconds; // La velocità primaria delle particelle è di 3 pixel / frame nella direzione opposta a cui viaggia la nave. Vector2 baseVel = Velocity.ScaleTo (-3); // Calcola la velocità laterale per i due flussi laterali. La direzione è perpendicolare alla velocità della nave e la magnitudine // varia sinusoidalmente. Vector2 perpVel = new Vector2 (baseVel.Y, -baseVel.X) * (0.6f * (float) Math.Sin (t * 10)); Colore sideColor = new Color (200, 38, 9); // deep red Colore midColor = new Color (255, 187, 30); // orange-yellow Vector2 pos = Position + Vector2.Transform (new Vector2 (-25, 0), rot); // posizione del tubo di scarico della nave. const float alpha = 0.7f; // flusso di particelle medie Vector2 velMid = baseVel + rand.NextVector2 (0, 1); GameRoot.ParticleManager.CreateParticle (Art.LineParticle, pos, Color.White * alpha, 60f, new Vector2 (0.5f, 1), new ParticleState (velMid, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.Glow, pos, midColor * alpha, 60f, new Vector2 (0.5f, 1), new ParticleState (velMid, ParticleType.Enemy)); // flussi di particelle laterali Vector2 vel1 = baseVel + perpVel + rand.NextVector2 (0, 0,3f); Vector2 vel2 = baseVel - perpVel + rand.NextVector2 (0, 0,3f); GameRoot.ParticleManager.CreateParticle (Art.LineParticle, pos, Color.White * alpha, 60f, new Vector2 (0.5f, 1), new ParticleState (vel1, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.LineParticle, pos, Color.White * alpha, 60f, new Vector2 (0.5f, 1), new ParticleState (vel2, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.Glow, pos, sideColor * alpha, 60f, new Vector2 (0.5f, 1), new ParticleState (vel1, ParticleType.Enemy)); GameRoot.ParticleManager.CreateParticle (Art.Glow, pos, sideColor * alpha, 60f, new Vector2 (0.5f, 1), new ParticleState (vel2, ParticleType.Enemy));
Non c'è nulla di subdolo in questo codice. Usiamo una funzione seno per produrre l'effetto girevole nei flussi laterali variando la loro velocità laterale nel tempo. Per ogni flusso, creiamo due particelle sovrapposte per fotogramma: un bianco semitrasparente LineParticle
e una particella di luce colorata dietro di esso. Chiamata MakeExhaustFire ()
alla fine di PlayerShip.Update ()
, immediatamente prima di impostare la velocità della nave a zero.
Con tutti questi effetti particellari, Shape Blaster sta diventando piuttosto interessante. Nella parte finale di questa serie, aggiungeremo un altro effetto fantastico: la griglia di sfondo di orditura.