Maak een Neon Vector Shooter met jME Warping Grid

In de serie tot nu toe hebben we de gameplay gecodeerd, vijanden toegevoegd en dingen gekruid met bloei en deeltjeseffecten. In dit laatste deel maken we een dynamisch, kromtrekkend achtergrondraster.


Overzicht

Deze video toont het raster in actie:


We maken het raster met behulp van een veersimulatie: bij elke kruising van het raster plaatsen we een klein gewicht (een puntmassa) en verbinden we deze gewichten met veren. Deze veren zullen alleen trekken en nooit duwen, net als een rubberen band. Om het raster in positie te houden, worden de massa's rond de rand van het raster op hun plaats verankerd.

Hieronder is een diagram van de lay-out.


We zullen een klasse aanmaken genaamd rooster om dit effect te creëren. Voordat we echter aan het raster zelf werken, moeten we twee helperklassen maken: De lente en PointMass.


De PointMass-klasse

De PointMass klasse vertegenwoordigt de massa's waaraan we de veren zullen bevestigen. Veren sluiten nooit rechtstreeks op andere veren aan. In plaats daarvan oefenen ze een kracht uit op de massa's die ze verbinden, die op hun beurt andere bronnen kunnen rekken.

 openbare klasse PointMass private Vector3f-positie; private Vector3f velocity = Vector3f.ZERO; private float inverseMass; private Vector3f-acceleratie = Vector3f.ZERO; privé vlotter demping = 0.98f; openbare PointMass (Vector3f-positie, zwevende inverseMass) this.position = position; this.inverseMass = inverseMass;  public void applyForce (Vector3f force) acceleration.addLocal (force.mult (inverseMass));  openbare ongeldige verhogingDamping (float-factor) demping * = factor;  public void update (float tpf) velocity.addLocal (acceleratie.mult (1f)); position.addLocal (velocity.mult (0.6f)); versnelling = 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;  

Er zijn een paar interessante punten over deze klasse. Merk allereerst op dat het de omgekeerde van de massa, 1 / massa. Dit is vaak een goed idee in natuurkundige simulaties omdat natuurkundige vergelijkingen de neiging hebben om vaker het omgekeerde van de massa te gebruiken, en omdat het ons een gemakkelijke manier geeft om oneindig zware, onverplaatsbare objecten weer te geven door de inverse massa op nul te zetten.

De klasse bevat ook een demping variabele, die optreedt om de massa geleidelijk te vertragen. Dit wordt grofweg gebruikt als wrijving of luchtweerstand. Dit zorgt ervoor dat het raster uiteindelijk tot rust komt en verhoogt ook de stabiliteit van de veersimulatie.

De Bijwerken() methode verplaatst het werk van het verplaatsen van de punt elk frame. Het begint met symplectische Euler-integratie, wat betekent dat we de versnelling toevoegen aan de snelheid en vervolgens de bijgewerkte snelheid aan de positie toevoegen. Dit verschilt van de standaard Euler-integratie waarin we de snelheid zouden bijwerken na de positie bijwerken.

Tip: Symplectische Euler is beter voor de lentesimulaties omdat het energie bespaart. Als je gewone Euler-integratie gebruikt en veren zonder demping maakt, zullen ze de neiging hebben om verder te rekken en verder te stuiteren als ze energie krijgen, en uiteindelijk je simulatie te verbreken.

Na het bijwerken van de snelheid en de positie, controleren we of de snelheid erg klein is en als dat zo is, stellen we het op nul. Dit kan van belang zijn voor de prestaties vanwege de aard van gedenormaliseerde drijvende-kommagetallen.

De IncreaseDamping () methode wordt gebruikt om de hoeveelheid demping tijdelijk te verhogen. We zullen dit later gebruiken voor bepaalde effecten.


De Lenteklasse

Een veer verbindt twee puntmassa's en oefent een kracht uit die over de natuurlijke lengte heen reikt. Springs volgen een aangepaste versie van Hooke's Law met demping:

\ [f = -kx - bv \]

  • \ (f \) is de kracht geproduceerd door de veer.
  • \ (k \) is de veerconstante of de "stijfheid" van de veer.
  • \ (x \) is de afstand waarover de veer zich uitstrekt voorbij zijn natuurlijke lengte.
  • \ (b \) is de dempingsfactor.
  • \ (v \) is de snelheid.

