Erstellen Sie einen Neon Vector Shooter für iOS Erste Schritte

In dieser Serie von Tutorials zeige ich Ihnen, wie Sie einen von Geometry Wars inspirierten Doppelstock-Shooter mit Neon-Grafik, verrückten Partikeleffekten und beeindruckender Musik für iOS mit C ++ und OpenGL ES 2.0 erstellen.

Anstatt sich auf ein vorhandenes Spiel-Framework oder eine Sprite-Bibliothek zu verlassen, versuchen wir, so nah wie möglich an der Hardware (oder "Bare Metal") zu programmieren. Da Geräte mit iOS im Vergleich zu einem Desktop-PC oder einer Spielekonsole auf einer kleineren Hardware laufen, können wir so viel wie möglich für unser Geld verdienen.

zusammenhängende Posts
Diese Tutorials basieren auf der ursprünglichen XNA-Serie von Michael Hoffman, die auf andere Plattformen übersetzt wurde:
  • Erstellen Sie einen Neon-Vektor-Shooter in XNA
  • Machen Sie einen Neon-Vektor-Shooter in jMonkeyEngine

Das Ziel dieser Tutorials ist es, die notwendigen Elemente durchzugehen, die es Ihnen ermöglichen, ein eigenes, hochwertiges mobiles Spiel für iOS zu erstellen, entweder von Grund auf oder basierend auf einem vorhandenen Desktop-Spiel. Ich empfehle Ihnen, den Code herunterzuladen und mit ihm zu spielen oder ihn sogar als Grundlage für Ihre eigenen Projekte zu verwenden.

In dieser Serie behandeln wir die folgenden Themen:

  1. Erste Schritte: Einführung in die Utility-Bibliothek, Einrichtung des grundlegenden Gameplays, Erstellung des Schiffs, des Sounds und der Musik des Spielers.
  2. Beenden Sie die Implementierung der Gameplay-Mechanik, indem Sie Gegner hinzufügen, die Kollisionserkennung durchführen und die Punktzahl und das Leben des Spielers verfolgen.
  3. Fügen Sie ein virtuelles Gamepad auf dem Bildschirm hinzu, sodass wir das Spiel mithilfe von Multi-Touch-Eingaben steuern können.
  4. Fügen Sie verrückte, übertriebene Partikeleffekte hinzu.
  5. Fügen Sie das Warping-Hintergrundraster hinzu.

Folgendes haben wir am Ende der Serie:


Warnung: Laut!

Und das haben wir am Ende dieses ersten Teils:


Warnung: Laut!

Die Musik und Soundeffekte, die Sie in diesen Videos hören können, wurden von RetroModular erstellt. Sie können in unserem Audio-Bereich nachlesen, wie er das getan hat.

Die Sprites stammen von Jacob Zinman-Jeanes, unserem ansässigen Designer Tuts +.

Die Schriftart, die wir verwenden, ist eine Bitmap-Schriftart (dh keine eigentliche "Schriftart", sondern eine Bilddatei), die ich für dieses Tutorial erstellt habe.

Das gesamte Bildmaterial befindet sich in den Quelldateien.

Lass uns anfangen.


Überblick

Bevor wir uns mit den Besonderheiten des Spiels beschäftigen, wollen wir über die Utility Library und den Application Bootstrap-Code sprechen, den ich zur Unterstützung der Entwicklung unseres Spiels bereitgestellt habe.

Die Utility-Bibliothek

Obwohl wir hauptsächlich C ++ und OpenGL verwenden, um unser Spiel zu codieren, benötigen wir einige zusätzliche Hilfsprogrammklassen. Dies sind alles Klassen, die ich geschrieben habe, um die Entwicklung in anderen Projekten zu unterstützen. Sie sind also zeitgeprüft und für neue Projekte wie dieses verwendbar.

  • package.h: Ein Komfort-Header, der alle relevanten Header aus der Utility-Bibliothek enthält. Wir werden es mit einschließen #include "Dienstprogramm / package.h" ohne etwas anderes hinzufügen zu müssen.

Muster

