Erstellen Sie einen Neon-Vektor-Shooter in XNA The Warping Grid

In dieser Serie von Tutorials zeige ich Ihnen, wie Sie einen Neon-Twin-Shooter wie Geometry Wars in XNA erstellen. Das Ziel dieser Tutorials ist es nicht, Ihnen eine exakte Nachbildung von Geometry Wars zu geben, sondern die notwendigen Elemente durchzugehen, mit denen Sie eine qualitativ 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.

Warnung: Laut!

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.

 Privatklasse PointMass public Vector3 Position; öffentliche Vector3-Geschwindigkeit; public float InverseMass; private Vector3-Beschleunigung; Dämpfung des privaten Schwimmers = 0,98 f; public PointMass (Position Vector3, Float invMass) Position = Position; InverseMass = invMass;  public void ApplyForce (Vector3 force) Beschleunigung + = force * InverseMass;  public void IncreaseDamping (Float-Faktor) Dämpfung * = Faktor;  public void Update () Geschwindigkeit + = Beschleunigung; Position + = Geschwindigkeit; Beschleunigung = Vector3.Zero; if (Velocity.LengthSquared () < 0.001f * 0.001f) Velocity = Vector3.Zero; Velocity *= damping; damping = 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.

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

Das Aktualisieren() 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 aktualisieren würden nach dem die Position aktualisieren.

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 Fließkommazahlen sehr klein werden, verwenden sie eine spezielle Darstellung, die Denormal-Nummer. Dies hat den Vorteil, dass Float kleinere Zahlen darstellen kann, jedoch zu einem Preis. Die meisten Chipsätze können ihre Standard-Arithmetikoperationen nicht verwenden denormalisierten Zahlen und müssen sie stattdessen mit einer Reihe von Schritten emulieren. Dies kann zehn bis hundert Mal langsamer sein als das Ausführen von Operationen mit normalisierten Fließkommazahlen. Da wir unsere Geschwindigkeit mit unserem Dämpfungsfaktor für jedes Bild multiplizieren, wird sie letztendlich sehr klein Wir interessieren uns nicht für solche kleinen Geschwindigkeiten, also setzen wir sie einfach auf Null.)

Das IncreaseDamping () 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.

 private struct Spring public PointMass End1; public PointMass End2; öffentlicher Float TargetLength; public float Steifigkeit; öffentlicher Schwimmer Dämpfung; public Spring (PointMass end1, PointMass end2, Schwimmkörpersteifigkeit, Schwimmerdämpfung) End1 = End1; End2 = End2; Steifheit = Steifheit; Dämpfung = Dämpfung; TargetLength = Vector3.Distance (end1.Position, end2.Position) * 0,95f;  public void Update () var x = Ende1.Position - End2.Position; Floatlänge = x.Length (); // Diese Federn können nur ziehen, wenn nicht drücken (Länge <= TargetLength) return; x = (x / length) * (length - TargetLength); var dv = End2.Velocity - End1.Velocity; var force = Stiffness * x - dv * Damping; End1.ApplyForce(-force); End2.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 Aktualisieren() 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.

 Feder [] Federn; PointMass [,] Punkte; public Grid (Rechteckgröße, Vector2-Abstand) var springList = new List (); int numColumns = (int) (Größe. Breite / Abstand.X) + 1; int numRows = (int) (Größe.Höhe / Abstand.Y) + 1; points = new PointMass [numColumns, numRows]; // Diese festen Punkte werden verwendet, um das Raster an festen Positionen auf dem Bildschirm zu verankern. PointMass [,] fixedPoints = new PointMass [numColumns, numRows]; // die Punktmassen erstellen int column = 0, row = 0; für (float y = size.Top; y <= size.Bottom; y += spacing.Y)  for (float x = size.Left; x <= size.Right; x += spacing.X)  points[column, row] = new PointMass(new Vector3(x, y, 0), 1); fixedPoints[column, row] = new PointMass(new Vector3(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) // anchor the border of the grid springList.Add(new Spring(fixedPoints[x, y], points[x, y], 0.1f, 0.1f)); else if (x % 3 == 0 && y % 3 == 0) // loosely anchor 1/9th of the point masses springList.Add(new Spring(fixedPoints[x, y], points[x, y], 0.002f, 0.02f)); const float stiffness = 0.28f; const float damping = 0.06f; if (x > 0) springList.Add (neue Feder (Punkte [x - 1, y], Punkte [x, y], Steifigkeit, Dämpfung)); if (y> 0) springList.Add (neue Feder (Punkte [x, y - 1], Punkte [x, y], Steifigkeit, Dämpfung));  springs = springList.ToArray (); 

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 irgendwann gesammelt, nachdem der Konstruktor beendet ist. 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 verlangsamen sich die Federn schneller.

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.

 public void Update () foreach (var-Feder in Federn) spring.Update (); foreach (variable Masse in Punkten) Masse.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. Unten sehen Sie 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.
 public void ApplyDirectedForce (Kraft Vector3, Position Vector3, Schwimmkörperradius) foreach (Masse in Punkten) if (Vector3.DistanceSquared (Position, Masse.Position) < radius * radius) mass.ApplyForce(10 * force / (10 + Vector3.Distance(position, mass.Position)));  public void ApplyImplosiveForce(float force, Vector3 position, float radius)  foreach (var mass in points)  float dist2 = Vector3.DistanceSquared(position, mass.Position); if (dist2 < radius * radius)  mass.ApplyForce(10 * force * (position - mass.Position) / (100 + dist2)); mass.IncreaseDamping(0.6f);    public void ApplyExplosiveForce(float force, Vector3 position, float radius)  foreach (var mass in points)  float dist2 = Vector3.DistanceSquared(position, mass.Position); if (dist2 < radius * radius)  mass.ApplyForce(100 * force * (mass.Position - position) / (10000 + dist2)); mass.IncreaseDamping(0.6f);   

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

