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 2

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

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

Ebenengeometrie

Es gibt zwei grundlegende Ansätze zum Erstellen von Platformer-Levels. Eine davon besteht darin, ein Raster zu verwenden und die entsprechenden Kacheln in Zellen zu platzieren, und die andere ist eine Freiformform, in der Sie die Ebenengeometrie jedoch beliebig und überall platzieren können.

Beide Ansätze haben Vor- und Nachteile. Wir werden das Raster verwenden, also schauen wir uns an, welche Vorteile es gegenüber der anderen Methode hat:

  • Bessere Leistung - Die Kollisionserkennung gegen das Gitter ist in den meisten Fällen billiger als gegen lose platzierte Objekte.
  • Erleichtert die Pfadfindung erheblich.
  • Fliesen sind genauer und vorhersehbarer als lose platzierte Objekte, insbesondere wenn Dinge wie zerstörbares Gelände betrachtet werden.

Erstellen einer Kartenklasse

Beginnen wir mit der Erstellung einer Map-Klasse. Es enthält alle kartenspezifischen Daten.

Jetzt müssen wir alle Kacheln definieren, die die Karte enthält, aber bevor wir das tun, müssen wir wissen, welche Kacheltypen in unserem Spiel existieren. Derzeit planen wir nur drei: eine leere Kachel, eine feste Kachel und eine Einwegplattform.

In der Demo entsprechen die Kacheltypen direkt der Art der Kollision, die wir mit einer Kachel haben möchten, aber in einem echten Spiel ist dies nicht unbedingt der Fall. Da Sie mehr visuell unterschiedliche Kacheln haben, ist es besser, neue Typen wie GrassBlock, GrassOneWay usw. hinzuzufügen, damit die TileType-Enumeration nicht nur den Kollisionstyp, sondern auch das Erscheinungsbild der Kachel definiert.

Jetzt können wir in der Kartenklasse ein Array von Kacheln hinzufügen.

Natürlich ist eine Kachelkarte, die wir nicht sehen können, für uns nicht von großem Nutzen. Daher benötigen wir auch Sprites, um die Kacheldaten zu sichern. Normalerweise ist es in Unity äußerst ineffizient, wenn jede Kachel ein separates Objekt ist. Da wir dies jedoch nur zum Testen unserer Physik verwenden, ist es in der Demo in Ordnung, dies so zu machen.

Die Karte benötigt auch eine Position im Weltraum, sodass wir sie auseinander bewegen können, wenn wir mehr als nur eine einzige benötigen.

Breite und Höhe in Fliesen.

Und die Kachelgröße: In der Demo arbeiten wir mit einer relativ kleinen Kachelgröße, die 16 x 16 Pixel beträgt.

Das wäre es. Jetzt benötigen wir einige Hilfsfunktionen, damit wir problemlos auf die Kartendaten zugreifen können. Beginnen wir mit einer Funktion, die Weltkoordinaten in die Kachelkoordinaten der Karte konvertiert.

Wie Sie sehen können, verwendet diese Funktion einen Vektor2 als Parameter und gibt einen Vektor2i zurück, bei dem es sich im Grunde um einen 2D-Vektor handelt, der mit Ganzzahlen anstelle von Gleitkommazahlen arbeitet.

Das Konvertieren der Weltposition in die Kartenposition ist sehr einfach. Wir müssen lediglich point um mPosition verschieben, damit wir die Kachel relativ zur Kartenposition zurückgeben und das Ergebnis durch die Kachelgröße dividieren.

Beachten Sie, dass wir point zusätzlich um cTileSize/2.0f verschieben mussten, da sich der Drehpunkt der Kachel in der Mitte befindet. Lassen Sie uns auch zwei zusätzliche Funktionen ausführen, die nur die X- und Y-Komponente der Position im Kartenraum zurückgeben. Es wird später nützlich sein.

Wir sollten auch eine komplementäre Funktion schaffen, die bei gegebener Kachel ihre Position im Weltraum zurückgibt.

