Erstellen Sie einen Neon-Vektor-Shooter in jMonkeyEngine Die Grundlagen

In dieser Tutorial-Serie erkläre ich, wie Sie ein von Geometry Wars inspiriertes Spiel mit der jMonkeyEngine erstellen. Die jMonkeyEngine (kurz "jME") ist eine Open-Source-Java-Engine für 3D-Spiele. Weitere Informationen finden Sie auf ihrer Website oder in unserem Handbuch "How to Learn Learn jMonkeyEngine".

Während die jMonkeyEngine an sich eine 3D-Spielengine ist, ist es auch möglich, 2D-Spiele damit zu erstellen.

zusammenhängende Posts
Diese Tutorialserie basiert auf der Serie von Michael Hoffman, in der erklärt wird, wie man dasselbe Spiel in XNA erstellt:
  • Erstellen Sie einen Neon-Vektor-Shooter in XNA

Die fünf Kapitel des Tutorials sind bestimmten Komponenten des Spiels gewidmet:

  1. Initialisieren Sie die 2D-Szene, laden Sie einige Grafiken und zeigen Sie sie an.
  2. Fügen Sie Feinde, Kollisionen und Soundeffekte hinzu.
  3. Fügen Sie die GUI und die schwarzen Löcher hinzu.
  4. Fügen Sie einige spektakuläre Partikeleffekte hinzu.
  5. Fügen Sie das Warping-Hintergrundraster hinzu.

Als kleinen visuellen Vorgeschmack, hier das Endergebnis unserer Bemühungen:


… Und hier sind unsere Ergebnisse nach diesem ersten Kapitel:


Die Musik und Soundeffekte, die Sie in diesen Videos hören können, wurden von RetroModular erstellt. Sie können hier nachlesen, wie er das gemacht hat.

Die Sprites stammen von Jacob Zinman-Jeanes, unserem ansässigen Designer Tuts +. Das gesamte Bildmaterial befindet sich in der Quelldatei zum Herunterladen.


Der Font ist Nova Square von Wojciech Kalinowski.

Das Tutorial soll Ihnen helfen, die Grundlagen der jMonkeyEngine zu erlernen und Ihr erstes Spiel damit zu erstellen. Während wir die Motorfunktionen nutzen werden, verwenden wir keine komplizierten Tools, um die Leistung zu verbessern. Wann immer es ein fortgeschritteneres Werkzeug gibt, um eine Funktion zu implementieren, verlinke ich mich auf die entsprechenden jME-Tutorials, halte mich jedoch an die einfache Vorgehensweise im Tutorial selbst. Wenn Sie sich mehr mit jME beschäftigen, können Sie später Ihre Version von MonkeyBlaster erweitern und verbessern.

Auf geht's!


Überblick

Das erste Kapitel enthält das Laden der erforderlichen Bilder, die Handhabung der Eingabe und das Bewegen und Schießen des Schiffes des Spielers.

Um dies zu erreichen, benötigen wir drei Klassen:

  • MonkeyBlasterMain: Unsere Hauptklasse, die die Spielschleife und das grundlegende Gameplay enthält.
  • PlayerControl: Diese Klasse bestimmt, wie sich der Spieler verhält.
  • BulletControlÄhnlich wie oben definiert dies das Verhalten unserer Kugeln.

Im Verlauf des Tutorials geben wir den allgemeinen Code für das Gameplay ein MonkeyBlasterMain und verwalten Sie die Objekte auf dem Bildschirm hauptsächlich über Steuerelemente und andere Klassen. Besonderheiten wie Sound werden auch eigene Klassen haben.


Laden des Spielerschiffs

Wenn Sie das jME SDK noch nicht heruntergeladen haben, ist es an der Zeit! Sie finden es auf der jMonkeyEngine-Homepage.

Erstellen Sie ein neues Projekt im jME SDK. Es wird automatisch die Hauptklasse generiert, die der folgenden ähnlich aussieht:

Paket Monkeyblaster; import com.jme3.app.SimpleApplication; import com.jme3.renderer.RenderManager; public class MonkeyBlasterMain erweitert SimpleApplication public static void main (String [] args) Main app = new Main (); app.start ();  @Override public void simpleInitApp ()  @Override public void simpleUpdate (float tpf)  @Override public void simpleRender (RenderManager rm) 

Wir beginnen mit dem Überschreiben simpleInitApp (). Diese Methode wird beim Start der Anwendung aufgerufen. Hier können Sie alle Komponenten einrichten:

 @Override public void simpleInitApp () // Kamera für 2D-Spiele einrichten cam.setParallelProjection (true); cam.setLocation (neuer Vector3f (0,0,0.5f)); getFlyByCamera (). setEnabled (false); // Schalten Sie die Statistikansicht aus (Sie können sie eingeschaltet lassen, wenn Sie möchten) setDisplayStatView (false); setDisplayFps (false); 

Zuerst müssen wir die Kamera ein wenig anpassen, da jME im Grunde eine 3D-Spielengine ist. Die Statistikansicht im zweiten Absatz kann sehr interessant sein, aber so deaktivieren Sie sie.

Wenn Sie das Spiel jetzt starten, können Sie… nichts sehen.

Nun, wir müssen den Spieler ins Spiel laden! Wir erstellen eine kleine Methode zum Laden unserer Entitäten:

 private Spatial getSpatial (Stringname) Node node = neuer Knoten (Name); // Bild laden Bild pic = neues Bild (Name); Texture2D tex = (Texture2D) assetManager.loadTexture ("Textures /" + name + ". Png"); pic.setTexture (assetManager, tex, true); // Bildflat anpassen width = tex.getImage (). getWidth (); float height = tex.getImage (). getHeight (); pic.setWidth (Breite); pic.setHeight (Höhe); Bildbewegungsbereich (-breite / 2f, -höhe / 2f, 0); // füge dem Bild ein Material hinzu Material picMat = neues Material (assetManager, "Common / MatDefs / Gui / Gui.j3md"); picMat.getAdditionalRenderState (). setBlendMode (BlendMode.AlphaAdditive); node.setMaterial (picMat); // setze den Radius des räumlichen // (nur Breite als einfache Annäherung verwendend) node.setUserData ("radius", width / 2); // das Bild an den Knoten anhängen und zurückgeben node.attachChild (pic); Rückkehrknoten; 

Am Anfang erstellen wir einen Knoten, der unser Bild enthalten soll.

Spitze: Der JME-Szenengraph besteht aus Spachteln (Knoten, Bilder, Geometrien usw.). Wann immer Sie ein räumliches Objekt hinzufügen guiNode, es wird in der Szene sichtbar. Wir werden die verwenden guiNode weil wir ein 2D-Spiel erstellen. Sie können Spatials an andere Spaces anhängen und so Ihre Szene organisieren. Um ein wahrer Meister des Szenendiagramms zu werden, empfehle ich dieses JME-Szenendiagramm-Tutorial.

Nach dem Erstellen des Knotens laden wir das Bild und wenden die entsprechende Textur an. Das Anwenden der richtigen Größe auf das Bild ist ziemlich einfach zu verstehen, aber warum müssen wir es verschieben??

Wenn Sie ein Bild in jME laden, befindet sich der Drehpunkt nicht in der Mitte, sondern in einer Ecke des Bildes. Wir können das Bild jedoch um die Hälfte nach links und die Hälfte nach oben verschieben und zu einem anderen Knoten hinzufügen. Wenn wir dann den übergeordneten Knoten drehen, wird das Bild selbst um seine Mitte gedreht.

Im nächsten Schritt fügen Sie dem Bild ein Material hinzu. Ein Material bestimmt, wie das Bild angezeigt wird. In diesem Beispiel verwenden wir das Standard-GUI-Material und legen das fest Mischmodus zu AlphaAdditive. Dies bedeutet, dass transparente Teile mehrerer Bilder überlappen und heller werden. Dies wird später nützlich sein, um Explosionen "glänzender" zu machen.

