Faceți un Shooter Vector Neon cu jME Grid Warping

În seria de până acum, am codificat gameplay-ul, am adăugat dușmani și amănunțit lucrurile cu efecte de inflorescență și particule. În această ultimă parte vom crea o grilă dinamică de bază.


Prezentare generală

Acest videoclip afișează grila în acțiune:


Vom face grila folosind o simulare de primăvară: la fiecare intersecție a grilajului vom plasa o greutate mică (o masă punctuală) și vom lega aceste greutăți cu ajutorul izvoarelor. Aceste izvoare vor trage și nu se vor împinge niciodată, la fel ca o bandă de cauciuc. Pentru a menține grila în poziție, masele din jurul graniței rețelei vor fi ancorate în poziție.

Mai jos este o diagramă a aspectului.


Vom crea o clasă numită Grilă pentru a crea acest efect. Cu toate acestea, înainte de a lucra la grila în sine, trebuie să facem două clase de ajutor: Primăvară și PointMass.


Clasa PointMass

PointMass clasa reprezintă masele la care vom atașa izvoarele. Arcurile nu se conectează direct la alte izvoare. În schimb, aplică o forță maselor pe care le conectează, care, la rândul lor, pot întinde alte izvoare.

 clasa publică PointMass privată Vector3f poziție; Vector3f velocitate privată = Vector3f.ZERO; private inverseMass float; privare Vector3f accelerație = Vector3f.ZERO; amortizare privată a flotorului = 0,98 f; public PointMass (poziția Vector3f, float inverseMass) this.position = position; this.inverseMass = inverseMass;  void publice applyForce (forța Vector3f) acceleration.addLocal (force.mult (inverseMass));  public void increaseDamping (float factor) amortizare * = factor;  public void update (float tpf) velocity.addLocal (acceleration.mult (1f)); position.addLocal (velocity.mult (0.6f)); accelerare = Vector3f.ZERO.clone (); dacă (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;  

Există câteva puncte interesante despre această clasă. Mai întâi, observați că stochează invers din masa, 1 / masă. Aceasta este adesea o idee bună în simulările fizicii deoarece ecuațiile fizicii tind să folosească mai des inversul masei și pentru că ne oferă o modalitate ușoară de a reprezenta obiecte infinit de grele și imobile prin stabilirea masei inverse la zero.

Clasa conține, de asemenea, a amortizare variabilă, care încetinește treptat masa în jos. Aceasta este folosită aproximativ drept rezistență la frecare sau la aer. Acest lucru face ca grila să ajungă în cele din urmă să se odihnească și, de asemenea, crește stabilitatea simulării primăverii.

Actualizați() metoda face munca de a muta masa punctului fiecare cadru. Aceasta începe prin a face integrarea simulctică Euler, ceea ce înseamnă doar că adăugăm accelerația la viteză și apoi adăugăm viteza actualizată la poziție. Aceasta diferă de integrarea standard Euler în care am fi actualizat viteza după actualizarea poziției.

Bacsis: Simulare Euler este mai bine pentru simulările de primăvară pentru că conservă energia. Dacă utilizați integrarea obișnuită a Euler și creați izvoare fără amortizare, acestea vor avea tendința să se extindă mai mult și mai mult, fiecare săriți pe măsură ce câștigă energie, eventual rupându-vă simularea.

După actualizarea vitezei și poziției, verificăm dacă viteza este foarte mică și, dacă este, ne-am stabilit la zero. Acest lucru poate fi important pentru performanță datorită naturii numerelor cu puncte plutitoare denormalizate.

IncreaseDamping () metoda este utilizată pentru a mări temporar cantitatea de amortizare. Vom folosi acest lucru mai târziu pentru anumite efecte.


Clasa de primăvară

Un arc conectează două mase punctuale și, dacă este întins peste lungimea sa naturală, aplică o forță care trage masele împreună. Arcurile urmăresc o versiune modificată a legii lui Hooke cu amortizare:

\ [f = -kx-bv \]

  • \ (f \) este forța produsă de primăvară.
  • \ (k \) este constanta arcului sau "rigiditatea" arcului.
  • \ (x \) este distanța pe care arcul este întins peste lungimea sa naturală.
  • \ (b \) este factorul de amortizare.
  • \ (v \) este viteza.

Codul pentru Primăvară clasa este după cum urmează:

 clasa publica de primavara private PointMass end1; privat PointMass end2; obiectivul plutitor privat Lungime; rigiditate privată a flotorului; amortizarea amortizoarelor private; public Spring (PointMass end1, PointMass end2, rigiditate flotantă, amortizare flotantă, Node gridNode, boolean vizibil, Geometry defaultLine) this.end1 = end1; this.end2 = end2; această greutate = rigiditate; this.damping = amortizare; targetLength = end1.getPosition () distanța (end2.getPosition ()) * 0.95f; dacă (vizibil) defaultLine.addControl (noul LineControl (end1, end2)); gridNode.attachChild (defaultLine);  actualizare publică void (float tpf) Vector3f x = end1.getPosition () scădea (end2.getPosition ()); float lungime = x.length (); dacă (lungime> targetLength) x.normalizeLocal (); x.multLocal (lungime-țintăLength); Vector3f dv = end2.getVelocitate (). Scădea (end1.getVelocity ()); Vector3f forța = x.mult (rigiditate); force.subtract (dv.mult (amortizare / 10f)); end1.applyForce (force.negate ()); end2.applyForce (forță); 

Când creăm un arc, setăm lungimea naturală a arcului să fie doar puțin mai mică decât distanța dintre cele două puncte de capăt. Aceasta menține grilă întinsă, chiar și atunci când este în repaus, și îmbunătățește aspectul oarecum.

Actualizați() metoda verifică mai întâi dacă arcul este întins dincolo de lungimea sa naturală. Dacă nu este întins, nimic nu se întâmplă. Dacă este cazul, vom folosi Legea modificată a lui Hooke pentru a găsi forța din primăvară și ao aplica celor două mase conectate.

Există o altă clasă pe care trebuie să o creăm pentru a afișa corect liniile. LineControl va avea grijă de deplasarea, scalarea și rotirea liniilor:

 clasa publica LineControl extinde AbstractControl privat PointMass end1, end2; public LineControl (PointMass end1, PointMass sfârșitul2) this.end1 = end1; this.end2 = end2;  @Override protejat void controlUpdate (float tpf) // spațiu de mișcare.setLocalTranslation (end1.getPosition ()); // scară Vector3f dif = end2.getPosition () scade (end1.getPosition ()); spatial.setLocalScale (dif.length ()); // rotația spatial.lookAt (end2.getPosition (), noul Vector3f (1,0,0));  @Override protejat void controlRender (RenderManager rm, ViewPort vp) 

Crearea grilei

Acum că avem clasele necesare imbricate, suntem gata să creăm grila. Începem prin a crea PointMass obiecte la fiecare intersecție pe grila. De asemenea, creăm niște ancore imobile PointMass obiecte pentru a ține grila în poziție. Apoi legăm masele împreună cu izvoarele:

 public class Grid nod privat gridNode; arcuri primare de primăvară; private PointMass [] [] puncte; privat Geometry defaultLine; privat Geometry thickLine; grilă publică (dimensiunea dreptunghiului, spațierea Vector2f, nodul guiNode, AssetManager assetManager) gridNode = nou Node (); guiNode.attachChild (gridNode); defaultLine = createLine (1f, assetManager); thickLine = createLine (3f, assetManager); ArrayList springList = nou ArrayList (); flotare rigiditate = 0.28f; amortizare flotantă = 0,06f; int numColumns = (int) (size.width / spacing.x) + 2; int numRows = (int) (size.height / spacing.y) + 2; puncte = puncte noi pentru puncte [numColumns] [numRows]; PointMass [] [] puncte fixe = puncte noi [numColumns] [numRows]; // a crea flota masei punctului xCoord = 0, yCoord = 0; pentru (int rând = 0; rând < 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) dacă (y% 3 == 0) line = grosime;  altfel line = defaultLine;  springList.add (noua primăvară (puncte [x-1] [y], puncte [x] [y], rigiditate, amortizare, gridNode, true, line.clone ()));  dacă (y> 0) if (x% 3 == 0) line = grosime;  altfel line = defaultLine;  springList.add (noua primăvară (puncte [x] [y-1], puncte [x] [y], rigiditate, amortizare, gridNode, true, line.clone ())); 

Primul pentru buclă creează atât mase regulate cât și mase imobile la fiecare intersecție a rețelei. Nu vom folosi de fapt toate masele imobile, iar masele neutilizate vor fi pur și simplu gunoi colectate la un moment dat după terminarea constructorului. Am putea optimiza evitând crearea obiectelor inutile, dar din moment ce grila este de obicei creată doar o singură dată, nu va face prea multă diferență.

În plus față de utilizarea masei punctului de ancorare în jurul marginii rețelei, vom folosi și câteva mase de ancore în interiorul rețelei. Acestea vor fi utilizate pentru a ajuta foarte ușor să trageți grila înapoi în poziția inițială după ce ați fost deformate.

Întrucât punctele de ancorare nu se mișcă niciodată, nu trebuie să fie actualizate fiecare cadru. Putem pur și simplu să le prindem până la izvoare și să uităm de ele. Prin urmare, nu avem o variabilă membru în Grilă pentru aceste mase.

Există o serie de valori pe care le puteți modifica în crearea rețelei. Cele mai importante sunt rigiditatea și amortizarea izvoarelor. Rigiditatea și amortizarea ancorelor de frontieră și a ancorelor interioare sunt stabilite independent de izvoarele principale. Valorile de rigiditate mai mari vor face ca arcurile să oscileze mai repede, iar valorile de amortizare mai mari vor determina încetinirea arcurilor mai repede.

Există un ultim lucru de spus: createLine () metodă.

 privat Geometry createLine (float grosime, AssetManager assetManager) Vector3f [] vertices = new Vector3f (0,0,0), nou Vector3f (0,0,1); int [] indici = 0,1; Mesh lineMesh = Mesh nou (); lineMesh.setMode (Mesh.Mode.Lines); lineMesh.setLineWidth (grosime); lineMesh.setBuffer (VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer (noduri)); lineMesh.setBuffer (VertexBuffer.Type.Index, 1, BufferUtils.createIntBuffer (indicii)); lineMesh.updateBound (); Geometria lineGeom = Geometrie nouă ("lineMesh", lineMesh); Material matWireframe = material nou (assetManager, "Common / MatDefs / Misc / Unshaded.j3md"); . MatWireframe.getAdditionalRenderState () setFaceCullMode (RenderState.FaceCullMode.Off); matWireframe.setColor ("Culoare", noul ColorRGBA (0.118f, 0.118f, 0.545f, 0.25f)); . MatWireframe.getAdditionalRenderState () setBlendMode (BlendMode.AlphaAdditive); lineGeom.setMaterial (matWireframe); return lineGeom; 

Aici, vom crea o linie prin specificarea vârfurilor liniei și ordinea nodurilor, crearea unei rețele, adăugarea unui material albastru și așa mai departe. Dacă doriți să înțelegeți exact procesul de creare a liniei, puteți să vă uitați întotdeauna la tutorialele jME.

De ce trebuie să fie atât de complicată crearea de linii - nu este doar o simplă linie? Da, este, dar trebuie să te uiți la ce intenționează să fie jME. De obicei, în jocurile 3D, nu aveți linii sau triunghiuri unice în joc, ci modele cu texturi și animații. Deci, în timp ce este posibilă generarea unei singure linii în jME, accentul principal se pune pe importul de modele care au fost generate cu alte programe, cum ar fi Blender.

Manipularea grilei

Pentru ca grila să se miște, trebuie să o actualizăm în fiecare cadru. Acest lucru este foarte simplu, deoarece am făcut deja toată munca grea în PointMass și Primăvară clase.

 public void update (float tpf) pentru (int i = 0; i 

Acum, vom adăuga câteva metode care manipulează grila. Puteți adăuga metode pentru orice fel de manipulare pe care vă puteți gândi. Vom implementa trei tipuri de manipulări aici: împingând o parte a grilei într-o anumită direcție, împingând grila spre exterior dintr-un anumit punct și trăgând grila spre un anumit punct. Toate cele trei vor afecta grila într-o anumită rază de la un punct țintă.

Mai jos sunt câteva imagini ale acestor manipulări în acțiune:


Wave creat prin împingerea grilajului de-a lungul axei z.
Gloanțele respingă grilele spre exterior.
Sugeți grilele spre interior.

Și aici sunt metodele pentru efecte:

 public void applyDirectedForce (forța Vector3f, poziția Vector3f, raza flotorului) for (int x = 0; x 

Folosind grila în Shape Blaster

Acum este timpul să folosim grila în jocul nostru. Începem prin a declara a Grilă variabilă în MonkeyBlasterMain și inițializarea acestuia simpleInitApp ():

 Dimensiunea dreptunghiului = nou dreptunghi (0, 0, settings.getWidth (), settings.getHeight ()); Distribuția vector2f = Vector2f nou (25,25); grilă = grila nouă (dimensiune, spațiere, guiNode, assetManager);

Apoi, trebuie să sunăm grid.update (float tpf) de la simpleUpdate metodă:

 @Override public void simpleUpdate (float tpf) dacă ((Boolean) player.getUserData ("viu")) spawnEnemies (); spawnBlackHoles (); handleCollisions (); handleGravity (TPF);  altfel dacă (System.currentTimeMillis () - (Long) player.getUserData ("dieTime")> 4000f &&! jocOver) // player player.setLocalTranslation (500,500,0); guiNode.attachChild (jucator); player.setUserData ( "viu", adevărat); sound.spawn ();  grid.update (tpf); hud.update (); 

Apoi, trebuie să apelați metodele de efect din locurile potrivite din jocul nostru.

Primul, care creează un val atunci când jucătorul apare, este destul de ușor - extindem doar locul în care jucăm jucătorul în simpleUpdate (float tpf) cu următoarea linie:

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

Rețineți că aplicăm o forță în direcția z. Este posibil să avem un joc 2D dar, deoarece jME este un motor 3D, putem folosi cu ușurință și efecte 3D. Dacă vom roti camera, vom vedea grilajul sări spre interior și spre exterior.

Al doilea și al treilea efect trebuie tratate în controale. Când gloanțele zboară prin joc, ei numesc această metodă controlUpdate (float tpf):

 grid.applyExplosiveForce (direcția.length () * (18f), spatial.getLocalTranslation (), 80);

Acest lucru va face ca gloanțele să respingă grila proporțional cu viteza lor. A fost destul de ușor.

Este similar cu găurile negre:

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

Acest lucru face gaura neagra suge in grila cu o forta variata de forta. Eu refolosesc sprayAngle variabilă, ceea ce va determina forța pe rețea să pulseze în sincronizare cu unghiul pe care îl pulverizează particulele (deși la jumătate din frecvența datorată împărțirii cu două). Forța care a trecut va varia în mod sinusoidal între 10 și 30.

Pentru a face acest lucru, nu trebuie să uitați să treceți grilă la BulletControl și BlackHoleControl.


Interpolare

Putem optimiza grila prin îmbunătățirea calității vizuale pentru un anumit număr de arcuri fără a crește în mod semnificativ costul de performanță.

Vom face grilarea mai densă adăugând segmente de linie în interiorul celulelor rețelei existente. Facem acest lucru prin trasarea liniilor de la mijlocul unei părți a celulei până la mijlocul părții opuse. Imaginea de mai jos prezintă liniile noi interpolate în roșu:

Vom crea acele linii suplimentare în constructorul nostru Grilă clasă. Dacă vă uitați la ea, veți vedea două pentru buclele unde legăm masele punctuale cu izvoarele. Doar introduceți acest bloc de cod acolo:

 dacă (x> 0 && y> 0) Geometria suplimentarăLine = defaultLine.clone (); aditionalLine.addControl (noulLineControl suplimentar (puncte [x-1] [y], puncte [x] [y], puncte [x-1] [y-1], puncte [x] [y-1])); gridNode.attachChild (additionalLine); Geometria suplimentarăLine2 = defaultLine.clone (); adiționalLine2.addControl (noulLineControl suplimentar (puncte [x] [y-1], puncte [x] [y], puncte [x-1] [y-1], puncte [x-1] [y])); gridNode.attachChild (additionalLine2); 

Dar, după cum știți, crearea obiectelor nu este singurul lucru pe care trebuie să-l facem; trebuie să adăugăm și un control pentru a le face să se comporte corect. După cum puteți vedea mai sus, AdditionalLineControl devine masă de patru puncte, astfel încât să poată calcula poziția, rotația și scara:

 clasa publica AdditionalLineControl extinde AbstractControl privat PointMass end11, end12, end21, end22; public AdditionalLineControl (PointMass end11, PointMass end12, PointMass sfârșitul21, PointMass end22) this.end11 = end11; this.end12 = end12; this.end21 = sfârșitul21; this.end22 = sfârșitul22;  @Override protejat void controlUpdate (float tpf) // spatial spatial.setLocalTranslation (pozitia1 ()); // scară Vector3f dif = poziția2 () scade (poziția1 ()); spatial.setLocalScale (dif.length ()); // rotația spatial.lookAt (pozitia2 (), noua Vector3f (1,0,0));  privată Vector3f position1 () returnează vectorul Vector3f () interpolate (end11.getPosition (), end12.getPosition (), 0.5f);  privată Vector3f position2 () returnează noua Vector3f () interpolate (end21.getPosition (), end22.getPosition (), 0.5f);  @Override protejat void controlRender (RenderManager rm, ViewPort vp) 

Ce urmeaza?

Avem jocul de bază și efectele implementate. Depinde de tine sa il transformi intr-un joc complet si lustruit cu propria ta aroma. Încearcă să adaugi niște mecanisme noi interesante, câteva efecte reci noi sau o poveste unică. În cazul în care nu sunteți sigur de unde să începeți, iată câteva sugestii:

  • Creați noi tipuri de inamici, cum ar fi șerpi sau explozivi.
  • Creați noi tipuri de arme, cum ar fi căutarea de rachete sau un pistol de trăsnet.
  • Adăugați un ecran de titlu și meniul principal.
  • Adăugați o tabelă cu scor mare.
  • Adăugați unele power-up-uri, cum ar fi un scut sau bombe. Pentru puncte bonus, creați-vă creativitatea cu ajutorul dispozitivelor dvs. de pornire. Poți să faci power-up-uri care manipulează gravitația, modifică timpul sau se dezvoltă ca niște organisme. Puteți atașa o corabie gigantică, bazată pe fizică, pe nava, pentru a sparge inamicii. Experimentați-vă pentru a găsi power-up-uri care sunt distractive și ajuta jocul să iasă în evidență.
  • Creați mai multe nivele. Nivelurile mai grele pot introduce inamici mai duri și arme mai avansate și upgrade-uri.
  • Permiteți unui al doilea jucător să se alăture unui jocpad.
  • Permiteți arenei să deruleze, astfel încât să fie mai mare decât fereastra jocului.
  • Adăugați pericole pentru mediu, cum ar fi laserele.
  • Adăugați un magazin sau un sistem de nivelare și permiteți jucătorului să câștige upgrade-uri.

Vă mulțumim pentru lectură!