Wir werden einige bewährte und echte Programmiermuster einsetzen, die in C ++ und anderen Sprachen verwendet werden.

  • tSingletonImplementiert eine Singleton-Klasse mit einem "Meyers Singleton" -Muster. Es basiert auf Vorlagen und ist erweiterbar, sodass wir den gesamten Singleton-Code in eine einzige Klasse zusammenfassen können.
  • tOptional: Dies ist eine Funktion aus C ++ 14 (aufgerufen std :: optional) das ist in aktuellen Versionen von C ++ noch nicht ganz verfügbar (wir sind immer noch in C ++ 11). Es ist auch eine Funktion, die in XNA und C # verfügbar ist Nullwert.) Es erlaubt uns "optionale" Parameter für Methoden. Es wird in verwendet tSpriteBatch Klasse.

Vektor Math

Da wir kein vorhandenes Spiel-Framework verwenden, benötigen wir einige Klassen, um sich mit der Mathematik hinter den Kulissen zu beschäftigen.

  • tMathEin statischer Klassen-Taht bietet einige Methoden, die über das in C ++ verfügbare hinausgehen, z. B. das Konvertieren von Grad in Radiant oder das Runden von Zahlen auf Potenzen von zwei.
  • tVector: Ein grundlegender Satz von Vector-Klassen, der 2-Element-, 3-Element- und 4-Element-Varianten bereitstellt. Wir haben diese Struktur auch für Punkte und Farben eingegeben.
  • tMatrix: Zwei Matrixdefinitionen, eine 2x2-Variante (für Rotationsoperationen) und eine 4x4-Option (für die Projektionsmatrix, die erforderlich ist, um Dinge auf dem Bildschirm anzuzeigen),
  • tRect: Eine Rechteckklasse, die Position, Größe und eine Methode angibt, um zu bestimmen, ob Punkte innerhalb von Rechtecken liegen oder nicht.

OpenGL-Wrapper-Klassen

Obwohl OpenGL eine leistungsfähige API ist, ist sie C-basiert, und die Verwaltung von Objekten kann in der Praxis etwas schwierig sein. Wir haben also eine kleine Handvoll Klassen, um die OpenGL-Objekte für uns zu verwalten.

  • tOberfläche: Bietet eine Möglichkeit zum Erstellen einer Bitmap basierend auf einem Bild, das aus dem Anwendungspaket geladen wurde.
  • tTextur: Schließt die Schnittstelle in die Texturbefehle von OpenGL ein und lädt tOberflächen in Texturen.
  • tShader: Umschließt die Schnittstelle mit dem Shader-Compiler von OpenGL, wodurch das Kompilieren der Shader vereinfacht wird.
  • tProgramm: Schließt die Schnittstelle in die Shader-Programmschnittstelle von OpenGL, die im Wesentlichen die Kombination von zwei ist tShader Klassen.

Support-Klassen für Spiele

Diese Klassen sind die, die einem "Spiel-Framework" am nächsten kommen. Sie bieten einige High-Level-Konzepte, die für OpenGL nicht typisch sind, aber für Spieleentwicklungszwecke nützlich sind.

  • tViewport: Enthält den Status des Ansichtsfensters. Wir verwenden dies vor allem, um Änderungen an der Geräteorientierung vorzunehmen.
  • tAutosizeViewport: Eine Klasse, die Änderungen am Ansichtsfenster verwaltet. Es behandelt Änderungen der Geräteausrichtung direkt und skaliert das Ansichtsfenster so, dass es auf den Bildschirm des Geräts passt, sodass das Seitenverhältnis gleich bleibt. Dies bedeutet, dass die Dinge nicht gedehnt oder gestaucht werden.
  • tSpriteFont: Erlaubt uns, eine "Bitmap-Schriftart" aus dem Anwendungspaket zu laden und damit Text auf den Bildschirm zu schreiben.
  • tSpriteBatch: Inspiriert von XNAs SpriteBatch Klasse, ich habe diese Klasse geschrieben, um das Beste von dem, was unser Spiel benötigt, zusammenzufassen. Es erlaubt uns, Sprites beim Zeichnen so zu sortieren, dass wir die bestmögliche Geschwindigkeitssteigerung unserer vorhandenen Hardware erzielen. Wir verwenden es auch direkt zum Schreiben von Text auf dem Bildschirm.

Verschiedene Klassen

Eine minimale Anzahl von Klassen, um die Dinge abzurunden.

  • tTimer: Ein Systemzeitgeber, der hauptsächlich für Animationen verwendet wird.
  • tInputEvent: Grundlegende Klassendefinitionen zum Bereitstellen von Ausrichtungsänderungen (Neigen des Geräts), Berührungsereignisse und ein "virtuelles Tastaturereignis" zum diskreteren Emulieren eines Gamepads.
  • tSound: Eine Klasse zum Laden und Abspielen von Soundeffekten und Musik.

