Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Programming

Erstellen Sie einen Neon Vector Shooter für iOS: The Warping Grid

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Cross-Platform Vector Shooter: iOS.
Make a Neon Vector Shooter for iOS: Particle Effects
Make a Neon Vector Shooter for iOS: First Steps

German (Deutsch) translation by Tatsiana Bochkareva (you can also view the original English article)

In dieser Reihe von Tutorials zeige ich Ihnen, wie Sie einen von Geometry Wars inspirierten Twin-Stick-Shooter mit Neongrafiken, verrückten Partikeleffekten und großartiger Musik für iOS mit C++ und OpenGL ES 2.0 erstellen. In diesem letzten Teil fügen wir das Hintergrundraster hinzu, das sich basierend auf der Aktion im Spiel verzieht.

Überblick

In der bisherigen Serie haben wir das Gameplay, das virtuelle Gamepad und die 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 Framerate feststellen, wenn Sie den Code noch im Debug-Modus ausführen. In diesem Tutorial finden Sie Details zum Umschalten in den Release-Modus für eine vollständige Compileroptimierung (und eine schnellere Erstellung).

Das Warping-Gitter

Einer der coolsten Effekte in Geometry Wars ist das Warping-Hintergrundgitter. Wir werden untersuchen, wie Sie in Shape Blaster einen ähnlichen Effekt erzielen. Das Gitter reagiert auf Kugeln, Schwarze Löcher und das Wiederauftauchen des Spielers. Es ist nicht schwer zu machen und es sieht toll aus.

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

ios-geometry-wars-warping-gridios-geometry-wars-warping-gridios-geometry-wars-warping-grid

Wir werden eine Klasse namens Grid erstellen, um diesen Effekt zu erzielen. Bevor wir jedoch am Raster selbst arbeiten, müssen wir zwei Hilfsklassen erstellen: Spring und PointMass.

Die PointMass-Klasse

Die PointMass-Klasse repräsentiert die Massen, an denen wir die Federn anbringen werden. Federn werden niemals direkt mit anderen Federn verbunden. Stattdessen üben sie eine Kraft auf die Massen aus, die sie verbinden, was wiederum andere Federn dehnen kann.

Es gibt einige interessante Punkte zu dieser Klasse. Beachten Sie zunächst, dass die Umkehrung der Masse 1 / mass gespeichert wird. Dies ist in Physiksimulationen oft eine gute Idee, da physikalische Gleichungen dazu neigen, die Umkehrung der Masse häufiger 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ämpfungsvariable. Dies wird ungefähr als Reibung oder Luftwiderstand verwendet; es verlangsamt allmählich die Masse. Dies trägt dazu bei, dass das Gitter schließlich zur Ruhe kommt, und erhöht auch die Stabilität der Federsimulation.

Die PointMass::update() -Methode verschiebt die Punktmasse in jedem Frame. Es beginnt mit der symplektischen Euler-Integration, was nur bedeutet, dass wir die Beschleunigung zur Geschwindigkeit hinzufügen und dann die aktualisierte Geschwindigkeit zur Position hinzufügen. Dies unterscheidet sich von der Standard-Euler-Integration, bei der die Geschwindigkeit nach dem Aktualisieren der Position aktualisiert wird.

Tipp: Symplectic Euler eignet sich besser für Frühlingssimulationen, da es Energie spart. Wenn Sie die reguläre Euler-Integration verwenden und Federn ohne Dämpfung erstellen, dehnen sie sich bei jedem Sprung mit zunehmender Energie immer weiter und brechen schließlich Ihre Simulation.

Nach dem Aktualisieren der Geschwindigkeit und Position prüfen wir, ob die Geschwindigkeit sehr klein ist, und setzen sie in diesem Fall auf Null. Dies kann aufgrund der Art der denormalisierten Gleitkommazahlen für die Leistung wichtig sein.

(Wenn Gleitkommazahlen sehr klein werden, verwenden sie eine spezielle Darstellung, die als denormalisierte Zahl bezeichnet wird. Dies hat den Vorteil, dass Gleitkommazahlen kleinere Zahlen darstellen können, dies hat jedoch einen Preis. Die meisten Chipsätze können ihre Standardarithmetikoperationen nicht für denormalisierte Zahlen verwenden und müssen sie stattdessen in einer Reihe von Schritten emulieren. Dies kann zehn- bis hundertmal langsamer sein als das Ausführen von Operationen mit normalisierten Gleitkommazahlen. Da wir unsere Geschwindigkeit mit unserem Dämpfungsfaktor jedes Bildes multiplizieren, wird es schließlich sehr klein. Wir kümmern uns eigentlich nicht um so kleine Geschwindigkeiten, also setzen wir sie einfach auf Null.)

Die PointMass::increaseDamping() -Methode wird verwendet, um den Dämpfungsgrad vorübergehend zu erhöhen. Wir werden dies später für bestimmte Effekte verwenden.

Die Frühlingsklasse

