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

Grundlegende 2D-Platformer-Physik, Teil 1

Read Time: 16 mins
This post is part of a series called Basic 2D Platformer Physics .
Basic 2D Platformer Physics, Part 2

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

Zeichenkollisionen

Okay, die Prämisse sieht also so aus: Wir wollen einen 2D-Plattformer mit einfacher, robuster, reaktionsschneller, genauer und vorhersehbarer Physik erstellen. In diesem Fall möchten wir keine große 2D-Physik-Engine verwenden, und dafür gibt es einige Gründe:

  • unvorhersehbare Kollisionsreaktionen
  • Es ist schwierig, eine genaue und robuste Charakterbewegung einzurichten
  • viel komplizierter zu arbeiten
  • benötigt viel mehr Rechenleistung als einfache Physik

Natürlich gibt es auch viele Profis, die eine handelsübliche Physik-Engine verwenden, beispielsweise die Möglichkeit, komplexe physikalische Interaktionen ganz einfach einzurichten, aber das ist nicht das, was wir für unser Spiel benötigen.

Eine benutzerdefinierte Physik-Engine verleiht dem Spiel ein benutzerdefiniertes Gefühl, und das ist wirklich wichtig! Selbst wenn Sie mit einem relativ einfachen Setup beginnen, wird die Art und Weise, wie sich Dinge bewegen und miteinander interagieren, immer nur von Ihren eigenen Regeln beeinflusst und nicht von denen anderer. Lasst uns anfangen!

Zeichengrenzen

Beginnen wir damit, zu definieren, welche Formen wir in unserer Physik verwenden werden. Eine der grundlegendsten Formen, mit denen wir ein physisches Objekt in einem Spiel darstellen können, ist eine Axis Aligned Bounding Box (AABB). AABB ist im Grunde ein nicht gedrehtes Rechteck.

Example of an AABBExample of an AABBExample of an AABB

In vielen Plattformspielen reichen AABBs aus, um den Körper jedes Objekts im Spiel zu approximieren. Sie sind äußerst effektiv, da es sehr einfach ist, eine Überlappung zwischen AABBs zu berechnen, und nur sehr wenige Daten erforderlich sind. Um einen AABB zu beschreiben, reicht es aus, dessen Zentrum und Größe zu kennen.

Lassen Sie uns ohne weiteres eine Struktur für unseren AABB erstellen.

Wie bereits erwähnt, benötigen wir hier nur zwei Vektoren, um Daten zu erhalten. Das erste ist das Zentrum des AABB und das zweite die halbe Größe. Warum halbe Größe? Die meiste Zeit für Berechnungen benötigen wir sowieso die halbe Größe. Anstatt sie jedes Mal zu berechnen, merken wir sie uns einfach anstelle der vollen Größe.

Beginnen wir mit dem Hinzufügen eines Konstruktors, damit die Struktur mit benutzerdefinierten Parametern erstellt werden kann.

Damit können wir die Kollisionsprüfungsfunktionen erstellen. Lassen Sie uns zunächst einfach überprüfen, ob zwei AABBs miteinander kollidieren. Dies ist sehr einfach - wir müssen nur sehen, ob der Abstand zwischen den Mittelpunkten auf jeder Achse kleiner ist als die Summe der halben Größen.

Hier ist ein Bild, das diese Prüfung auf der x-Achse zeigt. Die y-Achse wird auf die gleiche Weise überprüft.

Demonstrating a check on the X-AxisDemonstrating a check on the X-AxisDemonstrating a check on the X-Axis

Wie Sie sehen können, wäre keine Überlappung möglich, wenn die Summe der halben Größen kleiner als der Abstand zwischen den Zentren wäre. Beachten Sie, dass wir im obigen Code der Kollisionsprüfung frühzeitig entkommen können, wenn wir feststellen, dass sich die Objekte auf der ersten Achse nicht überlappen. Die Überlappung muss auf beiden Achsen vorhanden sein, wenn die AABBs im 2D-Raum kollidieren sollen.

Objekt verschieben

Beginnen wir mit der Erstellung einer Klasse für ein Objekt, das von der Physik des Spiels beeinflusst wird. Später werden wir dies als Basis für ein tatsächliches Spielerobjekt verwenden. Nennen wir diese Klasse MovingObject.