Anwendungs-Bootstrap

Wir brauchen auch einen "Boostrap" -Code, dh einen Code, der das Starten einer Anwendung oder das "Booten" wegfiltert.

Hier ist was drin ist Bootstrap:

  • AppDelegate: Diese Klasse behandelt den Anwendungsstart sowie das Anhalten und Wiederaufnehmen von Ereignissen, wenn der Benutzer die Home-Taste drückt.
  • ViewController: Diese Klasse behandelt Geräteorientierungsereignisse und erstellt unsere OpenGL-Ansicht
  • OpenGLView: Diese Klasse initialisiert OpenGL, teilt dem Gerät mit 60 Frames pro Sekunde eine Aktualisierung mit und behandelt Berührungsereignisse.

Überblick über das Spiel

In diesem Tutorial erstellen wir einen Twin-Stick-Shooter. Der Spieler steuert das Schiff mit den Multi-Touch-Bedienelementen auf dem Bildschirm.

Wir werden dazu eine Reihe von Klassen verwenden:

  • Entität: Die Basisklasse für Feinde, Kugeln und das Schiff des Spielers. Entitäten können sich bewegen und gezeichnet werden.
  • Kugel und SpielerSchiff.
  • EntityManager: Verfolgt alle Entitäten im Spiel und führt eine Kollisionserkennung durch.
  • Eingang: Hilft beim Verwalten von Eingaben über den Touchscreen.
  • Kunst: Lädt und enthält Verweise auf die für das Spiel benötigten Texturen.
  • Klingen: Lädt und enthält Verweise auf die Sounds und Musik.
  • MathUtil und Erweiterungen: Enthält einige hilfreiche statische Methoden und
    Erweiterungsmethoden.
  • GameRoot: Steuert die Hauptschleife des Spiels. Dies ist unsere Hauptklasse.

Der Code in diesem Lernprogramm soll einfach und verständlich sein. Es werden nicht alle Funktionen für alle möglichen Anforderungen entwickelt. es wird nur das tun, was es tun muss. Wenn Sie es einfach halten, wird es Ihnen leichter fallen, die Konzepte zu verstehen und sie dann zu einem eigenen, einzigartigen Spiel zu modifizieren und zu erweitern.


Entitäten und das Schiff des Spielers

Öffnen Sie das vorhandene Xcode-Projekt. GameRoot ist die Hauptklasse unserer Anwendung.

Wir beginnen mit der Erstellung einer Basisklasse für unsere Spieleinheiten. Schauen Sie sich das an Entity-Klasse:

 class Entity public: enum Kind kDontCare = 0, kBullet, kEnemy, kBlackHole,; protected: tTexture * mImage; tColor4f mColor; tPoint2f mPosition; tVector2f mVelocity; Float-Orientierung; Float mRadius; bool mIsExpired; Art mKind; public: Entity (); virtuelle ~ Entität (); tDimension2f getSize () const; virtuelles ungültiges Update () = 0; virtuelles Leerzeichen (tSpriteBatch * spriteBatch); tPoint2f getPosition () const; tVector2f getVelocity () const; void setVelocity (const tVector2f & nv); float getRadius () const; bool isExpired () const; Kind getKind () const; void setExpired (); ;

Alle unsere Entitäten (Feinde, Kugeln und das Schiff des Spielers) haben einige grundlegende Eigenschaften wie ein Bild und eine Position. mIsExpired wird verwendet, um anzuzeigen, dass die Entität zerstört wurde und aus allen Listen entfernt werden sollte, die einen Verweis darauf enthalten.

