Haz un Neon Vector Shooter con jME Efectos de partículas

Hemos codificado la jugabilidad, el audio y la interfaz de usuario de nuestro juego inspirado en Geometry Wars basado en jMonkeyEngine, y ahora podemos recurrir a algunos efectos gráficos pulidos y pulir. En esta parte específicamente, nos centraremos en los efectos de partículas (incluidas algunas explosiones muy coloridas).


Visión general

Esto es para lo que estamos trabajando en toda la serie:


... y aquí hay un video que muestra los efectos de partículas que estamos agregando en esta parte:


Habrá diferentes tipos de partículas, así como diferentes emisores:

  • Cuando los enemigos son golpeados, morirán en una colorida explosión..
  • Cuando el jugador muere, su nave explota en una gran explosión dorada..
  • El motor del jugador emite un simple efecto de fuego de partículas..
  • Las balas que golpean el borde de la pantalla explotan..
  • Los agujeros negros emiten constantemente partículas de color púrpura (para que se vean más frías).
  • Cuando un agujero negro pierde puntos de golpe, emite una explosión de partículas de colores.

Además del último tipo de partícula, todas las partículas son afectadas por la gravedad y son absorbidas por los agujeros negros. Entonces, cuando un agujero negro aspira muchas partículas a la vez, comienza a brillar debido a todas las partículas, lo que se ve muy bien.

Otro efecto que agregaremos es hacer que nuestras partículas se vuelvan más grandes y, por lo tanto, más brillantes cuanto más rápido sean. Esto significará que una explosión se ve muy brillante al principio, pero pierde su brillo rápidamente una vez que las partículas disminuyen su velocidad..

Para lograr nuestros objetivos, tendremos que agregar dos nuevas clases:

  • Administrador de partículas: Esta clase de gerente se hará cargo de los atributos para cada tipo de explosión.
  • Control de partículas: Creo que ya puedes adivinar que esta clase, una vez más, controla el comportamiento de nuestras partículas..

Comencemos con el efecto más notable: explotar enemigos..


Explosiones enemigas

La primera clase que necesitamos implementar es la Administrador de partículas clase. Ya que es responsable de desovar partículas, necesita algunas variables, como la guiNodo, la nodo de partículas y el artículo estándar.

Clonaremos esto cuando lo necesitemos, pero echemos un vistazo al código básico:

 clase pública ParticleManager nodo privado guiNode; Estándar espacial privadoParte, glowPartícula; nodo privado nodeNode; rand aleatorio privado; público ParticleManager (nodo guiNode, Spatial standardParticle, Spatial glowParticle) this.guiNode = guiNode; this.standardParticle = standardParticle; this.glowParticle = glowParticle; partículaNodo = nuevo nodo ("partículas"); guiNode.attachChild (particleNode); rand = new Random (); 

Integrando al gerente en MonkeyBlasterMain no es gran cosa Simplemente lo declaramos al principio y llamamos al constructor en simpleInitApp ():

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

Para hacer explotar realmente a un enemigo, necesitamos tener el método correcto para hacer esto en el Administrador de partículas:

 Public void enemyExplosion (Vector3f position) // 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); // crear 120 partículas para (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);  

Este método es corto, pero hace mucho, así que lo veremos paso a paso.

Colorear las particulas

Para hacer que nuestras partículas sean más interesantes, les asignaremos colores al azar..

Un método para producir colores aleatorios es elegir los componentes rojo, azul y verde al azar, pero esto producirá muchos colores apagados y nos gustaría que nuestras partículas tuvieran una apariencia de "luz de neón".

Podemos obtener más control sobre nuestros colores especificándolos en el HSV (matiz, saturación y valor) espacio de color. Nos gustaría elegir colores con un tono aleatorio pero con una saturación y un valor fijos, para que todos se vean brillantes y brillantes, por lo que necesitamos una función auxiliar que pueda producir un color a partir de los valores del VHS..

 ColorRGBA público hsvToColor (float h, float s, float v) if (h == 0 && s == 0) devolver nuevo ColorRGBA (v, v, v, 1);  float c = s * v; float x = c * (1 - Math.abs (h% 2 - 1)); float m = v - c; si (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); 