Füllen wir nun diese Klasse mit den Daten. Wir werden ziemlich viele Informationen für dieses Objekt benötigen:

  • Position und Position des vorherigen Frames
  • Geschwindigkeit und Geschwindigkeit des vorherigen Frames
  • Rahmen
  • AABB und ein Offset dafür (damit wir es mit einem Sprite ausrichten können)
  • ist Objekt auf dem Boden und ob es auf dem Boden letzten Rahmen war
  • ist ein Objekt neben der Wand auf der linken Seite und ob es neben dem letzten Frame war
  • ist ein Objekt neben der Wand auf der rechten Seite und ob es neben dem letzten Frame war
  • ist Objekt an der Decke und ob es an der Decke letzten Rahmen war

Position, Geschwindigkeit und Skalierung sind 2D-Vektoren.

Fügen wir nun den AABB und den Offset hinzu. Der Offset wird benötigt, damit wir den AABB frei an das Sprite des Objekts anpassen können.

Zum Schluss deklarieren wir die Variablen, die den Positionszustand des Objekts angeben, egal ob es sich auf dem Boden, neben einer Wand oder an der Decke befindet. Diese sind sehr wichtig, weil sie uns wissen lassen, ob wir springen können oder zum Beispiel einen Sound spielen müssen, nachdem wir gegen eine Wand gestoßen sind.

Das sind die Grundlagen. Jetzt erstellen wir eine Funktion, die das Objekt aktualisiert. Im Moment werden wir nicht alles einrichten, aber gerade genug, um grundlegende Zeichensteuerelemente zu erstellen.

Das erste, was wir hier tun möchten, ist, die Daten des vorherigen Frames in den entsprechenden Variablen zu speichern.

Jetzt aktualisieren wir die Position mit der aktuellen Geschwindigkeit.

Und jetzt machen wir es so, dass wir annehmen, dass der Charakter auf dem Boden liegt, wenn die vertikale Position kleiner als Null ist. Das ist nur für den Moment, damit wir die Steuerelemente des Charakters einrichten können. Später werden wir eine Kollision mit einer Tilemap durchführen.

Danach müssen wir auch das AABB-Zentrum aktualisieren, damit es tatsächlich der neuen Position entspricht.

Für das Demo-Projekt verwende ich Unity. Um die Position des Objekts zu aktualisieren, muss es auf die Transformationskomponente angewendet werden. Lassen Sie uns dies auch tun. Gleiches muss für die Waage getan werden.

Wie Sie sehen können, wird die gerenderte Position aufgerundet. Dies soll sicherstellen, dass das gerenderte Zeichen immer an einem Pixel ausgerichtet ist.

Zeichensteuerung

Daten

Nachdem wir unsere grundlegende MovingObject-Klasse fertiggestellt haben, können wir zunächst mit der Charakterbewegung spielen. Es ist schließlich ein sehr wichtiger Teil des Spiels und kann ziemlich sofort erledigt werden - es ist noch nicht nötig, zu tief in die Spielsysteme einzutauchen, und es wird fertig sein, wenn wir unseren Charakter testen müssen - Kartenkollisionen.

Lassen Sie uns zunächst eine Zeichenklasse erstellen und von der MovingObject-Klasse ableiten.

Wir müssen hier ein paar Dinge erledigen. Zunächst die Eingaben - lassen Sie uns eine Aufzählung erstellen, die alle Steuerelemente für den Charakter abdeckt. Erstellen wir es in einer anderen Datei und nennen es KeyInput.

Wie Sie sehen können, kann sich unser Charakter nach links, rechts, unten und oben bewegen. Das Abwärtsbewegen funktioniert nur auf Einwegplattformen, wenn wir durch sie fallen wollen.

Deklarieren wir nun zwei Arrays in der Zeichenklasse, eines für die Eingaben des aktuellen Frames und eines für die Eingaben des vorherigen Frames. Je nach Spiel kann dieses Setup mehr oder weniger sinnvoll sein. Anstatt den Schlüsselstatus in einem Array zu speichern, wird er normalerweise bei Bedarf mithilfe der spezifischen Funktionen einer Engine oder eines Frameworks überprüft. Ein Array, das nicht streng an reale Eingaben gebunden ist, kann jedoch von Vorteil sein, wenn wir beispielsweise Tastendrücke simulieren möchten.

Diese Arrays werden von der KeyInput-Enumeration indiziert. Um diese Arrays einfach zu verwenden, erstellen wir einige Funktionen, mit denen wir nach einem bestimmten Schlüssel suchen können.

Hier gibt es nichts Besonderes - wir möchten sehen können, ob eine Taste gerade gedrückt, gerade losgelassen oder ein- oder ausgeschaltet wurde.

Jetzt erstellen wir eine weitere Aufzählung, die alle möglichen Zustände des Charakters enthält.