Als nächstes erstellen wir eine EntityManager um unsere Entitäten zu verfolgen und sie zu aktualisieren und zu zeichnen:

 Klasse EntityManager: public tSingleton protected: std :: list mEntities; std :: list mAddedEntities; std :: list mBullets; bool mIsUpdating; protected: EntityManager (); public: int getCount () const; void add (Entität * Entität); void addEntity (Entität * Entität); void update (); void draw (tSpriteBatch * spriteBatch); bool isColliding (Entity * a, Entity * b); Freundenklasse tSingleton; ; void EntityManager :: add (Entity * entity) if (! mIsUpdating) addEntity (entity);  else mAddedEntities.push_back (entity);  void EntityManager :: update () mIsUpdating = true; für (std :: list:: iterator iter = mEntities.begin (); iter! = mEntities.end (); iter ++) (* iter) -> update (); if ((* iter) -> isExpired ()) * iter = NULL;  mIsUpdating = false; für (std :: list:: iterator iter = mAddedEntities.begin (); iter! = mAddedEntities.end (); iter ++) addEntity (* iter);  mAddedEntities.clear (); mEntities.remove (NULL); für (std :: list:: iterator iter = mBullets.begin (); iter! = mBullets.end (); iter ++) if ((* iter) -> isExpired ()) delete * iter; * iter = NULL;  mBullets.remove (NULL);  void EntityManager :: draw (tSpriteBatch * spriteBatch) für (std :: list):: iterator iter = mEntities.begin (); iter! = mEntities.end (); iter ++) (* iter) -> draw (spriteBatch); 

Denken Sie daran, wenn Sie eine Liste ändern, während Sie darüber iterieren, wird eine Laufzeitausnahme angezeigt. Der obige Code sorgt dafür, dass alle beim Aktualisieren hinzugefügten Entitäten in einer separaten Liste in eine Warteschlange gestellt werden und nach dem Aktualisieren der vorhandenen Entitäten hinzugefügt werden.

Sie sichtbar machen

Wir müssen einige Texturen laden, wenn wir etwas zeichnen möchten. Daher erstellen wir eine statische Klasse, um Verweise auf alle unsere Texturen zu enthalten:

 Klasse Kunst: public tSingleton protected: tTexture * mPlayer; tTexture * mSeeker; tTexture * mWanderer; tTexture * mBullet; tTexture * mPointer; geschützt: Kunst (); public: tTexture * getPlayer () const; tTexture * getSeeker () const; tTexture * getWanderer () const; tTexture * getBullet () const; tTexture * getPointer () const; Freundenklasse tSingleton; ; Art :: Art () mPlayer = new tTexture (tSurface ("player.png")); mSeeker = new tTexture (tSurface ("seeker.png")); mWanderer = new tTexture (tSurface ("wanderer.png")); mBullet = new tTexture (tSurface ("bullet.png")); mPointer = new tTexture (tSurface ("pointer.png")); 

Wir laden die Kunst, indem wir anrufen Kunst :: getInstance () im GameRoot :: onInitView (). Dies bewirkt das Kunst singleton, um konstruiert zu werden und den Konstruktor aufzurufen, Kunst :: Kunst ().

Außerdem müssen einige Klassen die Bildschirmabmessungen kennen, daher haben wir die folgenden Mitglieder GameRoot:

 tDimension2f mViewportSize; tSpriteBatch * mSpriteBatch; tAutosizeViewport * mViewport;

Und in der GameRoot Konstruktor legen wir die Größe fest:

 GameRoot :: GameRoot (): mViewportSize (800, 600), mSpriteBatch (NULL) 

Die Auflösung von 800x600px ist diejenige, die der ursprüngliche XNA-basierte Shape Blaster verwendet hat. Wir können jede Auflösung verwenden, die wir wünschen (wie etwa eine bestimmte Auflösung eines iPhones oder iPads), aber wir bleiben bei der ursprünglichen Auflösung, nur um sicherzustellen, dass unser Spiel dem Erscheinungsbild des Originals entspricht.

Jetzt gehen wir über die SpielerSchiff Klasse:

 class PlayerShip: public Entity, public tSingleton protected: static const int kCooldownFrames; int mCooldowmRemaining; int mFramesUntilRespawn; protected: PlayerShip (); public: void update (); void draw (tSpriteBatch * spriteBatch); bool getIsDead (); void kill (); Freundenklasse tSingleton; ; PlayerShip :: PlayerShip (): mCooldowmRemaining (0), mFramesUntilRespawn (0) mImage = Art :: getInstance () -> getPlayer (); mPosition = tPoint2f (GameRoot :: getInstance () -> getViewportSize (). x / 2, GameRoot :: getInstance () -> getViewportSize (). y / 2); mRadius = 10; 

Wir machten SpielerSchiff Ein Singleton, stellen Sie das Bild ein und platzieren Sie es in der Mitte des Bildschirms.

Schließlich fügen wir das Spielerschiff zum hinzu EntityManager. Der Code in GameRoot :: onInitView sieht aus wie das:

 // In GameRoot :: onInitView EntityManager :: getInstance () -> add (PlayerShip :: getInstance ());… glClearColor (0,0,0,1); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glHint (GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); glDisable (GL_DEPTH_TEST); glDisable (GL_CULL_FACE);

