Erstellen Sie einen Neon-Vektor-Shooter für iOS Das Warping-Raster

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. In diesem abschließenden Teil fügen wir das Hintergrundraster hinzu, das aufgrund der Aktion im Spiel verzerrt wird.

Überblick

In der bisherigen Serie haben wir das Gameplay, das virtuelle Gamepad und Partikeleffekte erstellt. In diesem letzten Teil erstellen wir ein dynamisches, verzerrendes Hintergrundraster.


Warnung: Laut!

Wie im vorherigen Teil erwähnt, werden Sie einen dramatischen Rückgang der Bildfrequenz feststellen, wenn Sie den Code weiterhin im Debug-Modus ausführen. In diesem Lernprogramm erfahren Sie, wie Sie in den Freigabemodus wechseln, um die vollständige Compiler-Optimierung (und einen schnelleren Build) zu ermöglichen..

Das Warping-Gitter

Einer der coolsten Effekte in Geometry Wars ist das verzerrte Hintergrundraster. Wir werden prüfen, wie Sie einen ähnlichen Effekt in Shape Blaster erzeugen. Das Gitter reagiert auf Kugeln, Schwarze Löcher und den erneuten Start des Spielers. Es ist nicht schwer zu machen und es sieht toll aus.

Wir machen das Raster mit einer Federsimulation. An jedem Schnittpunkt des Gitters legen wir ein kleines Gewicht auf und befestigen auf jeder Seite eine Feder. Diese Federn werden nur ziehen und niemals drücken, ähnlich wie ein Gummiband. Um das Gitter in Position zu halten, werden die Massen am Rand des Gitters verankert. Unten sehen Sie ein Diagramm des Layouts.


Wir erstellen eine Klasse namens Gitter um diesen Effekt zu erzeugen. Bevor wir jedoch am Gitter selbst arbeiten, müssen wir zwei Hilfsklassen erstellen: Frühling und PointMass.

Die PointMass-Klasse

Das PointMass Klasse stellt die Massen dar, an denen wir die Federn anbringen. Federn sind niemals direkt mit anderen Federn verbunden. Stattdessen üben sie eine Kraft auf die Massen aus, die sie verbinden, was wiederum andere Federn strecken kann.

 Klasse PointMass protected: tVector3f mAcceleration; Schwimmer mDamping; public: tVector3f mPosition; tVector3f mVelocity; float mInverseMass; public: PointMass (); PointMass (const tVector3f & position, float invMass); void applyForce (const tVector3f & force); HohlraumerhöhungDamping (Float-Faktor); void update (); ; PointMass :: PointMass (): mAcceleration (0,0,0), mDamping (0,98f), mPosition (0), mVelocity (0,0,0), mInverseMass (0)  PointMass :: PointMass (const tVector3f & position , float invMass): mAcceleration (0,0,0), mDamping (0,98f), mPosition (Position), mVelocity (0,0,0), mInverseMass (invMass)  void PointMass :: applyForce (const tVector3f & force) mAcceleration + = force * mInverseMass;  void PointMass ::plusDamping (Float-Faktor) mDamping * = Faktor;  void PointMass :: update () mVelocity + = mAcceleration; mPosition + = mVelocity; mAcceleration = tVector3f (0,0,0); if (mVelocity.lengthSquared () < 0.001f * 0.001f)  mVelocity = tVector3f(0,0,0);  mVelocity *= mDamping; mDamping = 0.98f; 

Es gibt ein paar interessante Punkte zu dieser Klasse. Beachten Sie zunächst, dass es die speichert invers der Masse, 1 / Masse. Dies ist in Physik-Simulationen oft eine gute Idee, da Physikgleichungen dazu neigen, häufiger das Inverse der Masse zu verwenden, und weil es uns eine einfache Möglichkeit gibt, unendlich schwere, unbewegliche Objekte darzustellen, indem die inverse Masse auf Null gesetzt wird.

Zweitens enthält die Klasse auch eine Dämpfung Variable. Dies wird ungefähr als Reibungs- oder Luftwiderstand verwendet. es verlangsamt allmählich die Masse. Dies führt dazu, dass das Gitter schließlich zum Stillstand kommt und erhöht auch die Stabilität der Federsimulation.