Eine Feder verbindet zwei Punktmassen und übt, wenn sie über ihre natürliche Länge hinaus gedehnt wird, eine Kraft aus, die die Massen zusammenzieht. Federn folgen einer modifizierten Version des Hookeschen Gesetzes 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 Spring-Klasse lautet wie folgt:

Wenn wir eine Feder erstellen, stellen wir die natürliche Länge der Feder so ein, dass sie nur geringfügig kleiner ist als der Abstand zwischen den beiden Endpunkten. Dies hält das Gitter auch in Ruhe straff und verbessert das Erscheinungsbild etwas.

Die Spring::update() -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 dies der Fall ist, verwenden wir das modifizierte Hookesche Gesetz, um die Kraft der Feder zu ermitteln und auf die beiden verbundenen Massen anzuwenden.

Erstellen des Rasters

Nachdem wir die erforderlichen verschachtelten Klassen haben, können wir das Raster erstellen. Wir beginnen mit der Erstellung von PointMass-Objekten an jeder Kreuzung im Raster. Wir erstellen auch einige unbewegliche PointMass-Ankerobjekte, um das Raster an Ort und Stelle zu halten. Wir verbinden dann die Massen mit Federn.

Die erste for-Schleife erzeugt an jedem Schnittpunkt des Gitters sowohl reguläre als auch unbewegliche Massen. Wir werden nicht alle unbeweglichen Massen verwenden, und die nicht verwendeten Massen werden einfach einige Zeit nach dem Ende des Konstruktors als Müll gesammelt. Wir könnten optimieren, indem wir vermeiden, unnötige Objekte zu erstellen. Da das Raster jedoch normalerweise nur einmal erstellt wird, macht es keinen großen Unterschied.

Zusätzlich zur Verwendung von Ankerpunktmassen um den Rand des Gitters werden wir auch einige Ankermassen innerhalb des Gitters verwenden. Diese werden verwendet, um das Gitter nach dem Verformen sehr sanft in seine ursprüngliche Position zurückzuziehen.

Da sich die Ankerpunkte niemals bewegen, müssen sie nicht bei jedem Frame aktualisiert werden. wir können sie einfach an die Federn anschließen und sie vergessen. Daher haben wir für diese Massen keine Mitgliedsvariable in der Grid-Klasse.

Es gibt eine Reihe von Werten, die Sie bei der Erstellung des Rasters anpassen können. Die wichtigsten sind die Steifheit und Dämpfung der Federn. (Die Steifigkeit und Dämpfung der Randanker und Innenanker wird unabhängig von den Hauptfedern eingestellt.) Höhere Steifigkeitswerte lassen die Federn schneller schwingen, und höhere Dämpfungswerte führen dazu, dass die Federn schneller langsamer werden.

Das Gitter manipulieren

Damit sich das Raster bewegen kann, müssen wir es bei jedem Frame aktualisieren. Dies ist sehr einfach, da wir bereits die ganze harte Arbeit in den Klassen PointMass und Spring geleistet haben:

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

bullets-repel-gridbullets-repel-gridbullets-repel-grid
Kugeln stoßen das Gitter nach außen ab.
grid-suctiongrid-suctiongrid-suction
Das Gitter nach innen saugen.
grid-pulsegrid-pulsegrid-pulse
Welle, die durch Verschieben des Gitters entlang der Z-Achse erzeugt wird.

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

Rendern des Rasters

Wir zeichnen das Gitter, indem wir Liniensegmente zwischen jedem benachbarten Punktpaar zeichnen. Zuerst fügen wir eine Erweiterungsmethode hinzu, die einen tSpriteBatch-Zeiger als Parameter verwendet, mit dem wir Liniensegmente zeichnen können, indem wir eine Textur eines einzelnen Pixels nehmen und zu einer Linie strecken.

Öffnen Sie die Art-Klasse und deklarieren Sie eine Textur für das Pixel:

Sie können die Pixeltextur genauso einstellen wie die anderen Bilder. Daher fügen wir dem Projekt pixel.png (ein 1x1px-Bild mit dem einzigen Pixel auf Weiß) hinzu und laden es in die tTexture:

Fügen wir nun der Extensions-Klasse die folgende Methode hinzu:

Diese Methode streckt, dreht und färbt die Pixeltextur, um die gewünschte Linie zu erzeugen.

Als nächstes benötigen wir eine Methode, um die 3D-Gitterpunkte auf unseren 2D-Bildschirm zu projizieren. Normalerweise wird dies mithilfe von Matrizen durchgeführt, aber hier werden die Koordinaten stattdessen manuell transformiert.

Fügen Sie der Grid-Klasse Folgendes hinzu:

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

Im obigen Code ist p unser aktueller Punkt im Raster, links ist der Punkt direkt left davon und up ist der Punkt direkt darüber. Für den visuellen Effekt zeichnen wir jede dritte Linie horizontal und vertikal dicker.

Interpolation

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

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

