Crea uno sparatutto al neon con JME Warping Grid

Nella serie finora, abbiamo codificato il gameplay, aggiunto i nemici e arricchito le cose con effetti di fioritura e di particelle. In questa parte finale creeremo una griglia di sfondo dinamica e deformante.


Panoramica

Questo video mostra la griglia in azione:


Creeremo la griglia usando una simulazione a molla: ad ogni intersezione della griglia, posizioneremo un piccolo peso (una massa puntiforme) e collegheremo questi pesi usando le molle. Queste molle tirano e non spingono mai, proprio come un elastico. Per mantenere la griglia in posizione, le masse attorno al bordo della griglia saranno ancorate al loro posto.

Di seguito è riportato un diagramma del layout.


Creeremo una classe chiamata Griglia per creare questo effetto. Tuttavia, prima di lavorare sulla griglia stessa, dobbiamo creare due classi helper: Primavera e PointMass.


La classe PointMass

Il PointMass la classe rappresenta le masse alle quali legheremo le molle. Le molle non si collegano mai direttamente ad altre molle. Invece, applicano una forza alle masse che collegano, che a loro volta possono allungare altre molle.

 classe pubblica PointMass posizione privata Vector3f; private Vector3f velocity = Vector3f.ZERO; private float inverseMass; private Vector3f acceleration = Vector3f.ZERO; smorzamento del galleggiante privato = 0.98f; PointMass pubblico (posizione Vector3f, float inverseMass) this.position = position; this.inverseMass = inverseMass;  public void applyForce (Forza Vector3f) acceleration.addLocal (force.mult (inverseMass));  public void increaseDamping (float factor) damping * = factor;  public void update (float tpf) velocity.addLocal (acceleration.mult (1f)); position.addLocal (velocity.mult (0.6f)); acceleration = Vector3f.ZERO.clone (); if (velocity.lengthSquared () < 0.0001f)  velocity = Vector3f.ZERO.clone();  velocity.multLocal(damping); damping = 0.98f; damping = 0.8f; position.z *= 0.9f; if (position.z < 0.01) position.z = 0;  public Vector3f getPosition()  return position;  public Vector3f getVelocity()  return velocity;  

Ci sono alcuni punti interessanti su questa classe. Innanzitutto, nota che memorizza il inverso della massa, 1 / massa. Questa è spesso una buona idea nelle simulazioni di fisica perché le equazioni fisiche tendono a utilizzare l'inverso della massa più spesso e perché ci offre un modo semplice per rappresentare oggetti infinitamente pesanti e immobili impostando la massa inversa a zero.

La classe contiene anche a smorzamento variabile, che agisce per rallentare gradualmente la massa verso il basso. Questo è usato approssimativamente come attrito o resistenza all'aria. Questo aiuta a far riposare la griglia e aumenta anche la stabilità della simulazione della molla.

Il Aggiornare() metodo fa il lavoro di spostare il punto di massa ogni fotogramma. Inizia facendo un'integrazione Eulero simplettale, il che significa che aggiungiamo l'accelerazione alla velocità e quindi aggiungiamo la velocità aggiornata alla posizione. Questo differisce dall'integrazione standard di Eulero in cui aggiorneremmo la velocità dopo aggiornare la posizione.

Mancia: Eulero simplettico è migliore per le simulazioni di primavera perché risparmia energia. Se si utilizza l'integrazione Eulero regolare e si creano molle senza smorzamento, tenderanno ad allungarsi ulteriormente ogni volta rimbalzando man mano che guadagnano energia, rompendo alla fine la simulazione.

Dopo aver aggiornato la velocità e la posizione, controlliamo se la velocità è molto piccola e, se lo è, la impostiamo su zero. Questo può essere importante per le prestazioni a causa della natura dei numeri in virgola mobile denormalizzati.

Il IncreaseDamping () il metodo è usato per aumentare temporaneamente la quantità di smorzamento. Lo useremo più tardi per determinati effetti.


La lezione di primavera