Das PointMass :: update () Diese Methode erledigt die Bewegung der Punktmasse für jeden Frame. Es beginnt mit der symplektischen Euler-Integration, was bedeutet, dass wir die Geschwindigkeit mit der Beschleunigung und dann mit der aktualisierten Geschwindigkeit der Position hinzufügen. Dies unterscheidet sich von der Euler-Standardintegration, bei der wir die Geschwindigkeit nach dem Aktualisieren der Position aktualisieren würden.

Spitze: Symplectic Euler ist für Frühlingssimulationen besser geeignet, da Energie gespart wird. Wenn Sie die reguläre Euler-Integration verwenden und Federn ohne Dämpfung erzeugen, neigen sie dazu, sich mit jedem Sprung weiter zu dehnen, wenn sie Energie gewinnen, was Ihre Simulation letztendlich bricht.

Nach der Aktualisierung der Geschwindigkeit und der Position überprüfen wir, ob die Geschwindigkeit sehr klein ist, und setzen sie auf Null. Dies kann aufgrund der Art der denormalisierten Fließkommazahlen für die Leistung wichtig sein.

(Wenn Gleitkommazahlen sehr klein werden, verwenden sie eine spezielle Darstellung, die als a bezeichnet wird denormalisierte Nummer. Dies hat den Vorteil, dass Floats kleinere Zahlen darstellen können, dies hat jedoch einen Preis. Die meisten Chipsätze können ihre Standard-Rechenoperationen nicht für denormalisierte Zahlen verwenden und müssen sie stattdessen mit einer Reihe von Schritten emulieren. Dies kann zehn bis hundert Mal langsamer sein als die Ausführung von Operationen mit normalisierten Gleitkommazahlen. Da wir mit jedem Dämpfungsfaktor unsere Geschwindigkeit mit jedem Dämpfungsfaktor multiplizieren, wird sie letztendlich sehr klein. Diese kleinen Geschwindigkeiten sind uns eigentlich egal, also setzen wir sie einfach auf Null.)

Das PointMass ::plusDamping () Diese Methode wird verwendet, um die Dämpfung vorübergehend zu erhöhen. Wir werden dies später für bestimmte Effekte verwenden.

Die Frühlingsklasse

Eine Feder verbindet zwei Punktmassen und bringt, wenn sie über ihre natürliche Länge gedehnt wird, eine Kraft auf, die die Massen zusammenzieht. Federn folgen einer modifizierten Version von Hookes Gesetz mit Dämpfung:

\ [f = −kx - bv \]

  • \ (f \) ist die von der Feder erzeugte Kraft.
  • \ (k \) ist die Federkonstante oder die Steifheit der Feder.
  • \ (x \) ist der Abstand, um den die Feder über ihre natürliche Länge hinaus gedehnt wird.
  • \ (b \) ist der Dämpfungsfaktor.
  • \ (v \) ist die Geschwindigkeit.

Der Code für die Frühling Klasse ist wie folgt:

 Klasse Spring public: PointMass * mEnd1; PointMass * mEnd2; Float mTargetLength; Float mStiffness; Schwimmer mDamping; public: Spring (PointMass * end1, PointMass * end2, Schwimmersteifigkeit, Schwimmerdämpfung); void update (); ; Spring :: Spring (PointMass * end1, PointMass * end2, Schwimmkörpersteifigkeit, Schwimmdämpfung): mEnd1 (end1), mEnd2 (end2), mTargetLength (mEnd1-> mPosition.distance (mEnd2-> mPosition) * 0,95f), mStiffness (Steifigkeit), mDämpfung (Dämpfung)  void Spring :: update () tVector3f x = mEnd1-> mPosition - mEnd2-> mPosition; float length = x.length (); if (Länge> mZielLänge) x = (x / Länge) * (Länge - mZielLänge); tVector3f dv = mEnd2 -> mVelocity - mEnd1 -> mVelocity; tVector3f force = mStiffness * x - dv * mDamping; mEnd1-> applyForce (-force); mEnd2-> applyForce (force); 

Wenn wir eine Feder erstellen, legen wir die natürliche Länge der Feder so fest, dass sie etwas geringer ist als der Abstand zwischen den beiden Endpunkten. Dies hält das Gitter auch im Ruhezustand straff und verbessert das Erscheinungsbild etwas.

