I serien så langt har vi kodet spillet, lagt til fiender og krydret med blomster og partikkeleffekter. I denne siste delen vil vi skape et dynamisk, krøllende bakgrunnsnett.
Denne videoen viser rutenettet i handling:
Vi lager nettverket ved hjelp av en vårsimulering: Ved hvert skjæringspunkt av nettverket legger vi en liten vekt (en punktmasse), og vi knytter disse vektene ved hjelp av fjærer. Disse fjærene vil bare trekke og aldri skyve, som et gummibånd. For å holde rutenettet på plass, vil massene rundt grensen til ruten bli forankret på plass.
Nedenfor er et diagram over oppsettet.
Vi lager en klasse som heter Nett
å skape denne effekten. Men før vi jobber på nettet selv, må vi lage to hjelperklasser: Vår
og PointMass
.
De PointMass
klassen representerer massene som vi vil knytte fjærene til. Fjærer forbinder aldri direkte med andre fjærer. I stedet bruker de en kraft til massene de forbinder, som igjen kan strekke andre fjærer.
offentlig klasse PointMass privat Vector3f stilling; privat Vector3f hastighet = Vector3f.ZERO; privat float inverseMass; privat Vector3f akselerasjon = Vector3f.ZERO; privat flottørdemping = 0,98f; offentlig PointMass (Vector3f posisjon, float inverseMass) this.position = posisjon; this.inverseMass = inverseMass; offentlig ugyldig applyForce (Vector3f kraft) acceleration.addLocal (force.mult (inverseMass)); offentlig tomgang økningDamping (flytfaktor) demping * = faktor; offentlig ugyldig oppdatering (float tpf) velocity.addLocal (acceleration.mult (1f)); position.addLocal (velocity.mult (0.6f)); akselerasjon = Vector3f.ZERO.clone (); hvis (hastighet.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;
Det er noen interessante poeng om denne klassen. Først legg merke til at det lagrer omvendt av massen, 1 / masse
. Dette er ofte en god ide i fysikk simuleringer fordi fysikk likninger pleier å bruke den inverse av massen oftere, og fordi det gir oss en enkel måte å representere uendelig tunge, faste objekter ved å sette den inverse massen til null.
Klassen inneholder også a demping variabel, som virker for å gradvis bremse massen ned. Dette brukes omtrent som friksjon eller luftmotstand. Dette bidrar til at gridet til slutt kommer til hvile og øker også stabiliteten til vårens simulering.
De Oppdater()
Metoden gjør arbeidet med å flytte punktmassen hver ramme. Det begynner med å simulere Euler-integrasjon, som bare betyr at vi legger til akselerasjonen til hastigheten og deretter legger til den oppdaterte hastigheten til stillingen. Dette er forskjellig fra standard Euler-integrasjon der vi vil oppdatere hastigheten etter oppdatering av stillingen.
Etter å ha oppdatert hastigheten og posisjonen, kontrollerer vi om hastigheten er svært liten, og hvis den er, setter vi den til null. Dette kan være viktig for ytelsen på grunn av arten av denormaliserte flytende punktnumre.
De IncreaseDamping ()
Metoden brukes til midlertidig å øke mengden demping. Vi vil bruke dette senere for visse effekter.
En fjær knytter to punktmasser, og hvis den strekkes forbi dens naturlige lengde, gjelder en kraft som trekker massene sammen. Fjærer følger en modifisert versjon av Hooke's Law med demping:
\ [f = -kx - bv \]
Koden for Vår
klassen er som følger:
offentlig klasse våren private PointMass end1; privat PointMass end2; private float targetLength; privat float stivhet; privat float demping; offentlig vår (PointMass end1, PointMass end2, float stivhet, float demping, Node gridNode, boolean synlig, Geometry defaultLine) this.end1 = end1; this.end2 = end2; this.stiffness = stivhet; this.damping = demping; targetLength = end1.getPosition () .avstand (end2.getPosition ()) * 0.95f; hvis (synlig) defaultLine.addControl (ny LineControl (end1, end2)); gridNode.attachChild (defaultLine); offentlig ugyldig oppdatering (float tpf) Vector3f x = end1.getPosition (). subtrahere (end2.getPosition ()); float lengde = x.length (); hvis (lengde> targetLength) x.normalizeLocal (); x.multLocal (lengde - mållengde); Vector3f dv = end2.getVelocity (). Subtrahere (end1.getVelocity ()); Vector3f force = x.mult (stivhet); force.subtract (dv.mult (damping / 10f)); end1.applyForce (force.negate ()); end2.applyForce (kraft);
Når vi lager en fjær, setter vi vårens naturlige lengde til bare litt mindre enn avstanden mellom de to endepunktene. Dette holder rutenettet stramt, selv når du er i ro, og forbedrer utseendet noe.
De Oppdater()
Metoden kontrollerer først om våren strekkes utover sin naturlige lengde. Hvis den ikke strekkes, skjer ingenting. Hvis det er, bruker vi den modifiserte Hooke's Law til å finne kraften fra våren og bruke den til de to forbundne massene.
Det er en annen klasse vi må lage for å kunne vise linjene riktig. De LineControl
vil ta vare på å flytte, skalere og rotere linjene:
offentlig klasse LineControl utvider AbstractControl private PointMass end1, end2; offentlig LineControl (PointMass end1, PointMass end2) this.end1 = end1; this.end2 = end2; @Override protected void controlUpdate (float tpf) // bevegelse spatial.setLocalTranslation (end1.getPosition ()); // skala Vector3f dif = end2.getPosition (). trekke ned (end1.getPosition ()); spatial.setLocalScale (dif.length ()); // rotasjon spatial.lookAt (end2.getPosition (), ny Vector3f (1,0,0)); @Override protected void controlRender (RenderManager rm, ViewPort vp)
Nå som vi har de nødvendige nestede klassene, er vi klare til å lage nettverket. Vi begynner med å lage PointMass
gjenstander ved hvert skjæringspunkt på rutenettet. Vi lager også noen fast anker PointMass
gjenstander for å holde rutenettet på plass. Vi knytter sammen massene sammen med fjærer:
offentlig klasse grid private node gridNode; private vår [] fjærer; private PointMass [] [] poeng; privat geometri defaultLine; privat geometri thickLine; offentlig rutenett (rektangelstørrelse, Vector2f mellomrom, Nod guiNode, AssetManager assetManager) gridNode = ny Nod (); guiNode.attachChild (gridNode); defaultLine = createLine (1f, assetManager); thickLine = createLine (3f, assetManager); ArrayList springList = ny ArrayList (); flytestivhet = 0,28f; flytdemping = 0,06f; int numColumns = (int) (size.width / spacing.x) + 2; int numRows = (int) (size.height / spacing.y) + 2; poeng = nytt PointMass [numColumns] [numRows]; PointMass [] [] fixedPoints = new PointMass [numColumns] [numRows]; // lag poengmassene float xCoord = 0, yCoord = 0; for (int rad = 0; rad < 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; y0) hvis (y% 3 == 0) line = thickLine; ellers line = defaultLine; springList.add (ny vår (poeng [x-1] [y], poeng [x] [y], stivhet, demping, gridNode, true, line.clone ())); hvis (y> 0) if (x% 3 == 0) line = thickLine; ellers line = defaultLine; springList.add (ny vår (poeng [x] [y-1], poeng [x] [y], stivhet, demping, gridNode, true, line.clone ()));
Den første til
sløyfe skaper både vanlige masser og faste masser ved hvert skjæringspunkt av rutenettet. Vi vil ikke faktisk bruke alle de faste massene, og de ubrukte massene vil rett og slett bli søppel samlet inn et tidspunkt etter at konstruktøren ender. Vi kan optimalisere ved å unngå å skape unødvendige objekter, men siden rutenettet vanligvis bare opprettes en gang, vil det ikke gjøre mye forskjell.
I tillegg til å bruke ankerpunktsmasser rundt grensen til rutenettet, vil vi også bruke noen ankermasser inne i rutenettet. Disse vil bli brukt til å forsiktig trekke rutenettet tilbake til sin opprinnelige posisjon etter å ha blitt deformert.
Siden ankerpunktene aldri beveger seg, trenger de ikke å bli oppdatert hver ramme. Vi kan bare koble dem opp til fjærene og glemme dem. Derfor har vi ikke en medlemsvariabel i Nett
klasse for disse massene.
Det finnes en rekke verdier du kan finjustere i etableringen av nettet. De viktigste er stivhet og demping av fjærene. Stivheten og dempingen av grenseankrene og indre ankre settes uavhengig av hovedfjærene. Høyere stivhetsverdier vil gjøre fjærene oscillere raskere, og høyere dempingsverdier vil føre til at fjærene senkes raskere.
Det er en siste ting å nevne: createLine ()
metode.
Private Geometry createLine (flyt tykkelse, AssetManager assetManager) Vector3f [] vertices = ny Vector3f (0,0,0), ny Vector3f (0,0,1); int [] indeks = 0,1; Mesh lineMesh = nytt Mesh (); lineMesh.setMode (Mesh.Mode.Lines); lineMesh.setLineWidth (tykkelse); lineMesh.setBuffer (VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer (vertices)); lineMesh.setBuffer (VertexBuffer.Type.Index, 1, BufferUtils.createIntBuffer (indekser)); lineMesh.updateBound (); Geometri lineGeom = ny geometri ("lineMesh", lineMesh); Material matWireframe = nytt materiale (assetManager, "Common / MatDefs / Misc / Unshaded.j3md"); . MatWireframe.getAdditionalRenderState () setFaceCullMode (RenderState.FaceCullMode.Off); matWireframe.setColor ("Color", ny ColorRGBA (0.118f, 0.118f, 0.545f, 0.25f)); . MatWireframe.getAdditionalRenderState () setBlendMode (BlendMode.AlphaAdditive); lineGeom.setMaterial (matWireframe); return lineGeom;
Her oppretter vi i utgangspunktet en linje ved å spesifisere linjene på linjen og rekkefølgen av toppene, lage et nett, legge til et blått materiale, og så videre. Hvis du vil forstå prosessen med linjeopprettingen nøyaktig, kan du alltid ta en titt på jME-opplæringen.
Hvorfor må linjeopprettelsen være så komplisert? Er det ikke bare en enkel linje? Ja, det er, men du må se på hva jME har tenkt å være. Vanligvis i 3D-spill har du ikke enkelte linjer eller trekanter i spillet, men heller modeller med teksturer og animasjoner. Så mens det er mulig å generere en enkelt linje i jME, er hovedfokuset på import av modeller som er generert med annen programvare, for eksempel Blender.For at nettverket skal bevege seg, må vi oppdatere det hver ramme. Dette er veldig enkelt, da vi allerede gjorde alt det harde arbeidet i PointMass
og Vår
klasser.
offentlig ugyldig oppdatering (float tpf) for (int i = 0; iNå vil vi legge til noen metoder som manipulerer rutenettet. Du kan legge til metoder for enhver form for manipulasjon du kan tenke på. Vi vil implementere tre typer manipulasjoner her: skyve en del av rutenettet i en gitt retning, skyve rutenettet utover fra et tidspunkt og trekke ruten inn mot et punkt. Alle tre vil påvirke rutenettet innenfor en gitt radius fra noen målpunkt.
Nedenfor er noen bilder av disse manipulasjonene i gang:
Bølge opprettet ved å skyve rutenettet langs z-aksen.
Kuler avviser rutenettet utover.
Suger ruten innover.Og her er metodene for effektene:
Offentlig ugyldig applyDirectedForce (Vector3f kraft, Vector3f posisjon, floatradius) for (int x = 0; xBruke Gitteret i Shape Blaster
Nå er det på tide å bruke rutenettet i spillet vårt. Vi begynner med å erklære en
Nett
variabel iMonkeyBlasterMain
og initialisere den innsimpleInitApp ()
:Rektangelstørrelse = Ny rektangel (0, 0, settings.getWidth (), settings.getHeight ()); Vector2f mellomrom = ny Vector2f (25,25); grid = nytt grid (størrelse, mellomrom, guiNode, assetManager);Da må vi ringe
grid.update (float tpf)
frasimpleUpdate
metode:@Override public void simpleUpdate (float tpf) if ((Boolean) player.getUserData ("live")) spawnEnemies (); spawnBlackHoles (); handleCollisions (); handleGravity (TPF); ellers hvis (System.currentTimeMillis () - (Long) player.getUserData ("dieTime")> 4000f &&! gameOver) // spawn player player.setLocalTranslation (500.500,0); guiNode.attachChild (spiller); player.setUserData ( "levende", true); sound.spawn (); grid.update (tpf); hud.update ();Deretter må vi ringe effektmetodene fra de riktige stedene i spillet vårt.
Den første, som skaper en bølge når spilleren gyter, er ganske enkelt - vi utvider bare stedet der vi gyter spilleren i
simpleUpdate (float tpf)
med følgende linje:grid.applyDirectedForce (ny Vector3f (0,0,5000), player.getLocalTranslation (), 100);Legg merke til at vi bruker en kraft i z-retningen. Vi kan ha et 2D-spill, men siden jME er en 3D-motor, kan vi også enkelt bruke 3D-effekter. Hvis vi skulle rotere kameraet, ville vi se rutenettet hoppe innover og utover.
Den andre og tredje effekten må håndteres i kontroller. Når kuler flyr gjennom spillet, kaller de denne metoden i
controlUpdate (float tpf)
:grid.applyExplosiveForce (direction.length () * (18f), spatial.getLocalTranslation (), 80);Dette vil gjøre kuler støte rutenettet proporsjonalt til deres hastighet. Det var ganske enkelt.
Det ligner på de svarte hullene:
grid.applyImplosiveForce (FastMath.sin (sprayAngle / 2) * 10 +20, spatial.getLocalTranslation (), 250);Dette gjør det svarte hullet suge i rutenett med varierende mengde kraft. Jeg gjenbrukte
sprayAngle
variabel, noe som vil føre til at kraften på rutenettet pulserer i synkronisering med vinkelen det sprays partikler (selv om det er halvparten av frekvensen som skyldes divisjonen med to). Kraften som kommer inn, vil variere sinusformet mellom10
og30
.For å gjøre dette arbeidet må du ikke glemme å passere
Nett
tilBulletControl
ogBlackHoleControl
.
interpole
Vi kan optimalisere rutenettet ved å forbedre visuell kvalitet for et gitt antall fjærer uten å øke ytelseskostnaden betydelig.
Vi vil gjøre grid tettere ved å legge til linjesegmenter inne i eksisterende gridceller. Vi gjør det ved å tegne linjer fra midtpunktet til den ene siden av cellen til midtpunktet på motsatt side. Bildet nedenfor viser de nye interpolerte linjene i rødt:
Vi vil skape de ekstra linjene i konstruktøren til vår
Nett
klasse. Hvis du ser på det, ser du totil
sløyfer hvor vi knytter punktmassene med fjærene. Bare sett inn denne koden der:hvis (x> 0 && y> 0) Geometry additionalLine = defaultLine.clone (); additionalLine.addControl (new AdditionalLineControl (poeng [x-1] [y], poeng [x] [y], poeng [x-1] [y-1], poeng [x] [y-1])); gridNode.attachChild (additionalLine); Geometri additionalLine2 = defaultLine.clone (); additionalLine2.addControl (new AdditionalLineControl (poeng [x] [y-1], poeng [x] [y], poeng [x-1] [y-1], poeng [x-1] [y])); gridNode.attachChild (additionalLine2);Men som du vet, er det ikke eneste vi trenger å skape objekter. Vi må også legge til en kontroll for dem for å få dem til å oppføre seg riktig. Som du kan se over, er det
AdditionalLineControl
får fire punktmasser passert til, slik at den kan beregne posisjon, rotasjon og skala:offentlig klasse AdditionalLineControl utvider AbstractControl private PointMass end11, end12, end21, end22; offentlig AdditionalLineControl (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) // bevegelse spatial.setLocalTranslation (posisjon1 ()); // skala Vector3f dif = position2 (). subtrahere (posisjon1 ()); spatial.setLocalScale (dif.length ()); // rotasjon spatial.lookAt (posisjon2 (), ny Vector3f (1,0,0)); privat Vector3f posisjon1 () returner ny Vector3f (). interpolere (end11.getPosition (), end12.getPosition (), 0.5f); privat Vector3f posisjon2 () returner ny Vector3f (). interpolere (end21.getPosition (), end22.getPosition (), 0.5f); @Override protected void controlRender (RenderManager rm, ViewPort vp)
Hva blir det neste?
Vi har grunnleggende spill og effekter implementert. Det er opp til deg å gjøre det til et komplett og polert spill med din egen smak. Prøv å legge til noen interessante nye mekanikere, noen kule nye effekter eller en unik historie. Hvis du ikke er sikker på hvor du skal begynne, er det noen forslag:
- Opprett nye fiendtlige typer som slanger eller eksploderende fiender.
- Lag nye våpentyper som å søke raketter eller en lynpistol.
- Legg til en tittel og hovedmeny.
- Legg til et high score-bord.
- Legg til noen power-ups, for eksempel skjold eller bomber. For bonuspoeng, bli kreative med din power-ups. Du kan lage power-ups som manipulerer tyngdekraft, endrer tid, eller vokser som organismer. Du kan knytte en gigantisk, fysikkbasert ødeleggelseskule til skipet for å smadre fiender. Forsøk å finne power-ups som er morsomme og hjelpe spillet ditt skiller seg ut.
- Opprett flere nivåer. Hardere nivåer kan introdusere tøffere fiender og mer avanserte våpen og powerups.
- Tillat en annen spiller å delta med en gamepad.
- La arenaen rulle slik at den kan være større enn spillvinduet.
- Legg til miljøfarer som lasere.
- Legg til et butikk eller nivelleringssystem, og la spilleren få oppgraderinger.
Takk for at du leste!