Una molla collega due masse puntiformi e, se allungata oltre la sua lunghezza naturale, applica una forza che unisce le masse. Le molle seguono una versione modificata della legge di Hooke con smorzamento:

\ [f = -kx - bv \]

  • \ (f \) è la forza prodotta dalla molla.
  • \ (k \) è la costante di primavera o la "rigidità" della molla.
  • \ (x \) è la distanza in cui la molla è allungata oltre la sua lunghezza naturale.
  • \ (b \) è il fattore di smorzamento.
  • \ (v \) è la velocità.

Il codice per il Primavera la classe è la seguente:

 classe pubblica Spring private PointMass end1; PointMass privato end2; private float targetLength; rigidità del galleggiante privata; smorzamento del galleggiante privato; public Spring (PointMass end1, PointMass end2, float rigidezza, float damping, Nodo gridNode, booleano visibile, Geometry defaultLine) this.end1 = end1; this.end2 = end2; this.stiffness = rigidità; questo.damping = smorzamento; targetLength = end1.getPosition (). distance (end2.getPosition ()) * 0.95f; if (visible) defaultLine.addControl (new LineControl (end1, end2)); gridNode.attachChild (defaultLine);  aggiornamento pubblico vuoto (float tpf) Vector3f x = end1.getPosition (). sottrarre (end2.getPosition ()); float length = x.length (); if (length> targetLength) x.normalizeLocal (); x.multLocal (length - targetLength); Vector3f dv = end2.getVelocity (). Sottrarre (end1.getVelocity ()); Vector3f force = x.mult (rigidità); force.subtract (dv.mult (smorzamento / 10f)); end1.applyForce (force.negate ()); end2.applyForce (forza); 

Quando creiamo una molla, impostiamo la lunghezza naturale della molla leggermente inferiore alla distanza tra i due punti finali. Ciò mantiene la griglia ben tesa, anche a riposo, e migliora un po 'l'aspetto.

Il Aggiornare() il metodo controlla innanzitutto se la molla è allungata oltre la sua lunghezza naturale. Se non è allungato, non succede nulla. Se lo è, usiamo la legge di Hooke modificata per trovare la forza dalla molla e applicarla alle due masse connesse.

C'è un'altra classe che dobbiamo creare per visualizzare correttamente le linee. Il LineControl si prenderà cura di spostare, ridimensionare e ruotare le linee:

 public class LineControl estende AbstractControl PointMass privato end1, end2; public LineControl (PointMass end1, PointMass end2) this.end1 = end1; this.end2 = end2;  @Override protected void controlUpdate (float tpf) // movement spatial.setLocalTranslation (end1.getPosition ()); // scale Vector3f dif = end2.getPosition (). sottrarre (end1.getPosition ()); spatial.setLocalScale (dif.length ()); // rotation spatial.lookAt (end2.getPosition (), new Vector3f (1,0,0));  @Override protected void controlRender (RenderManager rm, ViewPort vp) 

Creare la griglia

Ora che abbiamo le classi nidificate necessarie, siamo pronti a creare la griglia. Iniziamo creando PointMass oggetti ad ogni intersezione sulla griglia. Creiamo anche un'ancora immobile PointMass oggetti per mantenere la griglia in posizione. Quindi colleghiamo le masse con le molle:

 public class Grid private Node gridNode; sorgenti primaverili private []; punti PointMass [] [] privati; Geometria privata defaultLine; private Geometry thickLine; Griglia pubblica (Dimensione rettangolo, Spaziatura Vector2f, Nodo guiNode, AssetManager assetManager) gridNode = new Node (); guiNode.attachChild (gridNode); defaultLine = createLine (1f, assetManager); thickLine = createLine (3f, assetManager); ArrayList springList = new ArrayList (); rigidità di galleggiamento = 0,28f; smorzamento del galleggiante = 0,06f; int numColumns = (int) (size.width / spacing.x) + 2; int numRows = (int) (size.height / spacing.y) + 2; points = new PointMass [numColumns] [numRows]; PointMass [] [] fixedPoints = new PointMass [numColumns] [numRows]; // crea il punto masses float xCoord = 0, yCoord = 0; for (int row = 0; row < numRows; row++)  for (int column = 0; column < numColumns; column++)  points[column][row] = new PointMass(new Vector3f(xCoord,yCoord,0),1); fixedPoints[column][row] = new PointMass(new Vector3f(xCoord,yCoord,0),0); xCoord += spacing.x;  yCoord += spacing.y; xCoord = 0;  // link the point masses with springs Geometry line; for (int y=0; y 0) if (y% 3 == 0) line = thickLine;  else line = defaultLine;  springList.add (new Spring (points [x-1] [y], points [x] [y], rigidità, damping, gridNode, true, line.clone ()));  if (y> 0) if (x% 3 == 0) line = thickLine;  else line = defaultLine;  springList.add (new Spring (points [x] [y-1], points [x] [y], rigidità, damping, gridNode, true, line.clone ())); 