interpolated-linesinterpolated-linesinterpolated-lines

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

Die zweite Verbesserung besteht darin, unsere geraden Liniensegmente zu interpolieren, um sie zu glatteren Kurven zu machen. In der ursprünglichen XNA-Version dieses Spiels stützte sich der Code auf die Vector2.CatmullRom() -Methode von XNA, die die Catmull-Rom-Interpolation durchführt. Sie übergeben der Methode vier aufeinanderfolgende Punkte auf einer gekrümmten Linie und sie gibt Punkte entlang einer glatten Kurve zwischen dem zweiten und dem dritten Punkt zurück, 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 eine MathUtil::catmullRom() -Methode bereitgestellt, die auf dieser Referenzimplementierung basiert:

Das fünfte Argument für MathUtil::catmullRom() ist ein Gewichtungsfaktor, der bestimmt, welcher Punkt auf der interpolierten Kurve zurückgegeben wird. Ein Gewichtungsfaktor von 0 oder 1 gibt den von Ihnen angegebenen zweiten oder dritten Punkt zurück, und ein Gewichtungsfaktor von 0.5 gibt den Punkt auf der interpolierten Kurve auf halbem Weg zwischen den beiden Punkten zurück. 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, wird jedoch nur ein einziger interpolierter Punkt bei einem Gewichtungsfaktor von 0.5 berücksichtigt. 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:

catmull-rom-effectcatmull-rom-effectcatmull-rom-effect

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

Oft sind die Linien in unserem Raster sehr gerade und erfordern keine Glättung. Wir können dies überprüfen und vermeiden, zwei Linien anstelle von einer zeichnen zu 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 zeichnen zwei Liniensegmente.

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

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

interpolated-linesinterpolated-linesinterpolated-lines

Verwenden des Rasters in Form Blaster

Jetzt ist es Zeit, das Gitter in unserem Spiel zu verwenden. Wir beginnen damit, eine öffentliche, statische Grid-Variable in GameRoot zu deklarieren und das Grid in GameRoot::onInitView zu erstellen. Wir werden ein Raster mit ungefähr 600 Punkten erstellen.

Obwohl die ursprüngliche XNA-Version des Spiels 1.600 Punkte (statt 600) verwendet, ist dies viel zu viel, um es selbst für die leistungsstarke Hardware des iPhones zu handhaben. Glücklicherweise hat der ursprüngliche Code die Anzahl der Punkte anpassbar gelassen, und bei etwa 600 Rasterpunkten können wir sie weiterhin rendern und eine optimale Bildrate beibehalten.

Dann rufen wir Grid::update() und Grid::draw() von der GameRoot::onRedrawView() -Methode in GameRoot auf. Auf diese Weise können wir das Raster sehen, wenn wir das Spiel ausführen. Wir müssen jedoch noch verschiedene Spielobjekte mit dem Raster interagieren lassen.

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

Dadurch stoßen Kugeln das Gitter proportional zu ihrer Geschwindigkeit ab. Das war ziemlich einfach.

Jetzt arbeiten wir an schwarzen Löchern. Fügen Sie diese Zeile zu BlackHole::update() hinzu:

Dadurch saugt das Schwarze Loch mit unterschiedlicher Kraft im Gitter. Wir haben die Variable mSprayAngle wiederverwendet, wodurch die Kraft auf das Gitter synchron mit dem Winkel pulsiert, in dem Partikel gesprüht werden (allerdings mit der halben Frequenz aufgrund der Division durch zwei). Die eingelassene Kraft variiert sinusförmig zwischen 10 und 30.

Schließlich werden wir eine Schockwelle im Gitter erzeugen, wenn das Schiff des Spielers nach dem Tod wieder erscheint. Wir werden dies tun, indem wir das Gitter entlang der z-Achse ziehen und dann zulassen, dass sich die Kraft ausbreitet und durch die Federn springt. Auch dies erfordert nur eine kleine Änderung an PlayerShip::update().

Was kommt als nächstes?

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

  • Passen Sie die Touch-Steuerelemente an Ihre persönlichen Vorlieben an.
  • Fügen Sie Unterstützung für Hardware-Gamecontroller in iOS 7 über das GameController-Framework hinzu.
  • Profilieren und optimieren Sie alle langsamen Teile des Codes mit dem integrierten Tool 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 die Suche nach Raketen oder einer Blitzwaffe.
  • Fügen Sie einen Titelbildschirm und ein Hauptmenü sowie eine Highscore-Tabelle hinzu.
  • Fügen Sie einige Powerups wie einen Schild oder Bomben hinzu.
  • Experimentieren Sie, um Powerups zu finden, die Spaß machen und Ihrem Spiel helfen, sich abzuheben.
  • Erstellen Sie mehrere Ebenen. Härtere Level können härtere Feinde und fortgeschrittenere Waffen und Powerups einführen.
  • Fügen Sie Umweltgefahren wie Laser hinzu.

Der Himmel ist das Limit!

Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.