Das Frühling :: Update () Die Methode prüft zunächst, ob die Feder über ihre natürliche Länge hinaus gedehnt ist. Wenn es nicht gedehnt wird, passiert nichts. Wenn ja, verwenden wir das modifizierte Hookesche Gesetz, um die Kraft der Feder zu ermitteln und auf die beiden verbundenen Massen anzuwenden.

Das Gitter erstellen

Jetzt, da wir die notwendigen verschachtelten Klassen haben, können wir das Raster erstellen. Wir beginnen mit dem Erstellen PointMass Objekte an jeder Kreuzung im Raster. Wir erstellen auch einen unbeweglichen Anker PointMass Objekte, um das Gitter an Ort und Stelle zu halten. Dann verbinden wir die Massen mit Federn.

 std :: vector mSprings; PointMass * mPoints; Grid :: Grid (const tRectf & rect, const tVector2f und Abstand) mScreenSize = tVector2f (GameRoot :: getInstance () -> getViewportSize (). Width, GameRoot :: getInstance () -> getViewportSize (). Height); int numColumns = (int) ((float) rect.Size.width / widthing.x) + 1; int numRows = (int) ((float) rect.Größe.Höhe / Abstand.y) + 1; mPoints = neue PointMass [numColumns * numRows]; mCols = numColumns; mRows = numRows; PointMass * fixedPoints = neue PointMass [numColumns * numRows]; int Spalte = 0, Zeile = 0; für (float y = rect.location.y; y <= rect.location.y + rect.size.height; y += spacing.y)  for (float x = rect.location.x; x <= rect.location.x + rect.size.width; x += spacing.x)  SetPointMass(mPoints, column, row, PointMass(tVector3f(x, y, 0), 1)); SetPointMass(fixedPoints, column, row, PointMass(tVector3f(x, y, 0), 0)); column++;  row++; column = 0;  // link the point masses with springs for (int y = 0; y < numRows; y++)  for (int x = 0; x < numColumns; x++)  if (x == 0 || y == 0 || x == numColumns - 1 || y == numRows - 1)  mSprings.push_back(Spring(GetPointMass(fixedPoints, x, y), GetPointMass(mPoints, x, y), 0.1f, 0.1f));  else if (x % 3 == 0 && y % 3 == 0)  mSprings.push_back( Spring(GetPointMass(fixedPoints, x, y), GetPointMass(mPoints, x, y), 0.002f, 0.02f));  if (x > 0) mSprings.push_back (Spring (GetPointMass (mPoints, x - 1, y), GetPointMass (mPoints, x, y), 0,28f, 0,06f));  if (y> 0) mSprings.push_back (Spring (GetPointMass (mPoints, x, y - 1), GetPointMass (mPoints, x, y), 0.28f, 0.06f)); 

Der Erste zum Schleife erzeugt an jedem Schnittpunkt des Gitters sowohl regelmäßige Massen als auch unbewegliche Massen. Wir werden nicht wirklich alle unbeweglichen Massen verwenden, und die ungenutzten Massen werden einfach einige Zeit nach dem Ende des Konstrukteurs gesammelt. Wir könnten optimieren, indem Sie das Erstellen unnötiger Objekte vermeiden. Da das Raster jedoch normalerweise nur einmal erstellt wird, ist es nicht viel Unterschied.

Neben der Verwendung von Ankerpunktmassen am Rand des Gitters werden auch einige Ankermassen innerhalb des Gitters verwendet. Diese werden verwendet, um das Gitter nach dem Verformen sehr sanft in seine ursprüngliche Position zu ziehen.

Da sich die Ankerpunkte niemals bewegen, müssen sie nicht für jeden Rahmen aktualisiert werden. Wir können sie einfach an die Federn anschließen und sie vergessen. Daher haben wir keine Member-Variable in der Gitter Klasse für diese Massen.

Es gibt eine Reihe von Werten, die Sie bei der Erstellung des Gitters anpassen können. Die wichtigsten sind die Steifigkeit und Dämpfung der Federn. (Die Steifigkeit und Dämpfung der Randanker und Innenanker wird unabhängig von den Hauptfedern eingestellt.) Bei höheren Steifigkeitswerten schwingen die Federn schneller und bei höheren Dämpfungswerten bremsen die Federn schneller ab.

Das Gitter manipulieren