Propina: No te preocupes demasiado por cómo esta función funciona; acaba de comprender que puede generar un color RGBA a partir del valor HSV. El método está fuera del alcance y enfoque de este tutorial..

¿Por qué necesitamos dos colores??

Ahora volvamos a nuestro método de explosión. Echa un vistazo a las líneas resaltadas:

 Public void enemyExplosion (Vector3f position) // 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); // crear 120 partículas para (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);  

Para hacer que la explosión sea más colorida, calculamos dos colores aleatorios e interpolamos el color final de la partícula al azar para cada partícula.

Haciendo que las partículas se muevan

Lo siguiente que hacemos es calcular la velocidad para cada partícula. Manejamos esto en un método adicional porque queremos que la dirección sea aleatoria, pero no la velocidad:

 privado Vector3f getRandomVelocity (float max) // genera Vector3f con dirección aleatoria Vector3f velocidad = nuevo Vector3f (rand.nextFloat () - 0.5f, rand.nextFloat () - 0.5f, 0) .normalizeLocal (); // aplicar velocidad de partícula semi-aleatoria float random = rand.nextFloat () * 5 + 1; velocidad de partículas flotante = máx * (1f - 0.6f / aleatorio); velocity.multLocal (particleSpeed); velocidad de retorno 

Primero, generamos un vector de velocidad aleatorio y lo normalizamos. A continuación, calculamos una velocidad aleatoria en el rango entre 40% y 90% de max.

Ahora de vuelta a la enemigoExplosión () método. Aquí está la parte que aún no hemos discutido:

 Partícula espacial = standardParticle.clone (); particle.setLocalTranslation (posición); ColorRGBA color = nuevo ColorRGBA (); color.interpolate (color1, color2, rand.nextFloat () * 0.5f); particle.addControl (nuevo ParticleControl (velocidad, 3100, color)); particleNode.attachChild (partícula);

Clonamos el artículo estándar Y establece su traducción al origen de la explosión. Después de eso, interpolamos el color de la partícula entre los dos al azar (como se mencionó anteriormente). Como puedes ver, también agregamos un Control de partículas Eso controlará el comportamiento de la partícula. Finalmente, necesitamos agregar la partícula al nodo para que se muestre.

Controlando las Partículas

Ahora que nuestro Administrador de partículas está terminado, necesitamos implementar el Control de partículas. Las partes del código le resultarán familiares:

 la clase pública ParticleControl extiende AbstractControl private Vector3f speed; vida útil del flotador privado; tiempo de desove privado largo; ColorRGBA privado color; ParticleControl público (velocidad Vector3f, vida útil del flotador, color ColorRGBA) this.velocity = speed; this.lifespan = lifespan; this.color = color; spawnTime = System.currentTimeMillis ();  @ Anular el control 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 > vida útil) spatial.removeFromParent ();  @ Anular el control void protegidoRender (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);  

En la parte superior de la clase, declaramos e inicializamos algunas variables; sus nombres deben ser autoexplicativos por ahora. Si echas un vistazo a controlUpdate () Encontrará un código familiar: movemos la partícula por su velocidad, la desaceleramos un poco y la giramos en la dirección de la velocidad.
Si la partícula es muy lenta, ajustamos su velocidad a Vector3f.ZERO. Es mucho más rápido hacer cálculos con cero que con un número muy pequeño, y la diferencia no es visible de todos modos.

Para hacer explotar realmente una explosión. auge, Haremos que la partícula sea más grande cuando se mueva rápido, lo que generalmente ocurre justo después de la explosión. De la misma manera, hacemos que la partícula sea más pequeña e incluso transparente cuando se mueve muy lentamente o llega al final de su vida útil. Para hacerlo más transparente llamamos un método de ayuda., setAlpha (float alpha).

