Fai uno sparatutto al neon con jME effetti particellari

Abbiamo codificato il gameplay, l'audio e l'interfaccia utente per il nostro gioco ispirato a Geometry Wars basato su jMonkeyEngine, e ora possiamo passare ad alcuni effetti grafici e lucidare. In questa parte specificamente, ci concentreremo sugli effetti particellari (incluse alcune esplosioni molto colorate).


Panoramica

Ecco a cosa stiamo lavorando per l'intera serie:


... ed ecco un video che mostra gli effetti particellari che stiamo aggiungendo in questa parte:


Ci saranno diversi tipi di particelle e diversi emettitori:

  • Quando i nemici vengono colpiti, moriranno in un'esplosione colorata.
  • Quando il giocatore muore, la sua nave esplode in una grande esplosione d'oro.
  • Il motore del giocatore emette un semplice effetto di fuoco particellare.
  • I proiettili che colpiscono il bordo dello schermo esplodono.
  • I buchi neri emettono costantemente particelle viola (in modo che sembrino più fredde).
  • Quando un buco nero perde punti ferita, emette uno scoppio di particelle colorate.

Oltre all'ultima tipologia di particelle, tutte le particelle sono influenzate dalla gravità e vengono risucchiate nei buchi neri. Quindi, quando un buco nero succhia molte particelle alla volta, inizia a brillare a causa di tutte le particelle, il che sembra piuttosto bello.

Un altro effetto che aggiungeremo è di rendere le nostre particelle più grandi e quindi più luminose più velocemente sono. Ciò significa che all'inizio un'esplosione sembra molto brillante e brillante, ma perde rapidamente la sua luminosità quando le particelle rallentano.

Per raggiungere i nostri obiettivi, dovremo aggiungere due nuove classi:

  • ParticleManager: Questa classe manager si prenderà cura degli attributi per ogni tipo di esplosione.
  • ParticleControl: Penso che tu possa già intuire che questa classe, ancora una volta, controlla il comportamento delle nostre particelle.

Cominciamo con l'effetto più evidente: i nemici che esplodono.


Esplosioni nemiche

La prima classe che dobbiamo implementare è la ParticleManager classe. Dal momento che è responsabile della generazione di particelle, ha bisogno di alcune variabili, come la guiNode, il particleNode e il standardParticle.

Cloneremo questo quando ne avremo bisogno, ma dai un'occhiata al codice di base:

 public class ParticleManager private Node guiNode; private Spatial standardParticle, glowParticle; particleNode nodo privato; rand privato a caso; ParticleManager pubblico (Node guiNode, Spatial standardParticle, Spatial glowParticle) this.guiNode = guiNode; this.standardParticle = standardParticle; this.glowParticle = glowParticle; particleNode = new Node ("particles"); guiNode.attachChild (particleNode); rand = new Random (); 

Integrazione del manager in MonkeyBlasterMain non è un grosso problema. Lo dichiariamo solo all'inizio e chiamiamo il costruttore simpleInitApp ():

 particleManager = new ParticleManager (guiNode, getSpatial ("Laser"), getSpatial ("Glow"));

Per far esplodere un nemico, dobbiamo avere il metodo giusto per farlo ParticleManager:

 public void enemyExplosion (posizione Vector3f) // init colors float hue1 = rand.nextFloat () * 6; float hue2 = (rand.nextFloat () * 2)% 6f; ColorRGBA color1 = hsvToColor (hue1, 0.5f, 1f); ColorRGBA color2 = hsvToColor (hue2, 0.5f, 1f); // crea 120 particelle per (int i = 0; i<120; i++)  Vector3f velocity = getRandomVelocity(250); Spatial particle = standardParticle.clone(); particle.setLocalTranslation(position); ColorRGBA color = new ColorRGBA(); color.interpolate(color1, color2, rand.nextFloat()*0.5f); particle.addControl(new ParticleControl(velocity,true,3100,color)); particleNode.attachChild(particle);  