Neben der Übersetzung von Positionen benötigen wir auch einige Funktionen, um festzustellen, ob eine Kachel an einer bestimmten Position leer, eine feste Kachel oder eine Einwegplattform ist. Beginnen wir mit einer sehr generischen GetTile-Funktion, die einen Typ einer bestimmten Kachel zurückgibt.

Wie Sie sehen können, prüfen wir vor der Rückgabe des Kacheltyps, ob die angegebene Position außerhalb der Grenzen liegt. Wenn dies der Fall ist, möchten wir es als festen Block behandeln, andernfalls geben wir einen wahren Typ zurück.

Die nächste Warteschlange ist eine Funktion, mit der überprüft wird, ob eine Kachel ein Hindernis darstellt.

Auf die gleiche Weise wie zuvor prüfen wir, ob die Kachel außerhalb der Grenzen liegt, und wenn dies der Fall ist, geben wir true zurück, sodass jede Kachel außerhalb der Grenzen als Hindernis behandelt wird.

Lassen Sie uns nun prüfen, ob es sich bei der Kachel um eine gemahlene Kachel handelt. Wir können sowohl auf einer Block- als auch auf einer Einwegplattform stehen, daher müssen wir true zurückgeben, wenn die Kachel eine dieser beiden ist.

Zum Schluss fügen wir die Funktionen IsOneWayPlatform und IsEmpty auf dieselbe Weise hinzu.

Das ist alles, was wir von unserer Kartenklasse brauchen. Jetzt können wir weitermachen und die Charakterkollision dagegen implementieren.

Character-Map-Kollision

Kehren wir zur MovingObject-Klasse zurück. Wir müssen einige Funktionen erstellen, die erkennen, ob das Zeichen mit der Tilemap kollidiert.

Die Methode, mit der wir wissen, ob der Charakter mit einer Kachel kollidiert oder nicht, ist sehr einfach. Wir werden alle Kacheln überprüfen, die direkt außerhalb des AABB des sich bewegenden Objekts vorhanden sind.


Das gelbe Kästchen stellt den AABB des Charakters dar und wir werden die Kacheln entlang der roten Linien überprüfen. Wenn sich eine dieser Variablen mit einer Kachel überschneidet, setzen wir eine entsprechende Kollisionsvariable auf true (z. B. mOnGround, mPushesLeftWall, mAtCeiling oder mPushesRightWall).

Beginnen wir mit der Erstellung einer Funktion HasGround, die prüft, ob das Zeichen mit einer Grundkachel kollidiert.

Diese Funktion gibt true zurück, wenn sich das Zeichen mit einer der unteren Kacheln überschneidet. Es nimmt die alte Position, die aktuelle Position und die aktuelle Geschwindigkeit als Parameter und gibt auch die Y-Position der Oberseite der Kachel zurück, mit der wir kollidieren, und ob die kollidierte Kachel eine Einwegplattform ist oder nicht.

Als erstes wollen wir das Zentrum von AABB berechnen.

Nachdem wir das haben, müssen wir für die Überprüfung der unteren Kollision den Anfang und das Ende der unteren Sensorlinie berechnen. Die Sensorlinie befindet sich nur ein Pixel unter der unteren Kontur des AABB.

bottomLeft und bottomRight repräsentieren die beiden Enden des Sensors. Nachdem wir diese haben, können wir berechnen, welche Kacheln wir überprüfen müssen. Beginnen wir mit der Erstellung einer Schleife, in der wir die Kacheln von links nach rechts durchlaufen.

Beachten Sie, dass es hier keine Bedingung gibt, um die Schleife zu verlassen - das tun wir am Ende der Schleife.