Il primo per loop crea sia masse regolari che masse immobili a ogni intersezione della griglia. In realtà non useremo tutte le masse inamovibili, e le masse inutilizzate saranno semplicemente spazzatura raccolte ad un certo punto dopo la fine del costruttore. Potremmo ottimizzare evitando di creare oggetti non necessari, ma dato che la griglia di solito viene creata solo una volta, non farà molta differenza.

Oltre a utilizzare le masse del punto di ancoraggio attorno al bordo della griglia, utilizzeremo anche alcune masse di ancoraggio all'interno della griglia. Questi saranno usati per aiutare molto delicatamente a riportare la griglia nella sua posizione originale dopo essere stata deformata.

Poiché i punti di ancoraggio non si spostano mai, non è necessario aggiornarli ogni fotogramma. Possiamo semplicemente collegarli alle sorgenti e dimenticarli. Pertanto, non abbiamo una variabile membro in Griglia classe per queste masse.

Ci sono un certo numero di valori che puoi modificare nella creazione della griglia. I più importanti sono la rigidità e lo smorzamento delle molle. La rigidità e lo smorzamento delle ancore del bordo e degli ancoraggi interni sono regolati indipendentemente dalle molle principali. Valori di rigidità più alti faranno oscillare le molle più velocemente, e valori di smorzamento più alti faranno rallentare le molle più velocemente.

C'è un'ultima cosa da menzionare: il createLine () metodo.

 geometria privata createLine (spessore float, assetmanager AssetManager) Vector3f [] vertici = nuovo Vector3f (0,0,0), nuovo Vector3f (0,0,1); int [] indici = 0,1; Mesh lineMesh = new Mesh (); lineMesh.setMode (Mesh.Mode.Lines); lineMesh.setLineWidth (spessore); lineMesh.setBuffer (VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer (vertici)); lineMesh.setBuffer (VertexBuffer.Type.Index, 1, BufferUtils.createIntBuffer (indici)); lineMesh.updateBound (); Geometry lineGeom = new Geometry ("lineMesh", lineMesh); Materiale matWireframe = new Material (assetManager, "Common / MatDefs / Misc / Unshaded.j3md"); . MatWireframe.getAdditionalRenderState () setFaceCullMode (RenderState.FaceCullMode.Off); matWireframe.setColor ("Color", new ColorRGBA (0.118f, 0.118f, 0.545f, 0.25f)); . MatWireframe.getAdditionalRenderState () setBlendMode (BlendMode.AlphaAdditive); lineGeom.setMaterial (matWireframe); return lineGeom; 

Qui, praticamente creiamo una linea specificando i vertici della linea e l'ordine dei vertici, creando una mesh, aggiungendo un materiale blu e così via. Se vuoi capire esattamente il processo di creazione della linea, puoi sempre dare un'occhiata ai tutorial di jME.

Perché la creazione della linea deve essere così complicata, non è "solo" una linea semplice? Sì, lo è, ma devi guardare a ciò che jME intende essere. Di solito, nei giochi 3D, non ci sono linee singole o triangoli nel gioco, ma piuttosto modelli con trame e animazioni. Quindi, mentre è possibile generare una singola riga in jME, l'obiettivo principale è l'importazione di modelli che sono stati generati con altri software, come Blender.