Rendern des Gitters

Das Raster wird gezeichnet, indem Liniensegmente zwischen jedem benachbarten Punktpaar gezeichnet werden. Zuerst machen wir eine Erweiterungsmethode SpriteBatch Auf diese Weise können wir Liniensegmente zeichnen, indem wir die Textur eines einzelnen Pixels nehmen und in eine Linie strecken.

Öffne das Kunst Klasse und deklarieren eine Textur für das Pixel.

 public static Texture2D Pixel get; privates Set; 

Sie können die Pixel-Textur auf dieselbe Weise einstellen, wie wir die anderen Bilder einrichten, oder Sie fügen einfach die folgenden zwei Zeilen hinzu Art.Load () Methode.

 Pixel = new Texture2D (Player.GraphicsDevice, 1, 1); Pixel.SetData (new [] Color.White);

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

 public static void DrawLine (SpriteBatch SpriteBatch, Vector2-Start, Vector2-Ende, Farbe, Floatdicke = 2f) Vector2-Delta = Ende - Start; spriteBatch.Draw (Art.Pixel, start, null, color, delta.ToAngle (), neuer Vector2 (0, 0.5f), neuer Vector2 (delta.Length (), Dicke), SpriteEffects.none, 0f); 

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.

 public Vector2 ToVec2 (Vector3 v) // Floatfaktor für eine perspektivische Projektion = (v.Z + 2000) / 2000; return (neuer Vector2 (v.X, v.Y) - screenSize / 2f) * Faktor + screenSize / 2; 

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

 public void Draw (SpriteBatch SpriteBatch) int width = points.GetLength (0); int height = Punkte. GetLength (1); Farbfarbe = neue Farbe (30, 30, 139, 85); // dunkelblau für (int y = 1; y < height; y++)  for (int x = 1; x < width; x++)  Vector2 left = new Vector2(), up = new Vector2(); Vector2 p = ToVec2(points[x, y].Position); if (x > 1) left = ToVec2 (Punkte [x - 1, y]. Position); Auftriebsdicke = y% 3 == 1? 3f: 1f; spriteBatch.DrawLine (links, p, Farbe, Dicke);  if (y> 1) up = ToVec2 (Punkte [x, y - 1] .Position); Auftriebsdicke = x% 3 == 1? 3f: 1f; spriteBatch.DrawLine (bis, p, Farbe, Dicke); 

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.