Questo metodo è breve, ma fa molto, quindi lo analizzeremo passo dopo passo.

Colorare le particelle

Per rendere le nostre particelle più interessanti, assegneremo loro 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 "al neon".

Possiamo avere più controllo sui nostri colori specificandoli nel HSV (tonalità, saturazione e valore) lo spazio colore. Vogliamo selezionare i colori con una tonalità casuale ma con una saturazione e un valore fissi, per farli sembrare tutti luminosi e brillanti, quindi abbiamo bisogno di una funzione di supporto che possa produrre un colore dai valori HSV.

 public ColorRGBA hsvToColor (float h, float s, float v) if (h == 0 && s == 0) return new ColorRGBA (v, v, v, 1);  float c = s * v; float x = c * (1 - Math.abs (h% 2 - 1)); float m = v - c; se (h < 1) return new ColorRGBA(c + m, x + m, m, 1);  else if (h < 2) return new ColorRGBA(x + m, c + m, m, 1);  else if (h < 3) return new ColorRGBA(m, c + m, x + m, 1);  else if (h < 4) return new ColorRGBA(m, x + m, c + m, 1);  else if (h < 5) return new ColorRGBA(x + m, m, c + m, 1);  else return new ColorRGBA(c + m, m, x + m, 1); 

Mancia: Non preoccuparti troppo Come questa funzione funziona; basta capire che può generare un colore RGBA dal valore HSV. Il metodo va oltre lo scopo e il focus di questo tutorial.

Perché abbiamo bisogno di due colori?

Ora torniamo al nostro metodo di esplosione. Dai un'occhiata alle linee evidenziate:

 public void enemyExplosion (posizione Vector3f) // init colors float hue1 = rand.nextFloat () * 6; float hue2 = (rand.nextFloat () * 2)% 6f; ColorRGBA color1 = hsvToColor (hue1, 0.5f, 1f); ColorRGBA color2 = hsvToColor (hue2, 0.5f, 1f); // crea 120 particelle per (int i = 0; i<120; i++)  Vector3f velocity = getRandomVelocity(250); Spatial particle = standardParticle.clone(); particle.setLocalTranslation(position); ColorRGBA color = new ColorRGBA(); color.interpolate(color1, color2, rand.nextFloat()*0.5f); particle.addControl(new ParticleControl(velocity,true,3100,color)); particleNode.attachChild(particle);  

Per rendere l'esplosione più colorata, calcoliamo due colori casuali e interponiamo casualmente il colore delle particelle finali per ogni particella.

Spostare le particelle

La prossima cosa che facciamo è calcolare la velocità per ogni particella. Lo gestiamo in un metodo extra perché vogliamo che la direzione sia casuale, ma non la velocità:

 private Vector3f getRandomVelocity (float max) // genera Vector3f con direzione casuale Vector3f velocity = new Vector3f (rand.nextFloat () - 0.5f, rand.nextFloat () - 0.5f, 0) .normalizeLocal (); // applica la velocità delle particelle semi-casuale float random = rand.nextFloat () * 5 + 1; float particleSpeed ​​= max * (1f - 0.6f / random); velocity.multLocal (particleSpeed); velocità di ritorno; 

Innanzitutto, generiamo un vettore di velocità casuale e lo normalizziamo. Successivamente, calcoliamo una velocità casuale nell'intervallo tra il 40% e il 90% di max.

Ora torna al enemyExplosion () metodo. Ecco la parte che non abbiamo ancora discusso:

 Particella spaziale = standardParticle.clone (); particle.setLocalTranslation (posizione); ColorRGBA color = new ColorRGBA (); color.interpolate (color1, color2, rand.nextFloat () * 0.5f); particle.addControl (new ParticleControl (velocity, 3100, color)); particleNode.attachChild (particelle);

Noi cloniamo il standardParticle e impostare la sua traduzione all'origine dell'esplosione. Dopodiché, interponiamo il colore delle particelle tra i due casuali (come menzionato sopra). Come puoi vedere, aggiungiamo anche a ParticleControl che controllerà il comportamento della particella. Infine, dobbiamo aggiungere la particella al nodo affinché venga visualizzata.