Damit das Raster verschoben werden kann, müssen wir es für jeden Frame aktualisieren. Dies ist sehr einfach, da wir bereits die ganze harte Arbeit in erledigt haben PointMass und Frühling Klassen:

 void Grid :: update () für (size_t i = 0; i < mSprings.size(); i++)  mSprings[i].update();  for(int i = 0; i < mCols * mRows; i++)  mPoints[i].update();  

Jetzt werden wir einige Methoden hinzufügen, die das Raster bearbeiten. Sie können Methoden für jede denkbare Manipulation hinzufügen. Wir werden hier drei Arten von Manipulationen implementieren: Drücken Sie einen Teil des Gitters in eine bestimmte Richtung, drücken Sie das Gitter von einem Punkt aus nach außen und ziehen Sie das Gitter in Richtung eines bestimmten Punktes. Alle drei werden das Gitter innerhalb eines bestimmten Radius von einem Zielpunkt aus beeinflussen. Nachfolgend einige Bilder dieser Manipulationen in Aktion:


Kugeln, die das Gitter nach außen abweisen.


Das Gitter nach innen saugen.

Welle, die durch Verschieben des Gitters entlang der Z-Achse erzeugt wird.
 void Grid :: applyDirectedForce (const tVector3f & force, const tVector3f & position, Floatradius) für (int i = 0; i < mCols * mRows; i++)  if (position.distanceSquared(mPoints[i].mPosition) < radius * radius)  mPoints[i].applyForce(10.0f * force / (10 + position.distance(mPoints[i].mPosition)));    void Grid::applyImplosiveForce(float force, const tVector3f& position, float radius)  for (int i = 0; i < mCols * mRows; i++)  float dist2 = position.distanceSquared(mPoints[i].mPosition); if (dist2 < radius * radius)  mPoints[i].applyForce(10.0f * force * (position - mPoints[i].mPosition) / (100 + dist2)); mPoints[i].increaseDamping(0.6f);    void Grid::applyExplosiveForce(float force, const tVector3f& position, float radius)  for (int i = 0; i < mCols * mRows; i++)  float dist2 = position.distanceSquared(mPoints[i].mPosition); if (dist2 < radius * radius)  mPoints[i].applyForce(100 * force * (mPoints[i].mPosition - position) / (10000 + dist2)); mPoints[i].increaseDamping(0.6f);   

Wir werden alle drei dieser Methoden in Shape Blaster für unterschiedliche Effekte verwenden.

Rendern des Gitters

Wir zeichnen das Raster, indem wir Liniensegmente zwischen jedem benachbarten Punktpaar zeichnen. Zuerst fügen wir eine Erweiterungsmethode hinzu, wobei a tSpriteBatch Zeiger als Parameter, der das Zeichnen von Liniensegmenten ermöglicht, indem eine Textur eines einzelnen Pixels in eine Linie gezogen wird.

Öffne das Kunst klassifizieren und deklarieren Sie eine Textur für das Pixel:

 Klasse Kunst: public tSingleton; protected: tTexture * mPixel;… public: tTexture * getPixel () const;…;

Sie können die Pixel-Textur auf dieselbe Weise einstellen, wie wir die anderen Bilder einrichten, also fügen wir sie hinzu pixel.png (ein 1x1px-Bild mit dem einzigen Pixel auf weiß gesetzt), um das Projekt zu laden und in das zu laden tTextur:

 mPixel = neue tTexture (tSurface ("pixel.png"));

Fügen wir nun die folgende Methode hinzu Erweiterungen Klasse:

 void Extensions :: drawLine (tSpriteBatch * spriteBatch, const tVector2f & start, const tVector2f & end, const tColor4f & color, Fließdicke) tVector2f delta = end - start; spriteBatch-> draw (0, Art :: getInstance () -> getPixel (), tPoint2f ((int32_t) start.x, (int32_t) start.y), tOptional(), color, toAngle (delta), tPoint2f (0, 0), tVector2f (delta.length (), Dicke)); 

Bei dieser Methode wird die Pixelstruktur gedehnt, gedreht und eingefärbt, um die gewünschte Linie zu erzeugen.

Als Nächstes benötigen wir eine Methode, um die 3D-Rasterpunkte auf unseren 2D-Bildschirm zu projizieren. Normalerweise kann dies mit Matrizen erfolgen, aber hier werden die Koordinaten stattdessen manuell transformiert.