Das erste, was wir in der Schleife tun sollten, ist sicherzustellen, dass die checkedTile.x nicht größer als das rechte Ende des Sensors ist. Dies kann der Fall sein, weil wir den markierten Punkt um ein Vielfaches der Kachelgröße verschieben. Wenn das Zeichen beispielsweise 1,5 Kacheln breit ist, müssen wir die Kachel am linken Rand des Sensors und dann eine Kachel nach rechts überprüfen und dann 1,5 Kacheln rechts statt 2.

Jetzt müssen wir die Kachelkoordinate im Kartenbereich abrufen, um den Kacheltyp überprüfen zu können.

Berechnen wir zunächst die oberste Position der Kachel.

Wenn die aktuell überprüfte Kachel ein Hindernis darstellt, können wir leicht true zurückgeben.

Lassen Sie uns abschließend prüfen, ob wir bereits alle Kacheln durchgesehen haben, die sich mit dem Sensor schneiden. Wenn dies der Fall ist, können wir die Schleife sicher verlassen. Nachdem wir die Schleife verlassen haben und keine Kachel gefunden haben, mit der wir kollidiert sind, müssen wir false zurückgeben, um den Anrufer wissen zu lassen, dass sich unter dem Objekt kein Grund befindet.

Das ist die grundlegendste Version des Schecks. Lassen Sie uns versuchen, es jetzt zum Laufen zu bringen. Zurück in der UpdatePhysics-Funktion sieht unsere alte Bodenprüfung so aus.

Ersetzen wir es mit der neu erstellten Methode. Wenn der Charakter herunterfällt und wir auf unserem Weg ein Hindernis gefunden haben, müssen wir es aus der Kollision herausbewegen und den mOnGround auf true setzen. Beginnen wir mit der Bedingung.

Wenn die Bedingung erfüllt ist, müssen wir den Charakter oben auf das Plättchen verschieben, mit dem wir kollidiert sind.

Wie Sie sehen können, ist dies sehr einfach, da die Funktion das Bodenniveau zurückgibt, an dem das Objekt ausgerichtet werden soll. Danach müssen wir nur noch die vertikale Geschwindigkeit auf Null und mOnGround auf true setzen.

Wenn unsere vertikale Geschwindigkeit größer als Null ist oder wir keinen Boden berühren, müssen wir den mOnGround auf false setzen.

Nun wollen wir sehen, wie das funktioniert.

Wie Sie sehen können, funktioniert es gut! Die Kollisionserkennung für die Wände auf beiden Seiten und am oberen Rand des Charakters ist immer noch nicht vorhanden, aber der Charakter stoppt jedes Mal, wenn er auf den Boden trifft. Wir müssen noch etwas mehr Arbeit in die Kollisionsprüfungsfunktion stecken, um sie robust zu machen.

Eines der Probleme, die wir lösen müssen, ist sichtbar, wenn der Versatz des Charakters von einem Bild zum anderen zu groß ist, um die Kollision richtig zu erkennen. Dies ist im folgenden Bild dargestellt.

Diese Situation tritt jetzt nicht auf, da wir die maximale Fallgeschwindigkeit auf einen vernünftigen Wert festgelegt und die Physik mit einer Frequenz von 60 FPS aktualisiert haben, sodass die Positionsunterschiede zwischen den Frames eher gering sind. Mal sehen, was passiert, wenn wir die Physik nur 30 Mal pro Sekunde aktualisieren.

Wie Sie sehen können, schlägt uns in diesem Szenario unsere Bodenkollisionsprüfung fehl. Um dies zu beheben, können wir nicht einfach überprüfen, ob der Charakter an der aktuellen Position unter ihm Boden hat, sondern wir müssen eher sehen, ob es auf dem Weg von der Position des vorherigen Frames irgendwelche Hindernisse gab.

Kehren wir zu unserer HasGround-Funktion zurück. Hier möchten wir neben der Berechnung der Mitte auch die Mitte des vorherigen Frames berechnen.

Wir müssen auch die Sensorposition des vorherigen Frames ermitteln.

Jetzt müssen wir berechnen, bei welcher Kachel wir vertikal prüfen, ob es eine Kollision gibt oder nicht, und an welcher Stelle wir anhalten werden.