Controllo delle particelle

Ora che il nostro ParticleManager è finito, dobbiamo implementare il ParticleControl. Parti del codice ti sembreranno familiari:

 la classe pubblica ParticleControl estende AbstractControl velocità Vector3f privata; durata della vita del galleggiante privato; privato lungo spawnTime; colore ColorRGBA privato; ParticleControl pubblico (velocità Vector3f, durata della vita flottante, colore ColorRGBA) this.velocity = velocity; questo.lifespan = durata della vita; questo colore = colore; spawnTime = System.currentTimeMillis ();  @Override protected void controlUpdate (float tpf) // movement spatial.move (velocity.mult (tpf * 3f)); velocity.multLocal (1-3f * TPF); if (Math.abs (velocity.x) + Math.abs (velocity.y) < 0.001f)  velocity = Vector3f.ZERO;  // rotation if (velocity != Vector3f.ZERO)  spatial.rotateUpTo(velocity.normalize()); spatial.rotate(0,0,FastMath.PI/2f);  // scaling and alpha float speed = velocity.length(); long difTime = System.currentTimeMillis() - spawnTime; float percentLife = 1- difTime / lifespan; float alpha = lesserValue(1.5f,lesserValue(percentLife*2,speed)); alpha *= alpha; setAlpha(alpha); spatial.setLocalScale(0.3f+lesserValue(lesserValue(1.5f,0.02f*speed+0.1f),alpha)); spatial.scale(0.65f); // is particle expired? if (difTime > durata della vita) spatial.removeFromParent ();  @Override protected void controlRender (RenderManager rm, ViewPort vp)  private float lesserValue (float a, float b) return a < b ? a : b;  private void setAlpha(float alpha)  color.set(color.r,color.g,color.b,alpha); Node spatialNode = (Node) spatial; Picture pic = (Picture) spatialNode.getChild(spatialNode.getName()); pic.getMaterial().setColor("Color",color);  

Nella parte superiore della classe, dichiariamo e inizializziamo alcune variabili; i loro nomi dovrebbero essere auto-esplicativi ormai. Se dai un'occhiata controlUpdate () troverai codice familiare: spostiamo la particella per la sua velocità, la riduciamo un po 'e la ruotiamo nella direzione della velocità.
Se la particella è molto lenta, impostiamo la sua velocità su Vector3f.ZERO. È molto più veloce eseguire calcoli con zero di un numero molto piccolo, e la differenza non è comunque visibile.

Per far esplodere davvero un'esplosione boom, renderemo la particella più grande quando si muove velocemente, che di solito è subito dopo l'esplosione. Allo stesso modo, rendiamo la particella più piccola e persino trasparente quando si muove molto lentamente o raggiunge la fine della sua vita. Per renderlo più trasparente, chiamiamo un metodo di supporto, setAlpha (float alfa).

Mancia: Se non sai come ottenere spazi spaziali per bambini e impostare il loro materiale, puoi semplicemente copiare e incollare il metodo o dare un'occhiata a SeekerControl o WandererControl dal secondo capitolo; è spiegato lì.

Ora che abbiamo finito il ParticleControl, puoi iniziare il gioco e vedere ... niente.
Sai cosa abbiamo dimenticato?

Mettendolo insieme

Quando un nemico muore, dobbiamo chiamare enemyExplosion () nel ParticleManager, altrimenti non succederà nulla! Dare un'occhiata al MonkeyBlasterMain e cerca il metodo handleCollisions (), questo è dove i nemici muoiono. Ora basta inserire la chiamata nella linea corretta:

 // ... else if (enemyNode.getChild (i) .getName (). Equals ("Wanderer")) hud.addPoints (1);  particleManager.enemyExplosion (enemyNode.getChild (i) .getLocalTranslation ()); enemyNode.detachChildAt (i); bulletNode.detachChildAt (j); sound.explosion (); rompere; // ... 

