7 days of WordPress themes, graphics & videos - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Game Development
  2. Programming

Erstellung von einem Neon-Vektor-Shooter in XNA: The Warping Grid

Read Time: 16 mins
This post is part of a series called Cross-Platform Vector Shooter: XNA.
Make a Neon Vector Shooter in XNA: Particle Effects

German (Deutsch) translation by Alex Grigorovich (you can also view the original English article)

In dieser Reihe von Tutorials zeige ich Ihnen, wie Sie in XNA einen Neon-Twin-Stick-Shooter wie Geometry Wars erstellen. Das Ziel dieser Tutorials ist es nicht, Ihnen eine exakte Nachbildung von Geometry Wars zu hinterlassen, sondern die erforderlichen Elemente zu erläutern, mit denen Sie Ihre eigene hochwertige Variante erstellen können.


Überblick

In der bisherigen Serie haben wir die Gameplay-, Bloom- und Partikeleffekte erstellt. In diesem letzten Teil erstellen wir ein dynamisches, verzerrendes Hintergrundraster.

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.

Grid Layout

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 zuerst, dass die Umkehrung der Masse 1 / mass gespeichert wird. Dies ist in physikalischen Simulationen 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.

Die Klasse enthält auch eine Dämpfungsvariable. Dies wird grob 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 Update() -Methode erledigt die Arbeit des Verschiebens der 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 Denormalzahl bezeichnet wird. Dies hat den Vorteil, dass Float kleinere Zahlen darstellen kann, aber dies hat seinen 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 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 so.

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 Update() -Methode prüft zuerst, 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 irgendwann 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 Rand- 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 finden Sie einige Bilder dieser Manipulationen in Aktion.

Bullets repelling the grid outwardsBullets repelling the grid outwardsBullets repelling the grid outwards
Kugeln stoßen das Gitter nach außen ab.
Sucking the grid inwardsSucking the grid inwardsSucking the grid inwards
Das Gitter nach innen saugen.
Wave created by pushing the grid along the z-axisWave created by pushing the grid along the z-axisWave created by pushing the grid along the z-axis
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 werden das Gitter zeichnen, indem wir Liniensegmente zwischen jedem benachbarten Punktpaar zeichnen. Zuerst erstellen wir eine Erweiterungsmethode für SpriteBatch, mit der 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, oder Sie können der Art.Load() -Methode einfach die folgenden zwei Zeilen hinzufügen.

Dadurch wird einfach eine neue 1x1px-Textur erstellt und das einzige Pixel auf Weiß gesetzt. Fügen Sie nun die folgende Methode in die Extensions-Klasse ein.

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

Dann 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, left ist der Punkt direkt links 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
Gitter mit interpolierten Linien in rot dargestellt

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 Draw() -Methode ein.

Die zweite Verbesserung besteht darin, unsere geraden Liniensegmente zu interpolieren, um sie zu glatteren Kurven zu machen. XNA bietet die praktische Methode Vector2.CatmullRom(), mit der die Catmull-Rom-Interpolation durchgeführt wird. 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.

Das fünfte Argument für Vector2.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 Interpolation

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 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 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.

Smoothed Grid LinesSmoothed Grid LinesSmoothed Grid 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 der GameRoot.Initialize() -Methode zu erstellen. Wir werden ein Raster mit ungefähr 1600 Punkten erstellen.

Dann rufen wir Grid.Update() und Grid.Draw() von den Methoden Update() und Draw() 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 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. Ich habe die Variable sprayAngle 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 weiter?

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.

  • 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ü hinzu.
  • Fügen Sie eine Highscore-Tabelle hinzu.
  • Fügen Sie einige Powerups wie einen Schild oder Bomben hinzu. Werden Sie kreativ mit Ihren Powerups, um Bonuspunkte zu erhalten. Sie können Powerups erstellen, die die Schwerkraft manipulieren, die Zeit verändern oder wie Organismen wachsen. Sie können eine riesige, auf Physik basierende Abrissbirne am Schiff anbringen, um Feinde zu zerschlagen. 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.
  • Erlaube einem zweiten Spieler, sich einem Gamepad anzuschließen.
  • Lassen Sie die Arena scrollen, damit sie größer als das Spielfenster ist.
  • Fügen Sie Umweltgefahren wie Laser hinzu.
  • Füge einen Shop oder ein Levelsystem hinzu und erlaube dem Spieler Upgrades zu verdienen.

Danke fürs Lesen!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Scroll to top
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.