De code voor de De lente klasse is als volgt:

 public class Spring private PointMass end1; privé PointMass-end2; privé float targetLength; particuliere vlotterstijfheid; privé vlotter demping; public Spring (PointMass end1, PointMass end2, float-stijfheid, float-demping, knooppunt gridNode, Boolean visible, Geometry defaultLine) this.end1 = end1; this.end2 = end2; deze stijfheid = stijfheid; this.damping = demping; targetLength = end1.getPosition (). distance (end2.getPosition ()) * 0.95f; if (zichtbaar) defaultLine.addControl (nieuwe LineControl (end1, end2)); gridNode.attachChild (defaultLine);  openbare ongeldige update (float tpf) Vector3f x = end1.getPosition (). aftrekken (end2.getPosition ()); vlotterlengte = x.lengte (); if (length> targetLength) x.normalizeLocal (); x.multLocal (length - targetLength); Vector3f dv = end2.getVelocity (). Aftrekken (end1.getVelocity ()); Vector3f force = x.mult (stijfheid); force.subtract (dv.mult (demping / 10f)); end1.applyForce (force.negate ()); end2.applyForce (kracht); 

Wanneer we een veer maken, stellen we de natuurlijke lengte van de veer in op iets minder dan de afstand tussen de twee eindpunten. Dit houdt het rooster strak, zelfs in rust, en verbetert het uiterlijk enigszins.

De Bijwerken() methode controleert eerst of de veer uitgerekt is voorbij zijn natuurlijke lengte. Als het niet uitgerekt is, gebeurt er niets. Als dat zo is, gebruiken we de aangepaste wet van Hooke om de kracht van de veer te vinden en toe te passen op de twee verbonden massa's.

Er is een andere klasse die we moeten maken om de regels correct weer te geven. De LineControl zorgt voor verplaatsen, schalen en draaien van de lijnen:

 public class LineControl breidt AbstractControl uit private PointMass end1, end2; openbare LineControl (PointMass end1, PointMass end2) this.end1 = end1; this.end2 = end2;  @Override protected void controlUpdate (float tpf) // movement space.setLocalTranslation (end1.getPosition ()); // scale Vector3f dif = end2.getPosition (). aftrekken (end1.getPosition ()); spatial.setLocalScale (dif.length ()); // rotatie spatial.lookAt (end2.getPosition (), nieuwe Vector3f (1,0,0));  @Override protected void controlRender (RenderManager rm, ViewPort vp) 

Het raster maken

Nu we de benodigde geneste klassen hebben, zijn we klaar om het raster te maken. We beginnen met creëren PointMass objecten op elk kruispunt op het raster. We creëren ook een onroerende anker PointMass objecten om het raster op zijn plaats te houden. Vervolgens koppelen we de massa's samen met veren:

 public class Grid private knooppunt gridNode; particuliere lente [] veren; private PointMass [] [] punten; private Geometry defaultLine; private Geometry thickLine; public Grid (rechthoekgrootte, Vector2F-tussenruimte, knooppuntgodenode, AssetManager assetManager) gridNode = nieuwe knoop (); guiNode.attachChild (gridNode); defaultLine = createLine (1f, assetManager); thickLine = createLine (3f, assetManager); ArrayList springList = new ArrayList (); vlotterstijfheid = 0,28f; vlotter-demping = 0,06f; int numColumns = (int) (size.width / spacing.x) + 2; int numRows = (int) (size.height / spacing.y) + 2; punten = nieuwe PointMass [numColumns] [numRows]; PointMass [] [] fixedPoints = nieuwe PointMass [numColumns] [numRows]; // maak de puntmassa's zwevend xCoord = 0, yCoord = 0; voor (int rij = 0; rij < 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 (nieuwe lente (punten [x-1] [y], punten [x] [y], stijfheid, demping, gridNode, true, line.clone ()));  if (y> 0) if (x% 3 == 0) line = thickLine;  else line = defaultLine;  springList.add (nieuwe lente (punten [x] [y-1], punten [x] [y], stijfheid, demping, gridNode, true, line.clone ())); 

De eerste voor loop creëert zowel normale massa's als onroerende massa's op elk kruispunt van het raster. We zullen niet echt alle onwrikbare massa's gebruiken, en de ongebruikte massa's zullen eenvoudigweg vuilnis verzameld worden op een bepaald moment nadat de aannemer eindigt. We zouden kunnen optimaliseren door onnodige objecten te creëren, maar omdat het raster meestal maar één keer wordt gemaakt, maakt het niet veel uit.

Naast het gebruik van ankerpuntmassa's rond de rand van het raster, gebruiken we ook enkele ankermassa's in het raster. Deze zullen worden gebruikt om heel voorzichtig te helpen het rooster terug te trekken naar zijn oorspronkelijke positie na te zijn vervormd.

Omdat de ankerpunten nooit bewegen, hoeven ze niet elk frame te worden bijgewerkt. We kunnen ze gewoon aansluiten op de bronnen en ze vergeten. Daarom hebben we geen ledenvariabele in de rooster klasse voor deze massa's.