Fügen Sie Folgendes dem hinzu Gitter Klasse:

 tVector2f Grid :: toVec2 (const tVector3f & v) Floatfaktor = (v.z + 2000.0f) * 0,0005f; return (tVector2f (v.x, v.y) - mScreenSize * 0,5f) * Faktor + mScreenSize * 0,5f; 

Diese Transformation gibt dem Raster eine perspektivische Ansicht, in der weit entfernte Punkte näher auf dem Bildschirm erscheinen. Jetzt können wir das Gitter zeichnen, indem wir durch die Zeilen und Spalten iterieren und Linien zwischen ihnen zeichnen:

 void Grid :: draw (tSpriteBatch * spriteBatch) int width = mCols; int height = mRows; tColor4f-Farbe (0,12f, 0,12f, 0,55f, 0,33f); für (int y = 1; y < height; y++)  for (int x = 1; x < width; x++)  tVector2f left, up; tVector2f p = toVec2(GetPointMass(mPoints, x, y)->mPosition); if (x> 1) left = toVec2 (GetPointMass (mPoints, x - 1, y) -> mPosition); Auftriebsdicke = (y% 3 == 1)? 3,0f: 1,0f; Erweiterungen :: drawLine (spriteBatch, links, p, Farbe, Dicke);  if (y> 1) up = toVec2 (GetPointMass (mPoints, x, y - 1) -> mPosition); Auftriebsdicke = (x% 3 == 1)? 3,0f: 1,0f; Erweiterungen :: drawLine (spriteBatch, up, p, color, thickness); 

Im obigen Code, p ist unser aktueller Punkt am Netz, links ist der Punkt direkt links davon und oben ist der Punkt direkt darüber. Wir zeichnen jede dritte Linie, die horizontal und vertikal dicker ist, um visuelle Effekte zu erzielen.

Interpolation

Wir können das Netz optimieren, indem wir die visuelle Qualität für eine bestimmte Anzahl von Federn verbessern, ohne die Leistungskosten erheblich zu erhöhen. Wir werden zwei solche Optimierungen vornehmen.

Wir werden das Gitter dichter machen, indem Sie Liniensegmente in die vorhandenen Gitterzellen einfügen. Dazu zeichnen wir Linien vom Mittelpunkt einer Seite der Zelle bis zum Mittelpunkt der gegenüberliegenden Seite. Das Bild unten zeigt die neuen interpolierten Zeilen rot.

Das Zeichnen der interpolierten Linien ist unkompliziert. Wenn Sie zwei Punkte haben, ein und b, ihr Mittelpunkt ist (a + b) / 2. Um die interpolierten Linien zu zeichnen, fügen wir den folgenden Code in die Zeile ein zum Schleifen unserer Raster :: draw () Methode:

 if (x> 1 && y> 1) tVector2f upLeft = toVec2 (GetPointMass (mPoints, x - 1, y - 1) -> mPosition); Erweiterungen :: drawLine (spriteBatch, 0.5f * (upLeft + up), 0.5f * (links + p), Farbe, 1.0f); // vertikale Linie Erweiterungen :: drawLine (spriteBatch, 0.5f * (upLeft + left), 0.5f * (up + p), color, 1.0f); // horizontale Linie 

Die zweite Verbesserung besteht in der Interpolation unserer geraden Liniensegmente, um sie zu glatteren Kurven zu machen. In der ursprünglichen XNA-Version dieses Spiels stützte sich der Code auf XNAs Vector2.CatmullRom () Methode, die die Catmull-Rom-Interpolation durchführt. Sie übergeben die Methode vier aufeinanderfolgende Punkte auf einer gekrümmten Linie, und es werden Punkte entlang einer glatten Kurve zwischen dem zweiten und dritten Punkt zurückgegeben, die Sie angegeben haben.