Zum Schluss fügen wir unser Bild dem Knoten hinzu und geben es zurück.

Jetzt müssen wir den Player zum hinzufügen guiNode. Wir verlängern simpleInitApp ein bisschen mehr:

// Setup des Players Player = getSpatial ("Player"); player.setUserData ("lebendig", wahr); player.move (settings.getWidth () / 2, settings.getHeight () / 2, 0); guiNode.attachChild (Spieler);

Kurz gesagt: Wir laden den Player, konfigurieren einige Daten, bewegen ihn in die Mitte des Bildschirms und hängen ihn an guiNode damit es angezeigt wird.

Benutzerdaten ist einfach ein paar Daten, die Sie an jeden Raum anhängen können. In diesem Fall fügen wir einen Boolean hinzu und nennen es am Leben, damit wir nachsehen können, ob der Spieler lebt. Wir werden das später verwenden.

Starten Sie jetzt das Programm! Sie sollten den Player in der Mitte sehen können. Im Moment ist es ziemlich langweilig, gebe ich zu. Lassen Sie uns noch etwas hinzufügen!


Handhabung der Eingabe und Bewegen des Players

Die Eingabe von jMonkeyEngine ist ziemlich einfach, wenn Sie es einmal getan haben. Wir beginnen mit der Implementierung eines Action Listener:

public class MonkeyBlasterMain erweitert SimpleApplication implementiert ActionListener 

Jetzt fügen wir für jede Taste die Eingabezuordnung und den Listener hinzu simpleInitApp ():

 inputManager.addMapping ("left", neuer KeyTrigger (KeyInput.KEY_LEFT)); inputManager.addMapping ("right", neuer KeyTrigger (KeyInput.KEY_RIGHT)); inputManager.addMapping ("up", neuer KeyTrigger (KeyInput.KEY_UP)); inputManager.addMapping ("down", neuer KeyTrigger (KeyInput.KEY_DOWN)); inputManager.addMapping ("return", neuer KeyTrigger (KeyInput.KEY_RETURN)); inputManager.addListener (this, "left"); inputManager.addListener (dies "rechts"); inputManager.addListener (this, "up"); inputManager.addListener (this, "down"); inputManager.addListener (this, "return");

Immer wenn eine dieser Tasten gedrückt oder losgelassen wird, die Methode onAction wird genannt. Bevor wir uns auf was eigentlich konzentrieren tun Wenn eine Taste gedrückt wird, müssen wir unserem Player ein Steuerelement hinzufügen.

Info: Steuerelemente repräsentieren bestimmte Verhaltensweisen von Objekten in der Szene. Zum Beispiel könnten Sie eine hinzufügen FightControl und ein IdleControl zu einer feindlichen KI. Abhängig von der Situation können Sie Steuerelemente aktivieren und deaktivieren oder anhängen und trennen.

Unsere PlayerControl sorgt dafür, dass der Player bei jedem Tastendruck bewegt wird, dreht sich in die richtige Richtung und stellt sicher, dass der Player den Bildschirm nicht verlässt.

Bitte schön:

public class PlayerControl erweitert AbstractControl private int screenWidth, screenHeight; // bewegt sich der Spieler gerade? public boolean nach oben, unten, links, rechts; // Geschwindigkeit des privaten Schwimmers des Spielers Geschwindigkeit = 800f; // lastRotation des privaten Floates des Players lastRotation; public PlayerControl (int width, int height) this.screenWidth = width; this.screenHeight = Höhe;  @Override protected void controlUpdate (float tpf) // Bewegen Sie den Player in eine bestimmte Richtung //, wenn er sich nicht außerhalb des Bildschirms befindet, wenn (up) if (spac.getLocalTranslation (). Y < screenHeight - (Float)spatial.getUserData("radius"))  spatial.move(0,tpf*speed,0);  spatial.rotate(0,0,-lastRotation + FastMath.PI/2); lastRotation=FastMath.PI/2;  else if (down)  if (spatial.getLocalTranslation().y > (Float) spac.getUserData ("radius")) spatel.move (0, tpf * -speed, 0);  räumlich.rotate (0,0, -lastRotation + FastMath.PI * 1.5f); lastRotation = FastMath.PI * 1.5f;  else if (left) if (spat.getLocalTranslation (). x> (Gleitkommazahl) spac.getUserData ("radius")) spatel.move (tpf * -speed, 0,0);  räumlich.rotate (0,0, -lastRotation + FastMath.PI); lastRotation = FastMath.PI;  else if (rechts) if (spacious.getLocalTranslation (). x < screenWidth - (Float)spatial.getUserData("radius"))  spatial.move(tpf*speed,0,0);  spatial.rotate(0,0,-lastRotation + 0); lastRotation=0;   @Override protected void controlRender(RenderManager rm, ViewPort vp)  // reset the moving values (i.e. for spawning) public void reset()  up = false; down = false; left = false; right = false;  

Okay; Jetzt schauen wir uns den Code Stück für Stück an.

 private int screenWidth, screenHeight; // bewegt sich der Spieler gerade? public boolean nach oben, unten, links, rechts; // Geschwindigkeit des privaten Schwimmers des Spielers Geschwindigkeit = 800f; // lastRotation des privaten Floates des Players lastRotation; public PlayerControl (int width, int height) this.screenWidth = width; this.screenHeight = Höhe; 

Zunächst initialisieren wir einige Variablen und definieren, in welche Richtung und wie schnell sich der Spieler bewegt und wie weit er gedreht wird. Dann setzen wir die Bildschirmbreite und screenHeight, was wir in der nächsten großen Methode brauchen werden.

controlUpdate (float tpf) wird von jME bei jedem Aktualisierungszyklus automatisch aufgerufen. Die Variable tpf gibt die Zeit seit der letzten Aktualisierung an. Dies ist erforderlich, um die Geschwindigkeit zu steuern: Wenn einige Computer doppelt so lange brauchen, um ein Update zu berechnen, als andere, sollte sich der Player in einem einzigen Update auf diesen Computern doppelt so weit bewegen.

Nun zum ersten ob Aussage:

 if (auf) if (spat.getLocalTranslation (). y < screenHeight - (Float)spatial.getUserData("radius"))  spatial.move(0,tpf*speed,0); 

Wir prüfen, ob der Spieler nach oben geht, und wenn ja, prüfen wir, ob er weiter steigen kann. Wenn es weit genug von der Grenze entfernt ist, verschieben wir es einfach etwas nach oben.

Nun zur Rotation:

 räumlich.rotate (0,0, -lastRotation + FastMath.PI / 2); lastRotation = FastMath.PI / 2;

Wir drehen den Spieler zurück lastRotation seine ursprüngliche Richtung zu sehen. Aus dieser Richtung können wir den Player in die Richtung drehen, in die er schauen soll. Zum Schluss speichern wir die tatsächliche Drehung.

Wir verwenden dieselbe Logik für alle vier Richtungen. Das zurücksetzen () Die Methode ist nur hier, um alle Werte wieder auf Null zu setzen, um den Player erneut starten zu können.

So haben wir endlich die Kontrolle für unseren Spieler. Es ist Zeit, es dem tatsächlichen Raum hinzuzufügen. Fügen Sie einfach die folgende Zeile hinzu simpleInitApp () Methode:

player.addControl (new PlayerControl (settings.getWidth (), settings.getHeight ()));

Das Objekt die Einstellungen ist in der Klasse enthalten SimpleApplication. Es enthält Daten zu den Anzeigeeinstellungen des Spiels.