Er zijn een aantal waarden die u kunt aanpassen bij het maken van het raster. De belangrijkste zijn de stijfheid en demping van de veren. De stijfheid en demping van de grensankers en binnenankers worden onafhankelijk van de hoofdveren ingesteld. Hogere stijfheidswaarden zullen de veren sneller laten oscilleren, en hogere dempingswaarden zullen ervoor zorgen dat de veren sneller vertragen.

Er is nog een laatste ding dat moet worden vermeld: het createLine () methode.

 private Geometry createLine (float thickness, AssetManager assetManager) Vector3f [] vertices = new Vector3f (0,0,0), new Vector3f (0,0,1); int [] indices = 0,1; Mesh lineMesh = new Mesh (); lineMesh.setMode (Mesh.Mode.Lines); lineMesh.setLineWidth (dikte); lineMesh.setBuffer (VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer (hoekpunten)); lineMesh.setBuffer (VertexBuffer.Type.Index, 1, BufferUtils.createIntBuffer (indices)); lineMesh.updateBound (); Geometry lineGeom = new Geometry ("lineMesh", lineMesh); MateriaalmatWireframe = nieuw materiaal (assetManager, "Common / MatDefs / Misc / Unshaded.j3md"); . MatWireframe.getAdditionalRenderState () setFaceCullMode (RenderState.FaceCullMode.Off); matWireframe.setColor ("Color", nieuwe ColorRGBA (0.118f, 0.118f, 0.545f, 0.25f)); . MatWireframe.getAdditionalRenderState () setBlendMode (BlendMode.AlphaAdditive); lineGeom.setMaterial (matWireframe); return lineGeom; 

Hier maken we in feite een lijn door de hoekpunten van de lijn en de volgorde van de hoekpunten op te geven, een net te maken, een blauw materiaal toe te voegen, enzovoort. Als u het proces van het maken van de regel precies wilt begrijpen, kunt u altijd de jME-zelfstudies bekijken.

Waarom moet de lijncreatie zo gecompliceerd zijn - is het niet 'slechts' een eenvoudige regel? Ja, dat is het, maar je moet kijken naar wat jME wil zijn. Meestal, in 3D-spellen, heb je geen enkele lijn of driehoek in het spel, maar eerder modellen met texturen en animaties. Dus hoewel het mogelijk is om een ​​enkele regel in jME te genereren, is de belangrijkste focus op het importeren van modellen die met andere software zijn gegenereerd, zoals Blender.

Het raster manipuleren