Wie Sie sehen können, kann unser Charakter entweder stehen bleiben, gehen, springen oder einen Vorsprung ergreifen. Nachdem dies erledigt ist, müssen wir Variablen wie Sprunggeschwindigkeit, Gehgeschwindigkeit und aktuellen Status hinzufügen.

Natürlich werden hier einige weitere Daten benötigt, z. B. das Sprite für Charaktere, aber wie dies aussehen wird, hängt stark davon ab, welche Art von Engine Sie verwenden werden. Da ich Unity verwende, verwende ich einen Verweis auf einen Animator, um sicherzustellen, dass das Sprite die Animation für einen geeigneten Status wiedergibt.

Loop aktualisieren

Okay, jetzt können wir mit der Arbeit an der Update-Schleife beginnen. Was wir hier machen, hängt vom aktuellen Status des Charakters ab.

Stand State

Beginnen wir damit, zu füllen, was zu tun ist, wenn sich der Charakter nicht bewegt - im Standzustand. Zunächst sollte die Geschwindigkeit auf Null gesetzt werden.

Wir wollen auch das passende Sprite für den Staat zeigen.

Wenn der Charakter nicht auf dem Boden liegt, kann er nicht mehr stehen, sodass wir den Status ändern müssen, um zu springen.

Wenn die GoLeft- oder GoRight-Taste gedrückt wird, müssen wir unseren Status ändern, um gehen zu können.

Wenn die Sprungtaste gedrückt wird, möchten wir die vertikale Geschwindigkeit auf die Sprunggeschwindigkeit einstellen und den Zustand zum Springen ändern.

Das wird es zumindest vorerst für diesen Zustand sein.

Walk State

Erstellen wir nun eine Logik für die Bewegung auf dem Boden und beginnen sofort mit dem Abspielen der Laufanimation.

Wenn wir hier nicht die linke oder rechte Taste drücken oder beide gedrückt werden, möchten wir in den Stillstand zurückkehren.

Wenn die GoRight-Taste gedrückt wird, müssen wir die horizontale Geschwindigkeit auf mWalkSpeed ​​einstellen und sicherstellen, dass das Sprite entsprechend skaliert ist. Die horizontale Skalierung muss geändert werden, wenn das Sprite horizontal gedreht werden soll.

Wir sollten uns auch nur bewegen, wenn tatsächlich kein Hindernis vor uns liegt. Wenn also mPushesRightWall auf true gesetzt ist, sollte die horizontale Geschwindigkeit auf Null gesetzt werden, wenn wir uns nach rechts bewegen.

Wir müssen auch die linke Seite auf die gleiche Weise behandeln.

Wie im Stand müssen wir sehen, ob eine Sprungtaste gedrückt wird, und die vertikale Geschwindigkeit einstellen, wenn dies der Fall ist.

Wenn sich der Charakter nicht auf dem Boden befindet, muss er den Status ändern, um ebenfalls zu springen, jedoch ohne zusätzliche vertikale Geschwindigkeit, sodass er einfach herunterfällt.

Das war's für das Gehen. Gehen wir zum Sprungzustand über.

Sprungzustand

Beginnen wir mit der Einstellung einer geeigneten Animation für das Sprite.

Im Sprungzustand müssen wir die Geschwindigkeit des Charakters durch Schwerkraft erhöhen, damit er immer schneller in Richtung Boden geht.

Es wäre jedoch sinnvoll, ein Limit hinzuzufügen, damit der Charakter nicht zu schnell fallen kann.

In vielen Spielen, wenn der Charakter in der Luft ist, nimmt die Manövrierfähigkeit ab, aber wir werden einige sehr einfache und genaue Steuerungen wählen, die volle Flexibilität in der Luft ermöglichen. Wenn wir also die GoLeft- oder GoRight-Taste drücken, bewegt sich der Charakter in die Richtung, während er so schnell springt, als wäre er am Boden. In diesem Fall können wir einfach die Bewegungslogik aus dem Gehzustand kopieren.

Schließlich werden wir den Sprung höher machen, wenn die Sprungtaste länger gedrückt wird. Um das zu tun, machen wir den Sprung tatsächlich niedriger, wenn die Sprungtaste nicht gedrückt wird.

Wie Sie sehen können, wird die Geschwindigkeit auf den Maximalwert von cMinJumpSpeed ​​(200 Pixel pro Sekunde) begrenzt, wenn die Sprungtaste nicht gedrückt wird und die vertikale Geschwindigkeit positiv ist. Das bedeutet, dass, wenn wir nur auf die Sprungtaste tippen, die Sprunggeschwindigkeit nicht gleich mJumpSpeed ​​(standardmäßig 410) ist, sondern auf 200 gesenkt wird und der Sprung daher kürzer ist.