Wenn wir das Spiel jetzt starten, passiert noch nichts. Wir müssen dem Programm mitteilen, was zu tun ist, wenn eine der zugeordneten Tasten gedrückt wird. Um dies zu tun, überschreiben wir das onAction Methode:

 public void onAction (Zeichenfolgenname, boolean isPressed, float tpf) if ((Boolean) player.getUserData ("alive")) if (name.equals ("up")) player.getControl (PlayerControl.class). up = isPressed;  else if (name.equals ("down")) player.getControl (PlayerControl.class) .down = isPressed;  else if (name.equals ("left")) player.getControl (PlayerControl.class) .left = isPressed;  else if (name.equals ("right")) player.getControl (PlayerControl.class) .right = isPressed; 

Für jede gedrückte Taste sagen wir die PlayerControl der neue Status des Schlüssels. Jetzt ist es endlich Zeit, unser Spiel zu starten und etwas auf dem Bildschirm zu sehen!

Wenn Sie sich darüber freuen, dass Sie die Grundlagen des Eingabe- und Verhaltensmanagements verstehen, ist es Zeit, dasselbe noch einmal zu tun - diesmal für die Kugeln.


Einige Aufzählungsaktion hinzufügen

Wenn wir welche haben wollen echt Aktion läuft, müssen wir in der Lage sein, einige Feinde zu erschießen. Wir befolgen das gleiche grundlegende Verfahren wie im vorherigen Schritt: Eingabe verwalten, Aufzählungszeichen erstellen und Verhalten hinzufügen.

Um mit der Maus umgehen zu können, implementieren wir einen anderen Listener:

public class MonkeyBlasterMain erweitert SimpleApplication implementiert ActionListener, AnalogListener 

Bevor etwas passiert, müssen wir das Mapping und den Listener wie beim letzten Mal hinzufügen. Das machen wir im simpleInitApp () Methode neben der anderen Eingabe-Initialisierung:

 inputManager.addMapping ("mousePick", neuer MouseButtonTrigger (MouseInput.BUTTON_LEFT)); inputManager.addListener (dies "mousePick");

Immer wenn wir mit der Maus klicken, wird die Methode angezeigt onAnalog wird gerufen Bevor wir uns dem eigentlichen Dreh widmen, müssen wir eine kleine Hilfsmethode implementieren, Vector3f getAimDirection (), Dies gibt uns die Richtung, auf die Sie schießen können, indem Sie die Position des Spielers von der Position der Maus abziehen:

 private Vector3f getAimDirection () Vector2f mouse = inputManager.getCursorPosition (); Vector3f playerPos = player.getLocalTranslation (); Vector3f dif = new Vector3f (mouse.x-playerPos.x, mouse.y-playerPos.y, 0); return dif.normalizeLocal (); 
Spitze: Beim Anhängen von Objekten an die guiNode, Ihre lokalen Übersetzungseinheiten entsprechen einem Pixel. Dies macht es uns leicht, die Richtung zu berechnen, da die Cursorposition auch in Pixeleinheiten angegeben wird.

Nun, da wir eine Richtung haben, auf die wir schießen können, lassen Sie uns das eigentliche Schießen implementieren:

 public void onAnalog (Zeichenfolgenname, Gleitkommazahlwert, Gleitkommazahl tpf) if ((Boolean) player.getUserData ("alive")) if (name.equals ("mousePick")) // schießt das Bullet if (System.currentTimeMillis () - bulletCooldown> 83f) bulletCooldown = System.currentTimeMillis (); Vector3f aim = getAimDirection (); Vector3f offset = new Vector3f (aim.y / 3, -aim.x / 3,0); // init bullet 1 Spatial bullet = getSpatial ("Bullet"); Vector3f finalOffset = aim.add (Versatz) .mult (30); Vector3f trans = player.getLocalTranslation (). Add (finalOffset); bullet.setLocalTranslation (trans); bullet.addControl (neues BulletControl (Ziel, settings.getWidth (), settings.getHeight ())); bulletNode.attachChild (bullet); // init bullet 2 Spatial bullet2 = getSpatial ("Bullet"); finalOffset = aim.add (offset.negate ()). mult (30); trans = player.getLocalTranslation (). add (finalOffset); bullet2.setLocalTranslation (trans); bullet2.addControl (neues BulletControl (Ziel, settings.getWidth (), settings.getHeight ())); bulletNode.attachChild (bullet2); 