Om het raster te laten bewegen, moeten we het elk frame bijwerken. Dit is heel eenvoudig, omdat we al al het harde werk hebben gedaan in de PointMass en De lente klassen.

 openbare ongeldige update (float tpf) for (int i = 0; i 

Nu zullen we enkele methoden toevoegen die het raster manipuleren. U kunt methoden toevoegen voor elke vorm van manipulatie die u maar kunt bedenken. We zullen hier drie soorten manipulaties uitvoeren: een deel van het raster in een bepaalde richting duwen, het raster vanaf een bepaald punt naar buiten duwen en het raster naar een bepaald punt trekken. Alle drie hebben invloed op het raster binnen een bepaalde straal vanaf een bepaald doelpunt.

Hieronder staan ​​enkele afbeeldingen van deze manipulaties in actie:


Golf gemaakt door het raster langs de z-as te duwen.
Kogels die het raster naar buiten stoten.
Het rooster naar binnen zuigen.

En hier zijn de methoden voor de effecten:

 openbare ongeldige applyDirectedForce (Vector3f force, Vector3f-positie, zwevende straal) for (int x = 0; x 

De Grid in Shape Blaster gebruiken

Nu is het tijd om het raster in onze game te gebruiken. We beginnen met het verklaren van een rooster variabele in MonkeyBlasterMain en initialiseren in simpleInitApp ():

 Rectangle size = new Rectangle (0, 0, settings.getWidth (), settings.getHeight ()); Vector2f-spatiëring = nieuwe Vector2f (25,25); grid = new Grid (size, spacing, guiNode, assetManager);

Dan moeten we bellen grid.update (float tpf) van de simpleUpdate methode:

 @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 (speler); player.setUserData ( "leven", true); sound.spawn ();  grid.update (tpf); hud.update (); 

Vervolgens moeten we de effectmethoden bellen vanaf de juiste plaatsen in onze game.

De eerste, het creëren van een golf wanneer de speler spawnt, is vrij eenvoudig: we breiden gewoon de plaats uit waar we de speler in de spa leggen simpleUpdate (float tpf) met de volgende regel:

 grid.applyDirectedForce (nieuwe Vector3f (0,05000), player.getLocalTranslation (), 100);

Merk op dat we een kracht toepassen in de z-richting. We hebben misschien een 2D-game, maar omdat jME een 3D-engine is, kunnen we ook gemakkelijk 3D-effecten gebruiken. Als we de camera zouden draaien, zouden we het rooster naar binnen en naar buiten zien stuiteren.

Het tweede en derde effect moeten in de besturing worden behandeld. Wanneer kogels door het spel vliegen, noemen ze deze methode in controlUpdate (float tpf):

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

Dit zorgt ervoor dat kogels het raster proportioneel afstoten ten opzichte van hun snelheid. Dat was vrij eenvoudig.

Het is vergelijkbaar met de zwarte gaten:

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

Dit zorgt ervoor dat het zwarte gat in het rooster zuigt met een variërende hoeveelheid kracht. Ik heb het opnieuw gebruikt sprayAngle variabele, die ervoor zorgt dat de kracht op het rooster synchroon pulseert met de hoek waarin het deeltjes sproeit (hoewel met de helft van de frequentie als gevolg van de deling door twee). De doorgevoerde kracht zal sinusoïdaal variëren tussen 10 en 30.

Om dit te laten werken, moet je niet vergeten om te slagen rooster naar BulletControl en BlackHoleControl.


interpolatie

We kunnen het raster optimaliseren door de beeldkwaliteit voor een bepaald aantal veren te verbeteren zonder de prestatiekosten aanzienlijk te verhogen.

We zullen het raster dichter maken door lijnsegmenten toe te voegen in de bestaande rastercellen. We doen dit door lijnen te tekenen vanaf het middelpunt van één kant van de cel naar het middelpunt van de andere kant. De afbeelding hieronder toont de nieuwe geïnterpoleerde lijnen in rood:

We zullen die extra regels maken in de constructor van onze rooster klasse. Als je ernaar kijkt, zie je er twee voor lussen waar we de puntmassa's met de veren verbinden. Voer dit blok code gewoon in:

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

Maar zoals u weet, is het maken van objecten niet het enige dat we moeten doen; we moeten ook een controle toevoegen om ervoor te zorgen dat ze zich correct gedragen. Zoals je hierboven kunt zien, is de AdditionalLineControl krijgt vier puntmassa's doorgegeven zodat het zijn positie, rotatie en schaal kan berekenen:

 public class AdditionalLineControl breidt AbstractControl uit private PointMass end11, end12, end21, end22; openbare AdditionalLineControl (PointMass end11, PointMass end12, PointMass end21, PointMass end22) this.end11 = end11; this.end12 = einde12; this.end21 = end21; this.end22 = end22;  @Override protected void controlUpdate (float tpf) // movementspace.setLocalTranslation (position1 ()); // schaal Vector3f dif = positie2 (). aftrekken (positie1 ()); spatial.setLocalScale (dif.length ()); // rotation spatial.lookAt (position2 (), new Vector3f (1,0,0));  private Vector3f position1 () return new Vector3f (). interpoleren (end11.getPosition (), end12.getPosition (), 0.5f);  private Vector3f position2 () return new Vector3f (). interpoleren (end21.getPosition (), end22.getPosition (), 0.5f);  @Override protected void controlRender (RenderManager rm, ViewPort vp) 

Wat is het volgende?

We hebben de basis gameplay en effecten geïmplementeerd. Het is aan jou om er een compleet en gepolijst spel van te maken met je eigen smaak. Probeer wat interessante nieuwe mechanica, enkele coole nieuwe effecten of een uniek verhaal toe te voegen. Als u niet zeker weet waar u moet beginnen, volgt hier een paar suggesties:

  • Maak nieuwe vijandtypen zoals slangen of exploderende vijanden.
  • Maak nieuwe wapensoorten zoals het zoeken van raketten of een bliksemschiet.
  • Voeg een titelscherm en hoofdmenu toe.
  • Voeg een hoge scoretabel toe.
  • Voeg wat power-ups toe, zoals een schild of bommen. Voor bonuspunten, word creatief met je power-ups. Je kunt power-ups maken die de zwaartekracht manipuleren, de tijd veranderen of groeien als organismen. Je kunt een gigantische op fysica gebaseerde sloopkogel aan het schip bevestigen om vijanden te verslaan. Experimenteer om power-ups te vinden die leuk zijn en je spel helpen opvallen.
  • Maak meerdere niveaus. Hardere niveaus kunnen hardere vijanden en geavanceerdere wapens en power-ups introduceren.
  • Laat een tweede speler lid worden van een gamepad.
  • Sta de arena toe te scrollen zodat deze groter kan zijn dan het spelvenster.
  • Voeg milieurisico's toe zoals lasers.
  • Voeg een winkel of levelingsysteem toe en laat de speler upgrades verdienen.

Bedankt voor het lezen!