Da wir noch keine Ebenengeometrie haben, sollten wir die GrabLedge-Implementierung vorerst überspringen.

Aktualisieren Sie die vorherigen Eingaben

Sobald der Frame fertig ist, können wir die vorherigen Eingaben aktualisieren. Erstellen wir hierfür eine neue Funktion. Hier müssen Sie lediglich die Schlüsselstatuswerte aus dem Array mInputs in das Array mPrevInputs verschieben.

Ganz am Ende der CharacterUpdate-Funktion müssen wir noch einige Dinge tun. Das erste ist, die Physik zu aktualisieren.

Nachdem die Physik aktualisiert wurde, können wir sehen, ob wir einen Sound abspielen sollten. Wir möchten einen Sound spielen, wenn der Charakter auf eine Oberfläche stößt, aber im Moment kann er nur auf dem Boden aufschlagen, da die Kollision mit der Tilemap noch nicht implementiert ist.

Lassen Sie uns überprüfen, ob der Charakter gerade auf den Boden gefallen ist. Mit dem aktuellen Setup ist dies sehr einfach. Wir müssen nur nachsehen, ob sich der Charakter gerade auf dem Boden befindet, aber nicht im vorherigen Frame.

Zum Schluss aktualisieren wir die vorherigen Eingaben.

Alles in allem sollte die CharacterUpdate-Funktion jetzt so aussehen, mit geringfügigen Unterschieden je nach Art der verwendeten Engine oder des verwendeten Frameworks.

Initiere das Zeichen

Schreiben wir eine Init-Funktion für das Zeichen. Diese Funktion verwendet die Eingabearrays als Parameter. Wir werden diese später aus der Manager-Klasse liefern. Davon abgesehen müssen wir Dinge tun wie:

  • Weisen Sie die Skala zu
  • Weisen Sie die Sprunggeschwindigkeit zu
  • Weisen Sie die Gehgeschwindigkeit zu
  • Stellen Sie die Ausgangsposition ein
  • Stellen Sie den AABB ein

Wir werden hier einige der definierten Konstanten verwenden.

Im Fall der Demo können wir die Anfangsposition auf die Position im Editor setzen.

Für den AABB müssen wir den Versatz und die halbe Größe einstellen. Der Versatz im Sprite der Demo muss nur halb so groß sein.

Jetzt können wir uns um den Rest der Variablen kümmern.

Wir müssen diese Funktion vom Spielmanager aus aufrufen. Der Manager kann auf viele Arten eingerichtet werden, abhängig von den von Ihnen verwendeten Werkzeuge. Im Allgemeinen ist die Idee jedoch dieselbe. In der Init des Managers müssen wir die Eingabearrays erstellen, einen Player erstellen und ihn initiieren.

Zusätzlich müssen wir beim Update des Managers den Player und die Eingaben des Players aktualisieren.

Beachten Sie, dass wir die Physik des Charakters im festen Update aktualisieren. Dadurch wird sichergestellt, dass die Sprünge immer gleich hoch sind, unabhängig davon, mit welcher Framerate unser Spiel arbeitet. Es gibt einen ausgezeichneten Artikel von Glenn Fiedler darüber, wie Sie den Zeitschritt korrigieren können, falls Sie Unity nicht verwenden.

Testen Sie das Zeichen Controller

An diesem Punkt können wir die Bewegung des Zeichens testen und sehen, wie es sich anfühlt. Wenn es uns nicht gefällt, können wir jederzeit die Parameter oder die Art und Weise ändern, wie die Geschwindigkeit bei Tastendruck geändert wird.

An animation of the character controllerAn animation of the character controllerAn animation of the character controller

Zusammenfassung

Die Zeichensteuerung mag für manche sehr schwerelos und nicht so angenehm wie eine auf Momentum basierende Bewegung erscheinen, aber dies ist alles eine Frage der Art der Steuerung, die am besten zu Ihrem Spiel passt. Glücklicherweise ist es ziemlich einfach, die Art und Weise zu ändern, in der sich das Zeichen bewegt. Es reicht aus, zu ändern, wie sich der Geschwindigkeitswert im Geh- und Sprungzustand ändert.

Das war's für den ersten Teil der Serie. Wir haben ein einfaches Bewegungsschema für Zeichen entwickelt, aber nicht viel mehr. Das Wichtigste ist, dass wir den Weg für den nächsten Teil festgelegt haben, in dem wir den Charakter mit einer Tilemap interagieren lassen.

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.