Wir zeichnen die Sprites mit additive Mischung, Das ist ein Teil dessen, was ihnen ihren "Neon" -Look geben wird. Wir möchten auch kein Weichzeichnen oder Mischen, also verwenden wir es GL_NEAREST für unsere Filter. Wir brauchen keine Tiefenuntersuchungen oder Rückkopplungen, da dies ohnehin unnötigen Overhead bedeutet. Daher schalten wir sie aus.

Der Code in GameRoot :: onRedrawView sieht aus wie das:

 // In GameRoot :: onRedrawView EntityManager :: getInstance () -> update (); EntityManager :: getInstance () -> draw (mSpriteBatch); mSpriteBatch-> draw (0, Art :: getInstance () -> getPointer (), Eingabe :: getInstance () -> getMousePosition (), tOptional()); mViewport-> run (); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mSpriteBatch-> end (); glFlush ();

Wenn Sie das Spiel zu diesem Zeitpunkt ausführen, sollten Sie Ihr Schiff in der Mitte des Bildschirms sehen. Es reagiert jedoch nicht auf Eingaben. Lassen Sie uns als nächstes etwas zum Spiel hinzufügen.


Eingang

Für die Bewegung verwenden wir eine Multi-Touch-Oberfläche. Bevor wir mit den Bildschirm-Gamepads auf Hochtouren gehen können, erhalten wir lediglich eine grundlegende Touch-Oberfläche.

Beim ursprünglichen Shape Blaster für Windows konnte die Player-Bewegung mit den WASD-Tasten auf der Tastatur ausgeführt werden. Zum Zielen können sie die Pfeiltasten oder die Maus verwenden. Dies soll die Zweiknüppel-Steuerung von Geometry Wars nachahmen: Ein analoger Steuerknüppel zum Bewegen, einer zum Zielen.

Da Shape Blaster bereits das Konzept der Tastatur- und Mausbewegung verwendet, wäre der einfachste Weg, Eingaben hinzuzufügen, die Emulation von Tastatur- und Mausbefehlen durch Berührung. Wir beginnen mit der Mausbewegung, da sowohl die Berührung als auch die Maus eine ähnliche Komponente gemeinsam haben: einen Punkt, der X- und Y-Koordinaten enthält.

Wir erstellen eine statische Klasse, um die verschiedenen Eingabegeräte im Auge zu behalten und um zwischen den verschiedenen Zielarten zu wechseln:

 Klasse Eingabe: public tSingleton protected: tPoint2fMMouseState; tPoint2f mLastMouseState; tPoint2f mFreshMouseState; std :: vector mKeyboardState; std :: vector mLastKeyboardState; std :: vector mFreshKeyboardState; bool mIsAimingWithMouse; uint8_t mLeftEngaged; uint8_t mRightEngaged; public: enum KeyType kUp = 0, kLeft, kDown, kRight, kW, kA, kS, kD; protected: tVector2f GetMouseAimDirection () const; geschützt: Eingabe (); public: tPoint2f getMousePosition () const; void update (); // prüft, ob gerade eine Taste gedrückt wurde bool wasKeyPressed (KeyType) const; tVector2f getMovementDirection () const; tVector2f getAimDirection () const; void onKeyboard (const tKeyboardEvent & msg); void onTouch (const tTouchEvent & msg); Freundenklasse tSingleton; ; void Input :: update () mLastKeyboardState = mKeyboardState; mLastMouseState = mMouseState; mKeyboardState = mFreshKeyboardState; mMouseState = mFreshMouseState; if (mKeyboardState [kLeft] || mKeyboardState [kRight] || mKeyboardState [kUp] || mKeyboardState [kDown]) mIsAimingWithMouse = false;  else if (mMouseState! = mLastMouseState) mIsAimingWithMouse = true; 

Wir nennen Eingabe :: Update () am Anfang von GameRoot :: onRedrawView () damit die Eingabe-Klasse funktioniert.

Wie bereits erwähnt, verwenden wir die Tastatur Zustand später in der Serie, um die Bewegung zu berücksichtigen.

Schießen

Lassen Sie uns jetzt das Schiff schießen lassen.