E non devi dimenticare che esiste un secondo modo in cui i nemici possono morire: quando vengono risucchiati nei buchi neri. Basta inserire la (quasi) stessa linea alcune righe più in basso quando controlliamo le collisioni con il buco nero:

 if (checkCollision (enemyNode.getChild (j), blackHole)) particleManager.enemyExplosion (enemyNode.getChild (j) .getLocalTranslation ()); enemyNode.detachChildAt (j); 

Ora puoi finalmente iniziare il gioco e giocare un po '. Quelle particelle davvero aggiungono all'atmosfera, non credi? Ma non fermiamoci su un solo effetto; ce ne sono molti altri in arrivo ...


Esplosioni di proiettili

Quando un proiettile colpisce il bordo dello schermo, lo faremo esplodere.

Dare un'occhiata al BulletControl. C'è già un codice che controlla se il proiettile è fuori dai confini dello schermo, quindi inneschiamo l'esplosione lì. Per farlo dobbiamo dichiarare il ParticleManager nel BulletControl e passalo nel costruttore:

 BulletControl pubblico (direzione Vector3f, int screenWidth, int screenHeight, ParticleManager particleManager) this.particleManager = particleManager;

Non dimenticare che devi passare il particleManager nel MonkeyBlasterMain.

Inseriremo la chiamata qui:

 if (loc.x screenWidth || loc.y> screenHeight) particleManager.bulletExplosion (loc); spatial.removeFromParent (); 

Il bulletExplosion (posizione Vector3f) il metodo è molto simile al enemyExplosion (posizione Vector3f) metodo. Le uniche differenze sono che non renderemo le particelle altrettanto veloci, e che useremo un colore fisso (un blu brillante). Inoltre, riduciamo la durata delle particelle.

 pubblico vuoto bulletExplosion (posizione Vector3f) per (int i = 0; i<30; i++)  Vector3f velocity = getRandomVelocity(175); Spatial particle = standardParticle.clone(); particle.setLocalTranslation(position); ColorRGBA color = new ColorRGBA(0.676f,0.844f,0.898f,1); particle.addControl(new ParticleControl(velocity, 1000, color)); particleNode.attachChild(particle);  

Dato che abbiamo tutto il codice necessario, è facile aggiungere nuove esplosioni, come puoi vedere. Prima di aggiungere un'altra esplosione per la morte dei giocatori, aggiungeremo una nuova funzionalità al ParticleControl.


Respingere le particelle dai bordi dello schermo

Quando un proiettile colpisce il bordo dello schermo, circa la metà delle particelle è inutile. Queste particelle non appaiono mai sullo schermo perché volano via da esso. Cambiamo questo.

Ora gireremo la velocità di ogni particella che lascia lo schermo, in modo che vengano "respinti" dai confini.

 Vector3f loc = spatial.getLocalTranslation (); se (loc.x. < 0)  velocity.x = Math.abs(velocity.x);  else if (loc.x > screenWidth) velocity.x = -Math.abs (velocity.x);  if (loc.z < 0)  velocity.y = Math.abs(velocity.y);  else if (loc.y > screenHeight) velocity.y = -Math.abs (velocity.y); 

Non invertiamo l'intero vettore, solo il X o y variabile (a seconda del confine che è stato colpito). Ciò si traduce in un effetto respingente adeguato, come una luce riflettente specchio.

Mancia: Non devi dimenticare di passare screenwidth e ScreenHeight a partire dal MonkeyBlasterMain a ParticleManager e da lì a tutti ParticleControl. Se non ti interessa il codice pulito così tanto poteva crea due variabili statiche in MonkeyBlasterMain e lavorare con loro.

Inizia il gioco e noterai che le esplosioni di proiettili sembrano molto più luminose ora. Anche le particelle delle esplosioni nemiche vengono respinte.


Esplosione del giocatore