Wir starten die Suche von der Kachel an der Sensorposition des vorherigen Frames und beenden sie an der Sensorposition des aktuellen Frames. Das liegt natürlich daran, dass wir beim Überprüfen auf eine Bodenkollision davon ausgehen, dass wir herunterfallen, und das bedeutet, dass wir uns von der höheren Position zur niedrigeren bewegen.

Schließlich brauchen wir eine weitere Iterationsschleife. Bevor wir den Code für diese äußere Schleife füllen, betrachten wir das folgende Szenario.

Hier sehen Sie einen Pfeil, der sich schnell bewegt. Dieses Beispiel zeigt, dass wir nicht nur alle Kacheln durchlaufen müssen, die wir vertikal passieren müssten, sondern auch die Position des Objekts für jede Kachel interpolieren müssen, die wir durchlaufen, um den Pfad von der Position des vorherigen Rahmens zur aktuellen zu approximieren. Wenn wir einfach die Position des aktuellen Objekts weiter verwenden würden, würde im obigen Fall eine Kollision erkannt, obwohl dies nicht der Fall sein sollte.

Benennen wir bottomLeft und bottomRight in newBottomLeft und newBottomRight um, damit wir wissen, dass dies die Sensorpositionen des neuen Frames sind.

Lassen Sie uns nun innerhalb dieser neuen Schleife die Sensorpositionen interpolieren, sodass wir am Anfang der Schleife davon ausgehen, dass sich der Sensor an der Position des vorherigen Frames befindet und am Ende an der Position des aktuellen Frames.

Beachten Sie, dass wir die Vektoren basierend auf der Differenz der Kacheln auf der Y-Achse interpolieren. Wenn sich alte und neue Positionen innerhalb derselben Kachel befinden, ist der vertikale Abstand Null. In diesem Fall können wir nicht durch den Abstand dividieren. Um dieses Problem zu lösen, möchten wir, dass der Abstand einen Mindestwert von 1 hat. Wenn also ein solches Szenario eintreten sollte (und es wird sehr häufig vorkommen), verwenden wir einfach die neue Position für die Kollisionserkennung.

Schließlich müssen wir für jede Iteration denselben Code ausführen, den wir bereits für die Überprüfung der Bodenkollision entlang der Breite des Objekts ausgeführt haben.

Das war's so ziemlich. Wie Sie sich vielleicht vorstellen können, kann diese Art der Kollisionsprüfung viel teurer sein, wenn sich die Objekte des Spiels sehr schnell bewegen. Sie versichert uns jedoch auch, dass es keine seltsamen Störungen bei Objekten gibt, die sich durch feste Wände bewegen.

Zusammenfassung

Puh, das war mehr Code als wir dachten, nicht wahr? Wenn Sie Fehler oder mögliche Verknüpfungen entdecken, lassen Sie es mich und alle in den Kommentaren wissen! Die Kollisionsprüfung sollte robust genug sein, damit wir uns keine Sorgen über unglückliche Ereignisse machen müssen, bei denen Objekte durch die Blöcke der Tilemap rutschen.

Ein Großteil des Codes wurde geschrieben, um sicherzustellen, dass keine Objekte mit hoher Geschwindigkeit durch die Kacheln laufen. Wenn dies jedoch für ein bestimmtes Spiel kein Problem darstellt, können wir den zusätzlichen Code sicher entfernen, um die Leistung zu steigern. Es könnte sogar eine gute Idee sein, ein Flag für bestimmte sich schnell bewegende Objekte zu haben, damit nur diejenigen die teureren Versionen der Schecks verwenden.

Wir haben noch viel zu tun, aber wir haben es geschafft, eine zuverlässige Kollisionsprüfung für den Boden durchzuführen, die sich ziemlich einfach in die anderen drei Richtungen spiegeln lässt. Das machen wir im nächsten Teil.

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.