Erstens brauchen wir eine Klasse für Kugeln.

 Klasse Bullet: public Entity public: Bullet (const tPoint2f & position, const tVector2f & velocity); void update (); ; Bullet :: Bullet (const tPoint2f & position, const tVector2f & Velocity) mImage = Art :: getInstance () -> getBullet (); mPosition = Position; m Geschwindigkeit = Geschwindigkeit; mOrientation = atan2f (mVelocity.y, mVelocity.x); mRadius = 8; mKind = kBullet;  void Bullet :: update () if (mVelocity.lengthSquared ()> 0) mOrientation = atan2f (mVelocity.y, mVelocity.x);  mPosition + = mVelocity; if (! tRectf (0, 0, GameRoot :: getInstance () -> getViewportSize ()) enthält. (tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y))) mIsExpired = true; 

Wir wollen eine kurze Abklingzeit zwischen den Kugeln, also haben wir eine Konstante dafür:

 const int PlayerShip :: kCooldownFrames = 6;

Außerdem fügen wir den folgenden Code hinzu PlayerShip :: Update ():

 tVector2f aim = Eingabe :: getInstance () -> getAimDirection (); if (aim.lengthSquared ()> 0 && mCooldowmRemaining <= 0)  mCooldowmRemaining = kCooldownFrames; float aimAngle = atan2f(aim.y, aim.x); float cosA = cosf(aimAngle); float sinA = sinf(aimAngle); tMatrix2x2f aimMat(tVector2f(cosA, sinA), tVector2f(-sinA, cosA)); float randomSpread = tMath::random() * 0.08f + tMath::random() * 0.08f - 0.08f; tVector2f vel = 11.0f * (tVector2f(cosA, sinA) + tVector2f(randomSpread, randomSpread)); tVector2f offset = aimMat * tVector2f(35, -8); EntityManager::getInstance()->add (neues Aufzählungszeichen (mPosition + offset, vel)); offset = aimMat * tVector2f (35, 8); EntityManager :: getInstance () -> add (new Bullet (mPosition + offset, vel)); tSound * curShot = Sound :: getInstance () -> getShot (); if (! curShot-> isPlaying ()) curShot-> play (0, 1);  if (mCooldowmRemaining> 0) mCooldowmRemaining--; 

Dieser Code erstellt zwei Aufzählungszeichen, die sich parallel zueinander bewegen. Es fügt der Richtung ein wenig Zufall hinzu, wodurch sich die Schüsse ein wenig wie ein Maschinengewehr ausbreiten. Wir addieren zwei Zufallszahlen zusammen, da dies dazu führt, dass ihre Summe eher zentriert ist (um null herum) und weniger wahrscheinlich Kugeln in die Ferne schicken. Wir verwenden eine zweidimensionale Matrix, um die Ausgangsposition der Geschosse in Fahrtrichtung zu drehen.

Wir haben auch zwei neue Hilfsmethoden verwendet:

  • Erweiterungen :: NextFloat (): Gibt ein zufälliges Float zwischen einem minimalen und einem maximalen Wert zurück.
  • MathUtil :: FromPolar (): Erzeugt eine tVector2f aus einem Winkel und einer Größe.

Mal sehen, wie sie aussehen:

 // In Erweiterungen float Extensions :: nextFloat (float minValue, float maxValue) return (float) tMath :: random () * (maxValue - minValue) + minValue;  // In MathUtil tVector2f MathUtil :: fromPolar (Float-Winkel, Float-Größe) Rückgabewert * tVector2f ((Float) cosf (Winkel), (Float) Sinf (Winkel)); 

Benutzerdefinierter Cursor

Es gibt noch etwas, was wir jetzt tun sollten, wenn wir den Anfang haben Eingang Klasse: Lassen Sie uns einen benutzerdefinierten Mauszeiger zeichnen, damit Sie leichter erkennen können, wohin das Schiff zielt. Im GameRoot.Draw, einfach Kunst zeichnen mPointer an der "Maus" Position.

 mSpriteBatch-> draw (0, Art :: getInstance () -> getPointer (), Eingabe :: getInstance () -> getMousePosition (), tOptional());

Fazit

Wenn Sie das Spiel jetzt testen, können Sie eine beliebige Stelle auf dem Bildschirm berühren, um den ununterbrochenen Strom von Kugeln zu zielen, was ein guter Anfang ist.


Warnung: Laut!

Im nächsten Teil werden wir das anfängliche Gameplay durch Hinzufügen von Gegnern und einer Punktzahl beenden.