Quando il giocatore muore, vogliamo un veramente grande esplosione che copre l'intero schermo. Chiamiamo il metodo, ancora una volta, in killPlayer () nel MonkeyBlasterMain.

 particleManager.playerExplosion (player.getLocalTranslation ());

Il codice per playerExplosion è praticamente la stessa di prima. Tuttavia, questa volta utilizziamo due colori, bianco e giallo, e interpoliamo tra loro. Impostiamo la velocità su 1000 e la durata della vita a 2800 millisecondi.

 public void playerExplosion (posizione Vector3f) ColorRGBA color1 = ColorRGBA.White; ColorRGBA color2 = ColorRGBA.Yellow; per (int i = 0; i<1200; i++)  Vector3f velocity = getRandomVelocity(1000); Spatial particle = standardParticle.clone(); particle.setLocalTranslation(position); ColorRGBA color = new ColorRGBA(); color.interpolate(color1, color2, rand.nextFloat()); particle.addControl(new ParticleControl(velocity, 2800, color, screenWidth, screenHeight)); particleNode.attachChild(particle);  

Succhiare particelle in buchi neri

Ora che abbiamo un bel po 'di effetti particellari, aggiungiamo gravità a loro. Ogni volta che si avvicinano abbastanza a un buco nero, dovrebbero essere risucchiati, ma questo non è vero per ogni particella. Più avanti, vorremmo avere un tipo di particella che viene risucchiata e un tipo che non lo fa. Pertanto, dobbiamo aggiungere un attributo alle nostre particelle:

 particle.setUserData ("affectedByGravity", true);

Tutti i tipi di particelle che abbiamo creato fino ad ora dovrebbero essere risucchiati da buchi neri, quindi puoi aggiungere questa linea a tutti i metodi in cui generiamo particelle.

Ora per la gestione della gravità. Vai a handleGravity () nel MonkeyBlasterMain -questo è dove abbiamo implementato la gravità nella terza parte della serie.

Questa volta non controlleremo se una particella è alla portata del buco nero, applicheremo semplicemente la gravità a tutti loro. Se una particella specifica è lontana, l'effetto gravitazionale non sarà comunque molto forte.

Controlliamo se la particella è influenzata dalla gravità e, se lo è, la applichiamo:

 // controlla le particelle per (int j = 0; j 

Ora, dovremo estendere applyGravity () anche:

 // ... else if (target.getName (). Equals ("Laser") || target.getName (). Equals ("Glow")) target.getControl (ParticleControl.class) .applyGravity (gravity.mult ( 15000), distanza); 

Dobbiamo controllare il nome del bersaglio per entrambi Laser e splendore, perché quelli sono due diversi tipi di particelle che avranno lo stesso comportamento.

Un'altra cosa da notare è che non solo trasferiamo il vettore di gravità modificato, ma anche la distanza dal buco nero. Questo è importante nel calcolo della forza in applyGravity () nel ParticleControl:

 Vector3f additionalVelocity = gravity.mult (1000f / (distanza * distanza + 10000f)); velocity.addLocal (additionalVelocity); se (distanza < 400)  additionalVelocity = new Vector3f(gravity.y, -gravity.x, 0).mult(3f / (distance + 100)); velocity.addLocal(additionalVelocity); 

Qui, gravità è 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 * distanza) + 10000-cioè, contiene un termine a distanza quadrata. 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 diventa maggiore di 100 pixel, (distanza * distanza) diventa rapidamente molto più grande di 10.000. Pertanto, aggiungendo 10.000 a (distanza * distanza) ha un effetto molto piccolo e la funzione si avvicina ad 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 che abbiamo fatto è aggiungere un componente laterale alla velocità quando le particelle si avvicinano abbastanza al buco nero. Questo ha due scopi: primo, fa girare le particelle in senso orario verso il buco nero; in secondo luogo, 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, prendere (v.y, -v.x). Allo stesso modo, per ruotare di 90 ° in senso antiorario, prendere (-v.y, v.x).