Propina: Si no sabe cómo obtener materiales especiales para niños y configurar su material, puede simplemente copiar y pegar el método o echar un vistazo a SeekerControl o WandererControl del segundo capitulo; se explica allí.

Ahora que hemos terminado el Control de partículas, puedes comenzar el juego y ver ... nada.
¿Sabes lo que hemos olvidado??

Poniendo todo junto

Cuando un enemigo muere, necesitamos llamar enemigoExplosión () en el Administrador de partículas, ¡De lo contrario no pasará nada! Echa un vistazo a MonkeyBlasterMain y busca el método manejarColisiones (), aquí es donde mueren los enemigos. Ahora simplemente inserte la llamada en la línea derecha:

 // ... else if si (enemyNode.getChild (i) .getName (). Es igual a ("Wanderer")) hud.addPoints (1);  particleManager.enemyExplosion (enemyNode.getChild (i) .getLocalTranslation ()); enemyNode.detachChildAt (i); bulletNode.detachChildAt (j); sonido.explosión (); descanso; //… 

Y no debes olvidar que hay una segunda forma en que los enemigos pueden morir: cuando son absorbidos por agujeros negros. Simplemente inserte la (casi) misma línea unas líneas más abajo cuando verifiquemos las colisiones con el agujero negro:

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

Ahora puedes finalmente comenzar el juego y jugar un poco. Esas partículas realmente se agregan a la atmósfera, ¿no te parece? Pero no nos detengamos en un efecto; Hay muchos más por venir…


Explosiones de bala

Cuando una bala golpea el borde de la pantalla, también la haremos explotar..

Echa un vistazo a BulletControl. Ya existe un código que verifica si la bala está fuera de los límites de la pantalla, así que disparemos la explosión allí. Para ello debemos declarar la Administrador de partículas en BulletControl Y pasarlo en el constructor.

 Public BulletControl (Vector3f direction, int screenWidth, int screenHeight, ParticleManager particleManager) this.particleManager = particleManager;

No olvides que necesitas pasar el Administrador de partículas en MonkeyBlasterMain.

Insertaremos la llamada aquí:

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

los bulletExplosion (Vector3f posición) método es muy similar al enemigoExplosión (Vector3f posición) método. Las únicas diferencias son que no haremos que las partículas sean tan rápidas y que usamos un color fijo (un azul brillante). Además, disminuimos la vida útil de las partículas..

 Public void bulletExplosion (Vector3f position) for (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);  

Como tenemos todo el código necesario en su lugar, es fácil agregar nuevas explosiones, como puede ver. Antes de agregar otra explosión para la muerte de los jugadores, agregaremos una nueva funcionalidad a la Control de partículas.


Repeler partículas de los bordes de la pantalla

Cuando una bala golpea el borde de la pantalla, aproximadamente la mitad de las partículas son inútiles. Esas partículas nunca aparecen realmente en la pantalla porque vuelan lejos de ella. Vamos a cambiar eso.

Ahora giraremos la velocidad de cada partícula que abandona la pantalla, para que sean "repelidas" por los límites..

 Vector3f loc = spatial.getLocalTranslation (); si (loc.x) < 0)  velocity.x = Math.abs(velocity.x);  else if (loc.x > ancho de pantalla) velocity.x = -Math.abs (velocity.x);  si (loc.z < 0)  velocity.y = Math.abs(velocity.y);  else if (loc.y > screenHeight) velocity.y = -Math.abs (velocity.y); 

No invertimos todo el vector, solo el X o y variable (dependiendo del borde que fue golpeado). Esto produce un efecto repelente adecuado, como un espejo que refleja la luz..

Propina: No debes olvidar pasar. Ancho de pantalla y screenHeight desde MonkeyBlasterMain a Administrador de partículas y de allí a cada Control de partículas. Si no te importa tanto el código limpio, podría hacer dos variables estáticas en MonkeyBlasterMain y trabajar con ellos.