Da dieser Algorithmus weder in der C- noch in der C ++ - Standardbibliothek vorhanden ist, müssen wir ihn selbst implementieren. Glücklicherweise steht eine Referenzimplementierung zur Verfügung. Ich habe ein bereitgestellt MathUtil :: catmullRom () Methode basierend auf dieser Referenzimplementierung:

 float MathUtil :: catmullRom (const float value1, const float value2, const float value3, const float value4, float betrag) // Verwendung der Formel aus http://www.mvps.org/directx/articles/catmull/ float betragSquared = Betrag * Betrag; float betragCubed = betragquant * betrag; return (float) (0.5f * (2.0f * value2 + (value3 - value1) * betrag + (2.0f * value1 - 5.0f * value2 + 4.0f * value3 - value4) * betragquadrat + (3.0f * value2 - value1) - 3,0f * Wert3 + Wert4) * BetragCubed));  tVector2f MathUtil :: catmullRom (const tVector2f & value1, const tVector2f & value2, const tVector2f & value3, const tVector2f & value4, float betrag) , Betrag), MathUtil :: catmullRom (Wert1.y, Wert2.y, Wert3.y, Wert4.y, Betrag)); 

Das fünfte Argument zu MathUtil :: catmullRom () ist ein Gewichtungsfaktor, der bestimmt, welchen Punkt der interpolierten Kurve er zurückgibt. Ein Gewichtungsfaktor von 0 oder 1 gibt jeweils den von Ihnen angegebenen zweiten oder dritten Punkt und einen Gewichtungsfaktor von zurück 0,5 wird den Punkt auf der interpolierten Kurve auf halbem Weg zwischen den beiden Punkten zurückgeben. Durch schrittweises Verschieben des Gewichtungsfaktors von Null auf Eins und Zeichnen von Linien zwischen den zurückgegebenen Punkten können wir eine perfekt glatte Kurve erzeugen. Um die Leistungskosten niedrig zu halten, berücksichtigen wir jedoch nur einen einzigen interpolierten Punkt mit einem Gewichtungsfaktor von 0,5. Wir ersetzen dann die ursprüngliche gerade Linie im Gitter durch zwei Linien, die sich am interpolierten Punkt treffen.

Das folgende Diagramm zeigt den Effekt dieser Interpolation:


Da die Liniensegmente im Gitter bereits klein sind, macht die Verwendung von mehr als einem interpolierten Punkt im Allgemeinen keinen merklichen Unterschied.

Die Linien in unserem Raster sind oft sehr gerade und erfordern keine Glättung. Wir können dies überprüfen und vermeiden, dass Sie statt einer Linie zwei Linien zeichnen müssen: Wir prüfen, ob der Abstand zwischen dem interpolierten Punkt und dem Mittelpunkt der geraden Linie größer als ein Pixel ist. Wenn dies der Fall ist, nehmen wir an, dass die Linie gekrümmt ist und wir zwei Liniensegmente zeichnen.

Die Modifikation an unserer Raster :: draw () Die Methode zum Hinzufügen der Catmull-Rom-Interpolation für die horizontalen Linien ist unten dargestellt.

 left = toVec2 (GetPointMass (mPoints, x - 1, y) -> mPosition); Auftriebsdicke = (y% 3 == 1)? 3,0f: 1,0f; int clampedX = (int) tMath :: min (x + 1, Breite - 1); tVector2f mid = MathUtil :: catmullRom (toVec2 (GetPointMass (mPoints, x - 2, y) -> mPosition), links, p, toVec2 (GetPointMass (mPoints, clampedX, y) -> mPosition), 0.5f); if (mid.distanceSquared ((left + p) / 2)> 1) Extensions :: drawLine (spriteBatch, left, mid, color, thickness); Erweiterungen :: drawLine (spriteBatch, mid, p, color, thickness);  else Erweiterungen :: drawLine (spriteBatch, links, p, Farbe, Dicke); 

Das Bild unten zeigt die Auswirkungen der Glättung. An jedem interpolierten Punkt wird ein grüner Punkt gezeichnet, um besser darzustellen, wo die Linien geglättet werden.

Verwenden des Gitters in Shape Blaster

Jetzt ist es Zeit, das Raster in unserem Spiel zu verwenden. Wir beginnen mit der Deklaration eines öffentlichen Statischen Gitter Variable in GameRoot und Erstellen des Gitters in der GameRoot :: onInitView. Wir werden ein Raster mit etwa 600 Punkten erstellen.

 const int maxGridPoints = 600; tVector2f gridSpacing = tVector2f ((float) sqrtf (mViewportSize.width * mViewportSize.height / maxGridPoints)); mGrid = new Grid (tRectf (0,0, mViewportSize), gridSpacing);