Okay, lass uns das durchgehen:

 if (System.currentTimeMillis () - bulletCooldown> 83f) bulletCooldown = System.currentTimeMillis (); Vector3f aim = getAimDirection (); Vector3f offset = new Vector3f (aim.y / 3, -aim.x / 3,0);

Wenn der Spieler noch am Leben ist und die Maustaste gedrückt wird, prüft unser Code zuerst, ob der letzte Schuss vor mindestens 83 ms abgegeben wurde (bulletCooldown ist eine lange Variable, die wir am Anfang der Klasse initialisieren). Wenn ja, dann dürfen wir schießen und berechnen die richtige Richtung für das Zielen und den Versatz.

// init bullet 1 Spatial bullet = getSpatial ("Bullet"); Vector3f finalOffset = aim.add (Versatz) .mult (30); Vector3f trans = player.getLocalTranslation (). Add (finalOffset); bullet.setLocalTranslation (trans); bullet.addControl (neues BulletControl (Ziel, settings.getWidth (), settings.getHeight ())); bulletNode.attachChild (bullet); // init bullet 2 Spatial bullet2 = getSpatial ("Bullet"); finalOffset = aim.add (offset.negate ()). mult (30); trans = player.getLocalTranslation (). add (finalOffset); bullet2.setLocalTranslation (trans); bullet2.addControl (neues BulletControl (Ziel, settings.getWidth (), settings.getHeight ())); bulletNode.attachChild (bullet2);

Wir wollen zwei Kugeln nebeneinander erzeugen, also müssen wir jedem einen kleinen Versatz hinzufügen. Ein geeigneter Versatz ist orthogonal zur Zielrichtung, was leicht durch Umschalten der erreicht werden kann x und y Werte und negiert einen davon. Der zweite wird einfach eine Negation des ersten sein.

// init bullet 1 Spatial bullet = getSpatial ("Bullet"); Vector3f finalOffset = aim.add (Versatz) .mult (30); Vector3f trans = player.getLocalTranslation (). Add (finalOffset); bullet.setLocalTranslation (trans); bullet.addControl (neues BulletControl (Ziel, settings.getWidth (), settings.getHeight ())); bulletNode.attachChild (bullet); // init bullet 2 Spatial bullet2 = getSpatial ("Bullet"); finalOffset = aim.add (offset.negate ()). mult (30); trans = player.getLocalTranslation (). add (finalOffset); bullet2.setLocalTranslation (trans); bullet2.addControl (neues BulletControl (Ziel, settings.getWidth (), settings.getHeight ())); bulletNode.attachChild (bullet2);

Der Rest sollte ziemlich vertraut erscheinen: Wir initialisieren die Kugel, indem wir unsere eigene verwenden getSpatial Methode von Anfang an. Dann übersetzen wir es an die richtige Stelle und hängen es an den Knoten an. Aber warte auf welchen Knoten?

Wir organisieren unsere Entitäten in bestimmten Knoten, daher ist es sinnvoll, einen Knoten zu erstellen, an den wir alle unsere Aufzählungszeichen anhängen können. Um die Kinder dieses Knotens anzuzeigen, müssen wir ihn an den Knoten anhängen guiNode.

Die Initialisierung in simpleInitApp () ist ziemlich einfach:

// Einrichten des bulletNode bulletNode = neuer Knoten ("Aufzählungszeichen"); guiNode.attachChild (bulletNode);

Wenn Sie fortfahren und das Spiel starten, können Sie die Kugeln sehen, aber sie bewegen sich nicht! Wenn Sie sich selbst testen möchten, unterbrechen Sie das Lesen und überlegen Sie, was wir tun müssen, um sie in Bewegung zu setzen.

Hast du es herausgefunden?

Wir müssen jedem Geschoss eine Kontrolle hinzufügen, die sich um seine Bewegung kümmert. Dazu erstellen wir eine weitere Klasse namens BulletControl:

public class BulletControl erweitert AbstractControl private int screenWidth, screenHeight; Geschwindigkeit des privaten Schwimmers = 1100f; öffentliche Vector3f-Richtung; Rotation des privaten Schwimmers; public BulletControl (Vector3f direction, int screenWidth, int screenHeight) this.direction = direction; this.screenWidth = screenWidth; this.screenHeight = screenHeight;  @Override protected void controlUpdate (Float-Tpf) // Bewegung von räumlichen Bewegungen (direction.mult (Geschwindigkeit * Tpf)); // Rotation Float actualRotation = MonkeyBlasterMain.getAngleFromVector (Richtung); if (actualRotation! = Drehung) räumlich.rotieren (0,0, actualRotation - Drehung); Drehung = actualRotation;  // Grenzen überprüfen Vector3f loc = spat..localTranslation (); if (loc.x> screenWidth || loc.y> screenHeight || loc.x.) < 0 || loc.y < 0)  spatial.removeFromParent();   @Override protected void controlRender(RenderManager rm, ViewPort vp)  

Ein kurzer Blick auf die Struktur der Klasse zeigt, dass sie der PlayerControl Klasse. Der Hauptunterschied ist, dass wir keine Schlüssel haben, die überprüft werden müssen, und wir haben einen Richtung Variable. Wir bewegen das Geschoss einfach in seine Richtung und drehen es entsprechend.

 Vector3f loc = räumlich.getLocalTranslation (); if (loc.x> screenWidth || loc.y> screenHeight || loc.x.) < 0 || loc.y < 0)  spatial.removeFromParent(); 

Im letzten Block prüfen wir, ob sich das Aufzählungszeichen außerhalb der Bildschirmgrenzen befindet. Wenn dies der Fall ist, entfernen wir es von seinem übergeordneten Knoten, wodurch das Objekt gelöscht wird.

Möglicherweise haben Sie diesen Methodenaufruf abgefangen:

MonkeyBlasterMain.getAngleFromVector (Richtung);

Es bezieht sich auf eine kurze statische mathematische Hilfsmethode in der Hauptklasse. Ich habe zwei davon erstellt, eine konvertiert einen Winkel in einem Vektor im 2D-Raum und die andere konvertiert solche Vektoren wieder in einen Winkelwert.

 public static float getAngleFromVector (Vector3f vec) Vector2f vec2 = neuer Vector2f (vec.x, vec.y); return vec2.getAngle ();  public static Vector3f getVectorFromAngle (Float-Winkel) return new Vector3f (FastMath.cos (Winkel), FastMath.sin (Winkel), 0); 
Spitze: Wenn Sie sich durch all diese Vektoroperationen ziemlich verwirrt fühlen, tun Sie sich einen Gefallen und schauen Sie sich einige Tutorials über Vektormathematik an. Dies ist sowohl im 2D- als auch im 3D-Raum unerlässlich. Wenn Sie gerade dabei sind, sollten Sie auch den Unterschied zwischen Grad und Bogenmaß nachschlagen. Und wenn Sie mehr in die Programmierung von 3D-Spielen einsteigen möchten, sind auch Quaternionen großartig…

Zurück zur Hauptübersicht: Wir haben einen Eingabe-Listener erstellt, zwei Aufzählungszeichen initialisiert und ein erstellt BulletControl Klasse. Das einzige, was übrig bleibt, ist das Hinzufügen eines BulletControl zu jedem Geschoss beim Initialisieren:

bullet.addControl (neues BulletControl (Ziel, settings.getWidth (), settings.getHeight ()));

Jetzt macht das Spiel viel mehr Spaß!



Fazit

Es ist zwar nicht gerade eine Herausforderung, herumfliegen und einige Kugeln abzuschießen, aber Sie können es zumindest tun etwas. Aber verzweifeln Sie nicht - nach dem nächsten Tutorial wird es Ihnen schwer fallen, den wachsenden Feindenhorden zu entkommen!