Inicia el juego y notarás que las explosiones de bala se ven mucho más brillantes ahora. Las partículas de las explosiones enemigas también son repelidas..


Explosion jugador

Cuando el jugador muere, queremos un De Verdad Gran explosión que cubre toda la pantalla. Llamamos al método, una vez más, en killPlayer () en MonkeyBlasterMain.

 particleManager.playerExplosion (player.getLocalTranslation ());

El código para jugadorExplosion es más o menos lo mismo que antes. Sin embargo, esta vez usamos dos colores, blanco y amarillo, e interpolamos entre ellos. Ajustamos la velocidad a 1000 y la vida útil de 2800 milisegundos.

 Public void playerExplosion (Vector3f position) ColorRGBA color1 = ColorRGBA.White; ColorRGBA color2 = ColorRGBA.Amarillo; para (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);  

Chupar partículas en agujeros negros

Ahora que tenemos bastantes efectos de partículas, agreguemos gravedad a ellos. Siempre que se acerquen lo suficiente a un agujero negro, deben ser aspirados, pero esto no es cierto para todas las partículas. Más adelante, querremos tener un tipo de partícula que se aspire y un tipo que no. Por lo tanto, necesitamos agregar un atributo a nuestras partículas:

 particle.setUserData ("afectadoByGravity", verdadero);

Todos los tipos de partículas que hemos creado hasta ahora deben ser absorbidos por agujeros negros, para que pueda agregar esta línea a cada método en el que generamos partículas..

Ahora al manejo de la gravedad. Ir manejar la gravedad () en MonkeyBlasterMain -Aquí es donde implementamos la gravedad en la tercera parte de la serie..

Esta vez, no comprobaremos si una partícula está al alcance del agujero negro, simplemente aplicaremos la gravedad a todas ellas. Si una partícula específica está lejos, el efecto gravitatorio no será muy fuerte de todos modos.

Verificamos si la partícula está afectada por la gravedad y, si lo está, la aplicamos:

 // marcar Partículas para (int j = 0; j 

Ahora, tendremos que extender applyGravity () también:

 //… else if (target.getName (). Es igual a ("Laser") || target.getName (). Es igual a ("Glow")) target.getControl (ParticleControl.class) .applyGravity (gravity.mult ( 15000), distancia); 

Necesitamos verificar el nombre del objetivo para ambos Láser y Brillar, Porque esos son dos tipos diferentes de partículas que tendrán el mismo comportamiento..

Otra cosa a tener en cuenta es que no solo transmitimos el vector de gravedad modificado, sino también la distancia al agujero negro. Esto es importante en el cálculo de la fuerza en applyGravity () en Control de partículas:

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

aquí, gravedad Es el vector unitario que apunta hacia el agujero negro. La fuerza atractiva es una versión modificada de la función del cuadrado inverso..

La primera modificación es que el denominador es (distancia * distancia) + 10000-es decir, contiene un término de distancia al cuadrado. Esto hace que la fuerza de atracción se aproxime a un valor máximo en lugar de tender al infinito a medida que la distancia se vuelve muy pequeña..

Cuando la distancia supera los 100 píxeles, (distancia * distancia) Rápidamente se vuelve mucho mayor que 10,000. Por lo tanto, sumando 10,000 a (distancia * distancia) tiene un efecto muy pequeño, y la función se aproxima a una función cuadrada inversa normal.

Sin embargo, cuando la distancia es mucho menor que 100 píxeles, la distancia tiene un pequeño efecto sobre el valor del denominador, y la ecuación se vuelve aproximadamente igual a:

 vel + = n;

La segunda modificación que hemos hecho es agregar un componente lateral a la velocidad cuando las partículas se acercan lo suficiente al agujero negro. Esto tiene dos propósitos: primero, hace que las partículas formen una espiral en sentido horario hacia el agujero negro; segundo, cuando las partículas se acercan lo suficiente, alcanzarán el equilibrio y formarán un círculo brillante alrededor del agujero negro.