Questo effetto particellare sembra carino quando inizi il gioco e lo guardi, e questo è particolarmente vero quando ci sono molte esplosioni e particelle in giro. Ma quando non ci sono esplosioni, i buchi neri sembrano un po 'noiosi. Lo cambieremo presto.


Spruzzo di particelle fuori dai buchi neri

Per fare in modo che i buchi neri producano continuamente particelle, dobbiamo dare un'occhiata al controlUpdate (float tpf) metodo in BlackHoleControl. C'è un Se affermazione che verifica se il buco nero è attivo; se lo è, faremo eseguire questo codice:

 long sprayDif = System.currentTimeMillis () - lastSprayTime; if ((System.currentTimeMillis () / 250)% 2 == 0 && sprayDif> 20) lastSprayTime = System.currentTimeMillis (); Vector3f sprayVel = MonkeyBlasterMain.getVectorFromAngle (sprayAngle) .mult (rand.nextFloat () * 3 +6); Vector3f randVec = MonkeyBlasterMain.getVectorFromAngle (rand.nextFloat () * FastMath.PI * 2); randVec.multLocal (4 + rand.nextFloat () * 4); Vector3f position = spatial.getLocalTranslation (). Add (sprayVel.mult (2f)). AddLocal (randVec); particleManager.sprayParticle (posizione, sprayVel.mult (30f));  sprayAngle - = FastMath.PI * tpf / 10f;

Abbiamo un paio di nuove variabili qui. Devi dichiarare e inizializzare il long lastPrayTime, il float sprayAngle e il Rand casuale. Inoltre, è necessario dichiarare il particleManager e passarlo giù dalla classe principale in modo che possiamo effettivamente spruzzare le particelle.

Il metodo farà sì che i buchi neri spruzzino spruzzi di particelle viola che formeranno un anello luminoso e luminoso che orbita intorno al buco nero

L'attuale sprayParticle () il metodo non è niente di speciale. Creiamo una particella, applichiamo un colore viola, aggiungiamo un controllo e così via:

 public void sprayParticle (Vector3f position, Vector3f sprayVel) Spatial particle = standardParticle.clone (); particle.setLocalTranslation (posizione); ColorRGBA color = new ColorRGBA (0.8f, 0.4f, 0.8f, 1f); particle.addControl (new ParticleControl (sprayVel, 3500, color, screenWidth, screenHeight)); particle.setUserData ("affectedByGravity", true); ((Nodo) guiNode.getChild ("particelle")). AttachChild (particella); 

Avvia il gioco e guarda come appare.

Mancia: Se vuoi cambiare il comportamento circolare delle particelle, sentiti libero di giocare con i valori in applyGravity () nel ParticleControl.

Questo migliora l'aspetto generale dei buchi neri, ma non è ancora abbastanza buono! C'è un altro effetto che possiamo aggiungere a loro ...


Black Hole Explosions

Ora, non faremo esplodere i buchi neri quando moriranno. Avremo, invece, innescare un'esplosione di particelle ogni volta che un buco nero viene colpito.