Obwohl die ursprüngliche XNA-Version des Spiels 1.600 Punkte (anstelle von 600) benötigt, ist dies viel zu viel, um nur für die leistungsstarke Hardware im iPhone verwendet zu werden. Glücklicherweise ließ der ursprüngliche Code die Anzahl der Punkte anpassbar und bei etwa 600 Rasterpunkten können wir sie immer noch rendern und trotzdem eine optimale Bildrate beibehalten.

Dann rufen wir an Gitter :: Update () und Raster :: draw () von dem GameRoot :: onRedrawView () Methode in GameRoot. Dadurch können wir das Raster sehen, wenn wir das Spiel ausführen. Es müssen jedoch noch verschiedene Spielobjekte mit dem Raster interagieren.

Kugeln stoßen das Gitter ab. Wir haben bereits eine Methode dafür erstellt Grid :: applyExplosiveForce (). Fügen Sie die folgende Zeile hinzu Bullet :: Update () Methode.

 GameRoot :: getInstance () -> getGrid () -> applyExplosiveForce (0.5f * mVelocity.length (), mPosition, 80);

Dies führt dazu, dass Kugeln das Gitter proportional zu ihrer Geschwindigkeit abstoßen. Das war ziemlich einfach.

Lassen Sie uns nun an schwarzen Löchern arbeiten. Fügen Sie diese Zeile zu hinzu BlackHole :: Update ():

 GameRoot :: getInstance () -> getGrid () -> applyImplosiveForce ((float) sinf (mSprayAngle / 2.0f) * 10 + 20, mPosition, 200);

Dadurch saugt das Schwarze Loch mit unterschiedlicher Kraft in das Gitter ein. Wir haben die wiederverwendet mSprayAngle variabel, wodurch die Kraft auf das Gitter synchron mit dem Winkel pulsiert, mit dem die Partikel versprüht werden (obwohl die Hälfte durch die Division durch zwei geteilt wird). Die eingeleitete Kraft variiert sinusförmig zwischen 10 und 30.

Schließlich erstellen wir eine Schockwelle im Raster, wenn das Schiff des Spielers nach dem Tod respawgt. Wir werden dies tun, indem wir das Gitter entlang der z-Achse ziehen und dann die Kraft durch die Federn ausbreiten und springen lassen. Dies erfordert wiederum nur eine kleine Änderung an PlayerShip :: Update ().

 if (getIsDead ()) mFramesUntilRespawn--; if (mFramesUntilRespawn == 0) GameRoot :: getInstance () -> getGrid () -> applyDirectedForce (tVector3f (0, 0, 5000), tVector3f (mPosition.x, mPosition.y, 0), 50); 

Was kommt als nächstes?

Wir haben das grundlegende Gameplay und die Effekte implementiert. Es liegt an Ihnen, daraus ein komplettes und ausgefeiltes Spiel mit Ihrem eigenen Geschmack zu machen. Fügen Sie einige interessante neue Mechaniken hinzu, einige coole neue Effekte oder eine einzigartige Geschichte. Falls Sie nicht sicher sind, wo Sie anfangen sollen, finden Sie hier einige Vorschläge:

  • Passen Sie die Touch-Steuerelemente an Ihre persönlichen Vorlieben an.
  • Unterstützung für Hardware-Game-Controller in iOS 7 über das GameController-Framework hinzufügen.
  • Profilieren und optimieren Sie alle langsamen Teile des Codes mit dem integrierten Instrumentenwerkzeug von Xcode.
  • Versuchen Sie, Nachbearbeitungseffekte wie den in der ursprünglichen XNA-Version vorhandenen Bloom Shader hinzuzufügen.
  • Erstellen Sie neue Feindtypen wie Schlangen oder explodierende Feinde.
  • Erstellen Sie neue Waffentypen, wie Raketen oder eine Blitzkanone.
  • Fügen Sie einen Titelbildschirm und ein Hauptmenü sowie eine Highscore-Tabelle hinzu.
  • Fügen Sie einige Power-Ups wie ein Schild oder Bomben hinzu.
  • Experimentiere, um Powerups zu finden, die Spaß machen und dein Spiel hervorheben.
  • Erstellen Sie mehrere Ebenen. Härtere Level können härtere Gegner und fortgeschrittenere Waffen und Powerups einführen.
  • Fügen Sie Umweltgefahren wie Laser hinzu.

Der Himmel ist das Limit!