Manipolare la griglia

Affinché la griglia si muova, dobbiamo aggiornarla ogni fotogramma. Questo è molto semplice, dato che abbiamo già fatto tutto il duro lavoro nel PointMass e Primavera classi.

 aggiornamento pubblico vuoto (float tpf) for (int i = 0; i 

Ora, aggiungeremo alcuni metodi che manipolano la griglia. Puoi aggiungere metodi per qualsiasi tipo di manipolazione che puoi immaginare. Implementeremo tre tipi di manipolazioni qui: spingendo parte della griglia in una data direzione, spingendo la griglia verso l'esterno da qualche punto e tirando la griglia verso un certo punto. Tutti e tre influenzeranno la griglia all'interno di un dato raggio da qualche punto bersaglio.

Di seguito sono riportate alcune immagini di queste manipolazioni in azione:


Onda creata spingendo la griglia lungo l'asse z.
Pallottole che respingono la griglia verso l'esterno.
Succhiare la griglia verso l'interno.

E qui ci sono i metodi per gli effetti:

 public void applyDirectedForce (forza Vector3f, posizione Vector3f, raggio float) per (int x = 0; x 

Utilizzando la griglia in Shape Blaster

Ora è il momento di usare la griglia nel nostro gioco. Iniziamo dichiarando a Griglia variabile in MonkeyBlasterMain e inizializzandolo simpleInitApp ():

 Rectangle size = new Rectangle (0, 0, settings.getWidth (), settings.getHeight ()); Spaziatura Vector2f = nuova Vector2f (25,25); grid = new Grid (dimensione, spaziatura, guiNode, assetManager);

Quindi, dobbiamo chiamare grid.update (float tpf) dal simpleUpdate metodo:

 @Override public void simpleUpdate (float tpf) if ((Boolean) player.getUserData ("alive")) spawnEnemies (); spawnBlackHoles (); handleCollisions (); handleGravity (TPF);  else if (System.currentTimeMillis () - (Long) player.getUserData ("dieTime")> 4000f &&! gameOver) // spawn player player.setLocalTranslation (500,500,0); guiNode.attachChild (giocatore); player.setUserData ( "vivo", true); sound.spawn ();  grid.update (tpf); hud.update (); 

Successivamente, dobbiamo chiamare i metodi degli effetti dai posti giusti nel nostro gioco.

Il primo, creando un'onda quando il giocatore genera, è piuttosto facile: estendiamo semplicemente il punto in cui generiamo il giocatore simpleUpdate (float tpf) con la seguente riga:

 grid.applyDirectedForce (new Vector3f (0,0,5000), player.getLocalTranslation (), 100);

Nota che applichiamo una forza nel direzione z. Potremmo avere un gioco 2D ma, dal momento che jME è un motore 3D, possiamo facilmente utilizzare anche effetti 3D. Se dovessimo ruotare la telecamera, vedremmo la griglia rimbalzare verso l'interno e l'esterno.

Il secondo e il terzo effetto devono essere gestiti nei controlli. Quando i proiettili volano nel gioco, chiamano questo metodo controlUpdate (float tpf):

 grid.applyExplosiveForce (direction.length () * (18f), spatial.getLocalTranslation (), 80);

In questo modo i proiettili respingono la griglia proporzionalmente alla loro velocità. E 'stato abbastanza facile.

È simile ai buchi neri:

 grid.applyImplosiveForce (FastMath.sin (sprayAngle / 2) * 10 +20, spatial.getLocalTranslation (), 250);

Questo fa sì che il buco nero succhi nella griglia con una quantità variabile di forza. Ho riutilizzato il sprayAngle variabile, che farà sì che la forza sulla griglia pulsi in sincronia con l'angolo che irradia particelle (sebbene a metà della frequenza dovuta alla divisione per due). La forza passata varierà sinusoidalmente tra 10 e 30.

Per fare questo lavoro, non devi dimenticare di passare griglia a BulletControl e BlackHoleControl.


interpolazione

Possiamo ottimizzare la griglia migliorando la qualità visiva per un dato numero di molle senza aumentare significativamente il costo delle prestazioni.

Renderemo la griglia più densa aggiungendo segmenti di linea all'interno delle celle della griglia esistenti. Lo facciamo disegnando linee dal punto medio di un lato della cella al punto medio del lato opposto. L'immagine sotto mostra le nuove linee interpolate in rosso:

Creeremo quelle linee aggiuntive nel costruttore del nostro Griglia classe. Se dai un'occhiata a questo, ne vedrai due per loop dove colleghiamo le masse puntiformi con le molle. Basta inserire questo blocco di codice lì:

 if (x> 0 && y> 0) Geometry additionalLine = defaultLine.clone (); additionalLine.addControl (new AdditionalLineControl (points [x-1] [y], points [x] [y], points [x-1] [y-1], points [x] [y-1])); gridNode.attachChild (additionalLine); Geometry additionalLine2 = defaultLine.clone (); additionalLine2.addControl (new AdditionalLineControl (points [x] [y-1], points [x] [y], points [x-1] [y-1], points [x-1] [y])); gridNode.attachChild (additionalLine2); 

Ma, come sai, la creazione di oggetti non è l'unica cosa che dobbiamo fare; dobbiamo anche aggiungere loro un controllo per far sì che si comportino correttamente. Come puoi vedere sopra, il AdditionalLineControl ottiene quattro masse di punti passate in modo che possa calcolare la sua posizione, rotazione e scala:

 public class AdditionalLineControl estende AbstractControl PointMass privato end11, end12, end21, end22; public ExtraLineControl (PointMass end11, PointMass end12, PointMass end21, PointMass end22) this.end11 = end11; this.end12 = end12; this.end21 = end21; this.end22 = end22;  @Override protected void controlUpdate (float tpf) // movement spatial.setLocalTranslation (position1 ()); // scale Vector3f dif = position2 (). sottrarre (posizione1 ()); spatial.setLocalScale (dif.length ()); // rotation spatial.lookAt (position2 (), new Vector3f (1,0,0));  private Vector3f position1 () return new Vector3f (). interpolate (end11.getPosition (), end12.getPosition (), 0.5f);  private Vector3f position2 () return new Vector3f (). interpolate (end21.getPosition (), end22.getPosition (), 0.5f);  @Override protected void controlRender (RenderManager rm, ViewPort vp) 

Qual'è il prossimo?

Abbiamo il gameplay di base e gli effetti implementati. Spetta a te trasformarlo in un gioco completo e raffinato con il tuo gusto. Prova ad aggiungere alcune interessanti nuove meccaniche, alcuni fantastici nuovi effetti o una storia unica. Nel caso in cui non sei sicuro di dove cominciare, ecco alcuni suggerimenti:

  • Crea nuovi tipi di nemici come serpenti o nemici che esplodono.
  • Crea nuovi tipi di armi come cercare missili o un fulmine.
  • Aggiungi una schermata del titolo e il menu principale.
  • Aggiungi una tabella di punteggio elevato.
  • Aggiungi alcuni potenziamenti, come uno scudo o delle bombe. Per i punti bonus, diventa creativo con i tuoi potenziamenti. Puoi fare power-up che manipolano la gravità, alterano il tempo o crescono come organismi. Puoi attaccare una gigantesca palla demolitrice basata sulla fisica alla nave per distruggere i nemici. Sperimenta per trovare potenziamenti che siano divertenti e che aiutino il tuo gioco a distinguersi.
  • Crea più livelli. Livelli più duri possono introdurre nemici più duri e armi e potenziamenti più avanzati.
  • Permetti a un secondo giocatore di unirsi a un gamepad.
  • Permetti all'arena di scorrere in modo che possa essere più grande della finestra di gioco.
  • Aggiungi rischi ambientali come i laser.
  • Aggiungi un negozio o un sistema di livellamento e consenti al giocatore di guadagnare degli aggiornamenti.

Grazie per aver letto!