Aggiungere il seguente metodo a ParticleManager:

 public void blackHoleExplosion (posizione Vector3f) float hue = ((System.currentTimeMillis () - spawnTime) * 0.003f)% 6f; int numParticles = 150; ColorRGBA color = hsvToColor (hue, 0.25f, 1); float startOffset = rand.nextFloat () * FastMath.PI * 2 / numParticles; per (int i = 0; i 

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.


Ship Exhaust Fire

Come dettato dalle leggi della fisica dei neon geometrici, la nave del giocatore si spinge facendo fuoriuscire un flusso di particelle infuocate dal suo tubo di scappamento. 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: una corrente 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 ruotano in direzioni opposte per formare un motivo incrociato e hanno un colore più rosso, mentre il flusso centrale ha un colore più caldo, giallo-bianco.

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:


Una singola particella di bagliore.

Queste particelle saranno colorate e mescolate con le particelle regolari. Il codice per l'intero effetto è mostrato di seguito:

 pubblico vuoto makeExhaustFire (posizione Vector3f, rotazione mobile) ColorRGBA midColor = new ColorRGBA (1f, 0,73f, 0,12f, 0,7f); ColorRGBA sideColor = new ColorRGBA (0,78f, 0,15f, 0,04f, 0,7f); Vector3f direction = MonkeyBlasterMain.getVectorFromAngle (rotazione); float t = (System.currentTimeMillis () - spawnTime) / 1000f; Vector3f baseVel = direction.mult (-45f); Vector3f perpVel = new Vector3f (baseVel.y, -baseVel.x, 0) .multLocal (2f * FastMath.sin (t * 10f)); Vector3f pos = position.add (MonkeyBlasterMain.getVectorFromAngle (rotation) .multLocal (-25f)); // middle stream Vector3f randVec = MonkeyBlasterMain.getVectorFromAngle (new Random (). nextFloat () * FastMath.PI * 2); Vector3f velMid = baseVel.add (randVec.mult (7.5f)); Spatial particleMid = standardParticle.clone (); particleMid.setLocalTranslation (pos); particleMid.addControl (new ParticleControl (velMid, 800, midColor, screenWidth, screenHeight)); particleMid.setUserData ("affectedByGravity", true); ((Nodo) guiNode.getChild ("particelle")). AttachChild (particleMid); Spatial particleMidGlow = glowParticle.clone (); particleMidGlow.setLocalTranslation (pos); particleMidGlow.addControl (new ParticleControl (velMid, 800, midColor, screenWidth, screenHeight)); particleMidGlow.setUserData ("affectedByGravity", true); ((Nodo) guiNode.getChild ("particelle")). AttachChild (particleMidGlow); // stream laterali Vector3f randVec1 = MonkeyBlasterMain.getVectorFromAngle (new Random (). nextFloat () * FastMath.PI * 2); Vector3f randVec2 = MonkeyBlasterMain.getVectorFromAngle (new Random (). NextFloat () * FastMath.PI * 2); Vector3f velSide1 = baseVel.add (randVec1.mult (2.4f)). AddLocal (perpVel); Vector3f velSide2 = baseVel.add (randVec2.mult (2.4f)). SubtractLocal (perpVel); Spatial particleSide1 = standardParticle.clone (); particleSide1.setLocalTranslation (pos); particleSide1.addControl (new ParticleControl (velSide1, 800, sideColor, screenWidth, screenHeight)); particleSide1.setUserData ("affectedByGravity", true); ((Nodo) guiNode.getChild ("particles")). AttachChild (particleSide1); Spatial particleSide2 = standardParticle.clone (); particleSide2.setLocalTranslation (pos); particleSide2.addControl (new ParticleControl (velSide2, 800, sideColor, screenWidth, screenHeight)); particleSide2.setUserData ("affectedByGravity", true); ((Nodo) guiNode.getChild ("particelle")). AttachChild (particleSide2); Spatial particleSide1Glow = glowParticle.clone (); particleSide1Glow.setLocalTranslation (pos); particleSide1Glow.addControl (new ParticleControl (velSide1, 800, sideColor, screenWidth, screenHeight)); particleSide1Glow.setUserData ("affectedByGravity", true); ((Nodo) guiNode.getChild ("particelle")). AttachChild (particleSide1Glow); Spatial particleSide2Glow = glowParticle.clone (); particleSide2Glow.setLocalTranslation (pos); particleSide2Glow.addControl (new ParticleControl (velSide2, 800, sideColor, screenWidth, screenHeight)); particleSide2Glow.setUserData ("affectedByGravity", true); ((Nodo) guiNode.getChild ("particelle")). AttachChild (particleSide2Glow); 

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: una particella standard e una particella luminescente dietro di essa.

Inserisci questo bit di codice PlayerControl, alla fine di controlUpdate (float tpf):

 if (up || down || left || right) particleManager.makeExhaustFire (spatial.getLocalTranslation (), rotation); 

Ovviamente non devi dimenticare di passare il particleManager a partire dal MonkeyBlasterMain.


Conclusione

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