Propina: Para rotar un vector, v, 90 ° en el sentido de las agujas del reloj, tomar (v.y, -v.x). Del mismo modo, para girar 90 ° en sentido antihorario, tome (-v.y, v.x).

Este efecto de partículas parece bonito cuando comienzas el juego y lo miras, y esto es especialmente cierto cuando hay muchas explosiones y partículas alrededor. Pero cuando no hay explosiones, los agujeros negros parecen aburridos. Vamos a cambiar eso pronto.


Rociar partículas de agujeros negros

Para hacer que los agujeros negros produzcan partículas de manera continua, necesitamos echar un vistazo a la controlUpdate (float tpf) método en BlackHoleControl. Hay un Si declaración que verifica si el agujero negro está activo; Si es así, lo haremos ejecutar este código:

 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 (position, sprayVel.mult (30f));  sprayAngle - = FastMath.PI * tpf / 10f;

Tenemos un par de nuevas variables aquí. Necesitas declarar e inicializar el long lastSprayTime, la espray flotador y el Rand aleatorio. Además, necesitas declarar la Administrador de partículas y pasarlo de la clase principal para que podamos rociar las partículas.

El método hará que los agujeros negros rocíen chorros de partículas púrpuras que formarán un anillo brillante que orbita alrededor del agujero negro.

El actual sprayParticle () El método no es nada especial. Creamos una partícula, aplicamos un color púrpura, agregamos un control y así sucesivamente:

 Public void sprayParticle (Vector3f position, Vector3f sprayVel) Spatialticle = standardParticle.clone (); particle.setLocalTranslation (posición); ColorRGBA color = nuevo ColorRGBA (0.8f, 0.4f, 0.8f, 1f); particle.addControl (nuevo ParticleControl (sprayVel, 3500, color, screenWidth, screenHeight)); particle.setUserData ("afectadoByGravity", verdadero); ((Nodo) guiNode.getChild ("partículas")). AttachChild (partícula); 

Arranca el juego y mira como se ve..

Propina: Si desea cambiar el comportamiento en círculo de las partículas, siéntase libre de jugar con los valores en applyGravity () en Control de partículas.

Esto mejora el aspecto general de los agujeros negros, ¡pero aún no es lo suficientemente bueno! Hay otro efecto que podemos añadir a ellos ...


Explosiones de agujero negro

Ahora, no haremos explotar los agujeros negros cuando mueran. En vez de eso, dispararemos una explosión de partículas cada vez que un agujero negro sea golpeado..

Agregue el siguiente método a Administrador de partículas:

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

Esto funciona principalmente de la misma manera que las otras explosiones de partículas. Una diferencia es que elegimos el tono del color en función del tiempo total transcurrido del juego. Si disparas al agujero negro varias veces en rápida sucesión, verás que el tono de las explosiones gira gradualmente. Esto parece menos desordenado que el uso de colores aleatorios mientras se permite la variación.


Fuego de escape de la nave

Según lo dictado por las leyes de la física geométrica-neón, la nave del jugador se propulsa lanzando un chorro de partículas ardientes desde su tubo de escape. Con nuestro motor de partículas en su lugar, este efecto es fácil de hacer y agrega un toque visual al movimiento de la nave.

A medida que la nave se mueve, creamos tres corrientes de partículas: una corriente central que se dispara directamente desde la parte posterior de la nave, y dos corrientes laterales cuyos ángulos giran de un lado a otro en relación con la nave. Las dos corrientes laterales giran en direcciones opuestas para hacer un patrón entrecruzado, y tienen un color más rojo, mientras que la corriente central tiene un color más caliente, amarillo-blanco.

Para hacer que el fuego brille más intensamente de lo que lo haría solo con la floración, haremos que la nave emita partículas adicionales con este aspecto:


Una sola partícula de brillo..

Estas partículas se teñirán y se mezclarán con las partículas regulares. El código para el efecto completo se muestra a continuación:

 public void makeExhaustFire (Vector3f posición, flotación de rotación) ColorRGBA midColor = nuevo ColorRGBA (1f, 0.73f, 0.12f, 0.7f); ColorRGBA sideColor = nuevo ColorRGBA (0.78f, 0.15f, 0.04f, 0.7f); Vector3f dirección = MonkeyBlasterMain.getVectorFromAngle (rotación); float t = (System.currentTimeMillis () - spawnTime) / 1000f; Vector3f baseVel = direction.mult (-45f); Vector3f perpVel = nuevo Vector3f (baseVel.y, -baseVel.x, 0) .multLocal (2f * FastMath.sin (t * 10f)); Vector3f pos = position.add (MonkeyBlasterMain.getVectorFromAngle (rotación) .multLocal (-25f)); // flujo intermedio 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 (nuevo ParticleControl (velMid, 800, midColor, screenWidth, screenHeight)); particleMid.setUserData ("afectadoByGravity", verdadero); ((Nodo) guiNode.getChild ("partículas")). AttachChild (particleMid); SpatialticleMidGlow = glowParticle.clone (); particleMidGlow.setLocalTranslation (pos); particleMidGlow.addControl (nuevo ParticleControl (velMid, 800, midColor, screenWidth, screenHeight)); particleMidGlow.setUserData ("afectadoByGravity", verdadero); ((Nodo) guiNode.getChild ("partículas")). AttachChild (particleMidGlow); // flujos laterales 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 (nuevo ParticleControl (velSide1, 800, sideColor, screenWidth, screenHeight)); particleSide1.setUserData ("fectadoByGravity ", verdadero); ((Nodo) guiNode.getChild ("partículas")). AttachChild (particleSide1); Spatial particleSide2 = standardParticle.clone (); particleSide2.setLocalTranslation (pos); particleSide2.addControl (nuevo ParticleControl (velSide2, 800, sideColor, screenWidth, screenHeight)); particleSide2.setUserData ("fectadoByGravity ", verdadero); ((Nodo) guiNode.getChild ("partículas")). AttachChild (particleSide2); Spatial particleSide1Glow = glowParticle.clone (); particleSide1Glow.setLocalTranslation (pos); particleSide1Glow.addControl (nuevo ParticleControl (velSide1, 800, sideColor, screenWidth, screenHeight)); particleSide1Glow.setUserData ("afectadoByGravity", verdadero); ((Nodo) guiNode.getChild ("partículas")). AttachChild (particleSide1Glow); Spatial particleSide2Glow = glowParticle.clone (); particleSide2Glow.setLocalTranslation (pos); particleSide2Glow.addControl (nuevo ParticleControl (velSide2, 800, sideColor, screenWidth, screenHeight)); particleSide2Glow.setUserData ("afectadoByGravity", verdadero); ((Nodo) guiNode.getChild ("partículas")). AttachChild (particleSide2Glow); 

No hay nada furtivo en este código. Utilizamos una función sinusoidal para producir el efecto de giro en las corrientes laterales variando su velocidad de lado a lo largo del tiempo. Para cada flujo, creamos dos partículas superpuestas por fotograma: una partícula estándar y una partícula luminosa detrás de ella.

Inserta este bit de código en Control de jugador, al final de controlUpdate (float tpf):

 if (arriba || abajo || izquierda || derecha) particleManager.makeExhaustFire (spatial.getLocalTranslation (), rotación); 

Por supuesto que no hay que olvidar pasar el Administrador de partículas desde MonkeyBlasterMain.


Conclusión

Con todos estos efectos de partículas, Shape Blaster está empezando a verse muy bien. En la parte final de esta serie, agregaremos un efecto impresionante más: la cuadrícula de fondo combada