Gitter mit interpolierten Linien in roter Farbe

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

 if (x> 1 && y> 1) Vector2 upLeft = ToVec2 (Punkte [x - 1, y - 1] .Position); spriteBatch.DrawLine (0.5f * (upLeft + up), 0.5f * (links + p), Farbe, 1f); // vertikale Linie spriteBatch.DrawLine (0.5f * (upLinks links), 0.5f * (up + p), Farbe, 1f); // horizontale Linie 

Die zweite Verbesserung besteht in der Interpolation unserer geraden Liniensegmente, um sie zu glatteren Kurven zu machen. XNA bietet das Handy 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.

Das fünfte Argument zu Vector2.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 Zeichnen() Die Methode zum Hinzufügen der Catmull-Rom-Interpolation für die horizontalen Linien ist unten dargestellt.

 left = ToVec2 (Punkte [x - 1, y] .Position); Auftriebsdicke = y% 3 == 1? 3f: 1f; // Catmull-Rom-Interpolation verwenden, um das Biegen im Raster zu erleichtern int clampedX = Math.Min (x + 1, width - 1); Vector2 mid = Vector2.CatmullRom (ToVec2 (Punkte [x - 2, y] .Position), links, p, ToVec2 (Punkte [clampedX, y] .Position), 0.5f); // Wenn das Raster hier sehr gerade ist, zeichnen Sie eine einzelne gerade Linie. Andernfalls zeichnen Sie Linien zu unserem // neuen interpolierten Mittelpunkt if (Vector2.DistanceSquared (mid, (links + p) / 2)> 1) spriteBatch.DrawLine (links, mid, color, thickness); spriteBatch.DrawLine (mittel, p, Farbe, Dicke);  else spriteBatch.DrawLine (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.Initialize () Methode. Wir werden ein Raster mit ungefähr 1600 Punkten erstellen.

 const int maxGridPoints = 1600; Vector2 gridSpacing = new Vector2 ((float) Math.Sqrt (Viewport.Width * Viewport.Height / maxGridPoints)); Raster = neues Raster (Viewport.Bounds, Rasterabstand);

Dann rufen wir an Grid.Update () und Grid.Draw () von dem Aktualisieren() und Zeichnen() Methoden 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 ApplyExplosiveForce (). Fügen Sie die folgende Zeile hinzu Bullet.Update () Methode.

 GameRoot.Grid.ApplyExplosiveForce (0.5f * Velocity.Length (), Position, 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.Grid.ApplyImplosiveForce ((Float) Math.Sin (sprayAngle / 2) * 10 + 20, Position, 200);

Dadurch saugt das Schwarze Loch mit unterschiedlicher Kraft in das Gitter ein. Ich habe das wieder verwendet sprayAngle 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 (IsDead) if (--framesUntilRespawn == 0) GameRoot.Grid.ApplyDirectedForce (neuer Vector3 (0, 0, 5000), neuer Vector3 (Position, 0), 50); Rückkehr; 

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.

  • 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ü hinzu.
  • Fügen Sie eine Highscores-Tabelle hinzu.
  • Fügen Sie einige Power-Ups wie ein Schild oder Bomben hinzu. Für Bonuspunkte werden Sie kreativ mit Ihren Powerups. Sie können Power-Ups erstellen, die die Schwerkraft beeinflussen, die Zeit verändern oder wie Organismen wachsen. Sie können einen riesigen, auf Physik basierenden Abrissball an das Schiff anschließen, um Feinde zu besiegen. 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.
  • Erlaube einem zweiten Spieler, sich mit einem Gamepad anzuschließen.
  • Lassen Sie die Arena so scrollen, dass sie größer als das Spielfenster ist.
  • Fügen Sie Umweltgefahren wie Laser hinzu.
  • Fügen Sie ein Shop- oder Level-System hinzu und ermöglichen Sie dem Spieler, Upgrades zu erhalten.

Danke fürs Lesen!