Grundlegende 2D-Plattform-Physik, Teil 5 Erkennung von Objekten vs. Objektkollisionen

In diesem Teil der Serie arbeiten wir daran, dass Objekte nicht nur physisch mit der Tilemap allein, sondern auch mit jedem anderen Objekt interagieren können, indem ein Kollisionserkennungsmechanismus zwischen den Spielobjekten implementiert wird.

Demo

Die Demo zeigt das Endergebnis dieses Tutorials. Verwenden Sie WASD, um den Charakter zu verschieben. Die mittlere Maustaste erzeugt eine unidirektionale Plattform, die rechte Maustaste eine durchgehende Kachel und die Leertaste erzeugt einen Charakterklon. Die Schieberegler ändern die Größe des Charakters des Spielers. Die Objekte, die eine Kollision erkennen, werden halbtransparent gemacht. 

Die Demo wurde unter Unity 5.4.0f3 veröffentlicht, und der Quellcode ist auch mit dieser Unity-Version kompatibel.

Kollisionserkennung

Bevor wir über jede Art von Kollisionsreaktion sprechen, z. B. indem es den Objekten unmöglich macht, sich gegenseitig zu durchlaufen, müssen wir zunächst wissen, ob diese speziellen Objekte überlappen. 

Dies kann eine sehr teure Operation sein, wenn wir einfach jedes Objekt gegen jedes andere Objekt im Spiel prüfen, je nachdem, wie viele aktive Objekte das Spiel aktuell verarbeiten muss. Um den schlechten Prozessor unserer Spieler ein wenig zu lindern, verwenden wir…

Raumaufteilung!

Dies ist im Wesentlichen eine Aufteilung des Spiels in kleinere Bereiche, sodass wir die Kollisionen zwischen Objekten überprüfen können, die nur zu demselben Bereich gehören. Diese Optimierung ist in Spielen wie Terraria dringend erforderlich, wo die Welt und die Anzahl möglicher kollidierender Objekte riesig sind und die Objekte nur spärlich platziert werden. Bei Spielen mit einem Bildschirm, bei denen die Anzahl der Objekte durch die Größe des Bildschirms stark eingeschränkt ist, ist dies häufig nicht erforderlich, aber dennoch nützlich.

Die Methode

Die am weitesten verbreitete räumliche Partitionierungsmethode für den 2D-Raum ist der Quad-Baum. Sie finden die Beschreibung in diesem Tutorial. Für meine Spiele verwende ich eine flache Struktur, was im Wesentlichen bedeutet, dass der Spielraum in Rechtecke einer bestimmten Größe aufgeteilt ist, und ich suche nach Kollisionen mit Objekten, die sich in demselben rechteckigen Raum befinden.

Hier gibt es eine Nuance: Ein Objekt kann sich in mehr als einem Teilraum auf einmal befinden. Das ist völlig in Ordnung - es bedeutet nur, dass wir Objekte ermitteln müssen, die zu den Partitionen gehören, mit denen sich unser früheres Objekt überschneidet.

Daten für die Partitionierung

Die Basis ist einfach. Wir müssen wissen, wie groß jede Zelle sein soll, und ein zweidimensionales Array, in dem jedes Element eine Liste von Objekten ist, die sich in einem bestimmten Bereich befinden. Wir müssen diese Daten in die Map-Klasse einfügen.

public int mGridAreaWidth = 16; public int mGridAreaHeight = 16; öffentliche Liste[,] mObjectsInArea;

In unserem Fall habe ich beschlossen, die Größe der Partition in Kacheln auszudrücken. Daher ist jede Partition 16 mal 16 Kacheln groß. 

Für unsere Objekte benötigen wir eine Liste der Bereiche, mit denen sich das Objekt aktuell überlappt, sowie den Index in jeder Partition. Fügen wir diese zum hinzu MovingObject Klasse.

öffentliche Liste mAreas = neue Liste(); öffentliche Liste mIdsInAreas = neue Liste();

Anstelle von zwei Listen könnten wir ein einzelnes Wörterbuch verwenden, aber der Performance-Aufwand bei der Verwendung komplexer Container in der aktuellen Iteration von Unity lässt zu wünschen übrig. Daher bleiben wir bei den Listen für die Demo.

Partitionen initialisieren

Fahren wir fort, um zu berechnen, wie viele Partitionen wir benötigen, um den gesamten Kartenbereich abzudecken. Die Annahme hier ist, dass kein Objekt außerhalb der Kartengrenzen schweben kann. 

mHorizontalAreasCount = Mathf.CeilToInt ((float) mWidth / (float) mGridAreaWidth); mVerticalAreasCount = Mathf.CeilToInt ((float) mHohe / (Float) mGridAreaHeight);

Abhängig von der Kartengröße müssen die Partitionen natürlich nicht genau mit den Kartengrenzen übereinstimmen. Aus diesem Grund verwenden wir eine Obergrenze von berechneten Werten, um sicherzustellen, dass wir zumindest über die gesamte Karte verfügen.

Lassen Sie uns jetzt die Partitionen einleiten.

mObjectsInArea = neue Liste[mHorizontalAreasCount, mVerticalAreasCount]; für (var y = 0; y < mVerticalAreasCount; ++y)  for (var x = 0; x < mHorizontalAreasCount; ++x) mObjectsInArea[x, y] = new List(); 

Hier passiert nichts Besonderes - wir sorgen nur dafür, dass in jeder Zelle eine Liste von Objekten zur Verfügung steht, die bearbeitet werden können.

Weisen Sie die Partitionen des Objekts zu

Jetzt ist es Zeit, eine Funktion zu erstellen, die die Bereiche aktualisiert, die ein bestimmtes Objekt überlappt.

public void UpdateAreas (MovingObject obj) 

Zunächst müssen wir wissen, mit welchen Kartenkacheln sich das Objekt überlappt. Da wir nur AABBs verwenden, müssen wir nur prüfen, auf welcher Kachel jede Ecke der AABB landet.

var topLeft = GetMapTileAtPoint (obj.mAABB.center + new Vector2 (-obj.mAABB.HalfSize.x, obj.mAABB.HalfSizeY)); var topRight = GetMapTileAtPoint (obj.mAABB.center + obj.mAABB.HalfSize); var bottomLeft = GetMapTileAtPoint (obj.mAABB.center - obj.mAABB.HalfSize); var bottomRight = new Vector2i ();

Um die Koordinate im partitionierten Raum zu erhalten, müssen wir nur die Kachelposition durch die Größe der Partition teilen. Wir müssen die Partition der rechten unteren Ecke jetzt nicht berechnen, da ihre x-Koordinate der rechten oberen Ecke und die y-Koordinate der linken unteren Ecke entspricht.

topLeft.x / = mGridAreaWidth; topLeft.y / = mGridAreaHeight; topRight.x / = mGridAreaWidth; topRight.y / = mGridAreaHeight; bottomLeft.x / = mGridAreaWidth; bottomLeft.y / = mGridAreaHeight; bottomRight.x = topRight.x; bottomRight.y = bottomLeft.y;

Dies alles sollte auf der Annahme basieren, dass kein Objekt außerhalb der Kartengrenzen verschoben wird. Andernfalls müssten wir hier eine zusätzliche Überprüfung durchführen, um die Objekte zu ignorieren, die außerhalb der Grenzen liegen. 

Nun ist es möglich, dass sich das Objekt vollständig in einer einzigen Partition befindet, in zwei oder in dem Bereich, in dem sich vier Partitionen treffen. Es wird davon ausgegangen, dass kein Objekt größer als die Partitionsgröße ist. In diesem Fall könnte es die gesamte Map und alle Partitionen einnehmen, wenn sie groß genug wäre! Ich habe unter dieser Annahme gearbeitet, so werden wir im Tutorial damit umgehen. Die Modifikationen für das Zulassen größerer Objekte sind jedoch ziemlich unbedeutend, daher werde ich sie auch erklären.

Beginnen wir mit der Prüfung, mit welchen Bereichen sich der Charakter überlappt. Wenn alle Partitionskoordinaten der Ecke gleich sind, nimmt das Objekt nur einen einzigen Bereich ein.

if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft); 

Wenn dies nicht der Fall ist und die Koordinaten auf der x-Achse gleich sind, überlappt das Objekt vertikal mit zwei verschiedenen Partitionen.

if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft);  else if (topLeft.x == topRight.x) mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (bottomLeft); 

Wenn wir Objekte unterstützen, die größer als Partitionen sind, reicht es aus, wenn Sie einfach alle Partitionen von der oberen linken Ecke zur unteren linken Ecke mithilfe einer Schleife hinzufügen.

Die gleiche Logik gilt, wenn nur die vertikalen Koordinaten gleich sind.

if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft);  else if (topLeft.x == topRight.x) mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (bottomLeft);  else if (topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (topRight); 

Wenn alle Koordinaten unterschiedlich sind, müssen wir schließlich alle vier Bereiche hinzufügen.

if (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft);  else if (topLeft.x == topRight.x) mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (bottomLeft);  else if (topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (topRight);  else mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (bottomLeft); mOverlappingAreas.Add (topRight); mOverlappingAreas.Add (bottomRight); 

Bevor wir mit dieser Funktion fortfahren, müssen wir das Objekt zu einer bestimmten Partition hinzufügen und entfernen können. Lassen Sie uns diese Funktionen erstellen, beginnend mit dem Hinzufügen.

public void AddObjectToArea (Vector2i areaIndex, MovingObject obj) var area = mObjectsInArea [areaIndex.x, areaIndex.y]; // speichere den Index des Objektes im Bereich obj.mAreas.Add (areaIndex); obj.mIdsInAreas.Add (area.Count); // füge das Objekt zum Bereich area hinzu.Add (obj); 

Wie Sie sehen, ist die Prozedur sehr einfach - wir fügen den Index des Bereichs zur Liste der überlappenden Bereiche des Objekts hinzu, fügen den entsprechenden Index zur IDs-Liste des Objekts hinzu und fügen das Objekt schließlich der Partition hinzu.

Nun erstellen wir die Entfernungsfunktion.

public void RemoveObjectFromArea (Vector2i areaIndex, int objIndexInArea, MovingObject obj) 

Wie Sie sehen, verwenden wir die Koordinaten des Bereichs, mit dem sich das Zeichen nicht mehr überschneidet, dessen Index in der Objektliste in diesem Bereich und die Referenz auf das Objekt, das Sie entfernen müssen.

Um das Objekt zu entfernen, tauschen wir es mit dem letzten Objekt in der Liste aus. Dazu müssen wir auch sicherstellen, dass der Index des Objekts für diesen bestimmten Bereich auf denjenigen aktualisiert wird, den unser entferntes Objekt hatte. Wenn wir das Objekt nicht vertauschen, müssten wir die Indizes aller Objekte aktualisieren, die hinter demjenigen liegen, den wir entfernen müssen. Stattdessen müssen wir nur den aktualisieren, mit dem wir ausgetauscht wurden. 

Ein Wörterbuch hier zu haben, erspart viel Aufwand, aber das Entfernen des Objekts aus einem Bereich ist weitaus weniger erforderlich als das Durchlaufen des Wörterbuchs. Dies muss bei jedem Objekt für jedes Objekt durchgeführt werden, wenn die Überlappung des Objekts aktualisiert wird Bereiche.

// Tauschen Sie das letzte Element gegen das aus, das Sie entfernen. var tmp = area [area.Count - 1]; area [area.Count - 1] = obj; area [objIndexInArea] = tmp;

Nun müssen wir den betroffenen Bereich in der Bereichsliste des ausgelagerten Objekts suchen und den Index in der IDs-Liste in den Index des entfernten Objekts ändern.

var tmpIds = tmp.mIdsInAreas; var tmpAreas = tmp.mAreas; für (int i = 0; i < tmpAreas.Count; ++i)  if (tmpAreas[i] == areaIndex)  tmpIds[i] = objIndexInArea; break;  

Schließlich können wir das letzte Objekt aus der Partition entfernen. Dies ist jetzt eine Referenz auf das Objekt, das wir entfernen mussten.

area.RemoveAt (area.Count - 1);

Die ganze Funktion sollte so aussehen:

public void RemoveObjectFromArea (Vector2i areaIndex, int objIndexInArea, MovingObject obj) var area = mObjectsInArea [areaIndex.x, areaIndex.y]; // Tauschen Sie das letzte Element gegen das aus, das Sie entfernen. var tmp = area [area.Count - 1]; area [area.Count - 1] = obj; area [objIndexInArea] = tmp; var tmpIds = tmp.mIdsInAreas; var tmpAreas = tmp.mAreas; für (int i = 0; i < tmpAreas.Count; ++i)  if (tmpAreas[i] == areaIndex)  tmpIds[i] = objIndexInArea; break;   //remove the last item area.RemoveAt(area.Count - 1); 

Gehen wir zurück zur UpdateAreas-Funktion.

Wir wissen, in welchen Bereichen der Charakter diesen Frame überlappt, aber das letzte Frame, dem das Objekt bereits zugewiesen wurde, konnte demselben oder verschiedenen Bereichen zugewiesen worden sein. Lassen Sie uns zunächst die alten Bereiche durchlaufen. Wenn sich das Objekt nicht mehr mit ihnen überlappt, entfernen Sie das Objekt aus diesen Bereichen.

var areas = obj.mAreas; var ids = obj.mIdsInAreas; für (int i = 0; i < areas.Count; ++i)  if (!mOverlappingAreas.Contains(areas[i]))  RemoveObjectFromArea(areas[i], ids[i], obj); //object no longer has an index in the area areas.RemoveAt(i); ids.RemoveAt(i); --i;  

Lassen Sie uns nun die neuen Bereiche durchgehen, und wenn Ihnen das Objekt noch nicht zugewiesen wurde, fügen Sie sie jetzt hinzu.

für (var i = 0; i < mOverlappingAreas.Count; ++i)  var area = mOverlappingAreas[i]; if (!areas.Contains(area)) AddObjectToArea(area, obj); 

Löschen Sie schließlich die Liste der überlappenden Bereiche, damit das nächste Objekt verarbeitet werden kann.

mOverlappingAreas.Clear ();

Das ist es! Die endgültige Funktion sollte so aussehen:

public void UpdateAreas (MovingObject obj) // Die Bereiche an den Ecken der Aabb abrufen var topLeft = GetMapTileAtPoint (obj.mAABB.center + neuer Vector2 (-obj.mAABB.HalfSize.x, obj.mAABB.HalfSizeY)); var topRight = GetMapTileAtPoint (obj.mAABB.center + obj.mAABB.HalfSize); var bottomLeft = GetMapTileAtPoint (obj.mAABB.center - obj.mAABB.HalfSize); var bottomRight = new Vector2i (); topLeft.x / = mGridAreaWidth; topLeft.y / = mGridAreaHeight; topRight.x / = mGridAreaWidth; topRight.y / = mGridAreaHeight; bottomLeft.x / = mGridAreaWidth; bottomLeft.y / = mGridAreaHeight; bottomRight.x = topRight.x; bottomRight.y = bottomLeft.y; // sehen, wie viele verschiedene Bereiche wir haben, wenn (topLeft.x == topRight.x && topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft);  else if (topLeft.x == topRight.x) mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (bottomLeft);  else if (topLeft.y == bottomLeft.y) mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (topRight);  else mOverlappingAreas.Add (topLeft); mOverlappingAreas.Add (bottomLeft); mOverlappingAreas.Add (topRight); mOverlappingAreas.Add (bottomRight);  var areas = obj.mAreas; var ids = obj.mIdsInAreas; für (int i = 0; i < areas.Count; ++i)  if (!mOverlappingAreas.Contains(areas[i]))  RemoveObjectFromArea(areas[i], ids[i], obj); //object no longer has an index in the area areas.RemoveAt(i); ids.RemoveAt(i); --i;   for (var i = 0; i < mOverlappingAreas.Count; ++i)  var area = mOverlappingAreas[i]; if (!areas.Contains(area)) AddObjectToArea(area, obj);  mOverlappingAreas.Clear(); 

Kollision zwischen Objekten erkennen

Zunächst einmal müssen wir unbedingt anrufen UpdateAreas auf allen Spielobjekten. Wir können dies in der Hauptaktualisierungsschleife nach jedem Aktualisierungsaufruf jedes einzelnen Objekts tun.

void FixedUpdate () für (int i = 0; i < mObjects.Count; ++i)  switch (mObjects[i].mType)  case ObjectType.Player: case ObjectType.NPC: ((Character)mObjects[i]).CustomUpdate(); mMap.UpdateAreas(mObjects[i]); break;   

Bevor wir eine Funktion erstellen, in der wir alle Kollisionen prüfen, erstellen wir eine Struktur, die die Daten der Kollision enthält. 

Dies ist sehr nützlich, da wir die Daten so wie zum Zeitpunkt der Kollision erhalten können. Wenn wir jedoch nur den Verweis auf ein Objekt speichern, mit dem wir kollidieren, haben wir nicht nur zu wenig Arbeit, um damit zu arbeiten. Aber auch die Position und andere Variablen könnten sich für dieses Objekt geändert haben, bevor die Kollision in der Aktualisierungsschleife des Objekts tatsächlich verarbeitet wird.

public struct CollisionData public CollisionData (Andere MovingObject, Vector2-Überlappung = Standard (Vector2), Vector2-Geschwindigkeit1 = Standard (Vector2), Vector2-Geschwindigkeit2 = Standard (Vector2), Vector2 oldPos1 = Standard (Vector2), Vector2 oldPos2 = Standard (Vector2), Vector2 pos1 = Standard (Vector2), Vector2 Pos2 = Standard (Vector2)) this.other = other; this.overlap = Überlappung; this.speed1 = speed1; this.speed2 = speed2; this.oldPos1 = oldPos1; this.oldPos2 = oldPos2; this.pos1 = pos1; this.pos2 = pos2;  public MovingObject Sonstiges; öffentliche Vector2-Überlappung; public Vector2 speed1, speed2; public Vector2 oldPos1, oldPos2, Pos1, Pos2; 

Die Daten, die wir speichern, sind der Bezug auf das Objekt, mit dem wir kollidiert haben, die Überlappung, die Geschwindigkeit beider Objekte zum Zeitpunkt der Kollision, ihre Positionen und auch ihre Positionen kurz vor dem Kollisionszeitpunkt.

Gehen wir zum MovingObject klassifizieren und erstellen Sie einen Container für die neu erstellten Kollisionsdaten, die wir erkennen müssen.

öffentliche Liste mAllCollidingObjects = neue Liste();

Gehen wir jetzt zurück zum Karte Klasse und erstellen Sie eine CheckCollisions Funktion. Dies ist unsere Hochleistungsfunktion, bei der wir die Kollisionen zwischen allen Spielobjekten erkennen.

public void CheckCollisions () 

Um die Kollisionen zu erkennen, werden wir alle Partitionen durchlaufen.

für (int y = 0; y < mVerticalAreasCount; ++y)  for (int x = 0; x < mHorizontalAreasCount; ++x)  var objectsInArea = mObjectsInArea[x, y];  

Für jede Partition werden wir jedes Objekt innerhalb der Partition durchlaufen.

für (int y = 0; y < mVerticalAreasCount; ++y)  for (int x = 0; x < mHorizontalAreasCount; ++x)  var objectsInArea = mObjectsInArea[x, y]; for (int i = 0; i < objectsInArea.Count - 1; ++i)  var obj1 = objectsInArea[i];   

Für jedes Objekt überprüfen wir jedes andere Objekt, das sich weiter unten in der Liste in der Partition befindet. Auf diese Weise prüfen wir jede Kollision nur einmal.

für (int y = 0; y < mVerticalAreasCount; ++y)  for (int x = 0; x < mHorizontalAreasCount; ++x)  var objectsInArea = mObjectsInArea[x, y]; for (int i = 0; i < objectsInArea.Count - 1; ++i)  var obj1 = objectsInArea[i]; for (int j = i + 1; j < objectsInArea.Count; ++j)  var obj2 = objectsInArea[j];    

Jetzt können wir überprüfen, ob sich die AABBs der Objekte überlappen.

Vector2 Überlappung; für (int y = 0; y < mVerticalAreasCount; ++y)  for (int x = 0; x < mHorizontalAreasCount; ++x)  var objectsInArea = mObjectsInArea[x, y]; for (int i = 0; i < objectsInArea.Count - 1; ++i)  var obj1 = objectsInArea[i]; for (int j = i + 1; j < objectsInArea.Count; ++j)  var obj2 = objectsInArea[j]; if (obj1.mAABB.OverlapsSigned(obj2.mAABB, out overlap))      

Folgendes passiert in den AABBs OverlapsSigned Funktion.

public bool OverlapsSigned (AABB other, Überschneidung von Vector2) overlap = Vector2.zero; if (HalfSizeX == 0.0f || HalfSizeY == 0.0f || other.HalfSizeX == 0.0f || other.HalfSizeY == 0.0f || Mathf.Abs (center.x - other.center.x)> HalfSizeX + other.HalfSizeX || Mathf.Abs (center.y - other.center.y)> HalfSizeY + other.HalfSizeY) geben false zurück; Überlappung = neuer Vector2 (Mathf.Sign (center.x - other.center.x) * ((other.HalfSizeX + HalfSizeX) - Mathf.Abs (center.x - other.center.x)), Mathf.Sign (center .y - other.center.y) * ((other.HalfSizeY + HalfSizeY) - Mathf.Abs (center.y - other.center.y))); wahr zurückgeben; 

Wie Sie sehen, kann die Größe einer AABB auf einer Achse nicht mit Null kollidieren. Die andere Sache, die Sie feststellen konnten, ist, dass die Funktion auch dann zurückgegeben wird, wenn die Überlappung gleich Null ist, da sie die Fälle zurückweist, in denen die Lücke zwischen den AABBs größer als Null ist. Das liegt vor allem daran, dass wir, wenn sich die Objekte berühren und sich nicht überlappen, immer noch die Information haben, dass dies der Fall ist, also brauchen wir dies, um durchzugehen. 

Als letztes berechnen wir, sobald die Kollision erkannt wurde, wie stark sich die AABB mit der anderen AABB überlappt. Die Überlappung ist vorzeichenbehaftet. Wenn sich also die überlappende AABB auf der rechten Seite dieser AABB befindet, ist die Überlappung auf der x-Achse negativ, und wenn sich die andere AABB auf der linken Seite dieser AABB befindet, ist die Überlappung auf der x-Achse positiv. Dadurch wird es später einfacher, die überlappende Position zu verlassen, da wir wissen, in welche Richtung sich das Objekt bewegen soll.

Zurück zu unserem CheckCollisions Funktion, wenn es keine Überlappung gab, ist es das, wir können zum nächsten Objekt gehen, aber wenn eine Überlappung aufgetreten ist, müssen wir die Kollisionsdaten zu beiden Objekten hinzufügen.

if (obj1.mAABB.OverlapsSigned (obj2.mAABB, außerhalb der Überlappung)) obj1.mAllCollidingObjects.Add (neue CollisionData (obj2, überlappen, obj1.mSpeed, obj2.mSpeed, obj1.mOldPosition, obj1.mOldPosition, obj1.mOldPosition, obj1.mOldPosition obj2.mPosition)); obj2.mAllCollidingObjects.Add (neue CollisionData (obj1, -overlap, obj2.mSpeed, obj1.mSpeed, obj2.mOldPosition, obj1.mOldPosition, obj2.mPosition, obj1.mPosition)); 

Um es uns einfacher zu machen, gehen wir davon aus, dass die Einsen (Geschwindigkeit1, Pos1, AltePos1) in der CollisionData-Struktur immer auf den Eigentümer der Kollisionsdaten verweisen, und die Zwei sind die Daten, die sich auf das andere Objekt beziehen. 

Die andere Sache ist, die Überlappung wird aus der Perspektive von obj1 berechnet. Die Überlappung von obj2 muss negiert werden. Wenn sich obj1 nach links bewegen muss, um sich aus der Kollision heraus zu bewegen, muss sich obj2 nach rechts bewegen, um die gleiche Kollision zu verlassen.

Es gibt noch eine kleine Sache, auf die Sie achten müssen, denn wir durchlaufen die Partitionen der Karte und ein Objekt kann in mehreren Partitionen gleichzeitig sein. In unserem Fall können es bis zu vier sein. Möglicherweise werden wir eine Überlappung für dieselbe Partition feststellen zwei Objekte bis zu viermal. 

Um diese Möglichkeit zu beseitigen, prüfen wir einfach, ob wir bereits eine Kollision zwischen zwei Objekten festgestellt haben. Wenn dies der Fall ist, überspringen wir die Iteration.

if (obj1.mAABB.OverlapsSigned (obj2.mAABB, out überlappung) &&! obj1.HasCollisionDataFor (obj2)) obj1.mAllCollidingObjects.Add (neue CollisionData (obj2, Overlap, obj1.mSpeed, objj.j.J. obj2.mOldPosition, obj1.mPosition, obj2.mPosition)); obj2.mAllCollidingObjects.Add (neue CollisionData (obj1, -overlap, obj2.mSpeed, obj1.mSpeed, obj2.mOldPosition, obj1.mOldPosition, obj2.mPosition, obj1.mPosition)); 

Das HasCollisionDataFor Funktion wird wie folgt implementiert.

public bool HasCollisionDataFor (MovingObject other) für (int i = 0; i < mAllCollidingObjects.Count; ++i)  if (mAllCollidingObjects[i].other == other) return true;  return false; 

Es durchläuft einfach alle Kollisionsdatenstrukturen und sucht nach, ob die bereits zu dem Objekt gehören, für das die Kollision geprüft werden soll. 

Im allgemeinen Anwendungsfall sollte dies in Ordnung sein, da wir nicht erwarten, dass ein Objekt mit vielen anderen Objekten kollidiert. Das Durchsehen der Liste wird also schnell gehen. In einem anderen Szenario kann es jedoch besser sein, die Liste von zu ersetzen CollisionData mit einem Wörterbuch, so dass wir, anstatt zu iterieren, sofort feststellen können, ob ein Element bereits vorhanden ist oder nicht. 

Die andere Sache ist, diese Prüfung erspart uns das Hinzufügen mehrerer Kopien derselben Kollision zu derselben Liste. Wenn die Objekte jedoch nicht kollidieren, werden wir sowieso mehrfach auf Überlappung prüfen, ob beide Objekte zu derselben Partition gehören. 

Dies sollte kein großes Problem sein, da die Kollisionsprüfung billig ist und die Situation nicht so üblich ist. Wenn es sich jedoch um ein Problem handelt, könnte die Lösung darin bestehen, einfach eine Matrix von geprüften Kollisionen oder ein zweiseitiges Wörterbuch aufzufüllen Wenn die Kollisionen überprüft werden, setzen Sie sie zurück, bevor Sie das aufrufen CheckCollisions Funktion.

Rufen wir nun die Funktion auf, die wir gerade in der Hauptspielschleife beendet haben.

void FixedUpdate () für (int i = 0; i < mObjects.Count; ++i)  switch (mObjects[i].mType)  case ObjectType.Player: case ObjectType.NPC: ((Character)mObjects[i]).CustomUpdate(); mMap.UpdateAreas(mObjects[i]); mObjects[i].mAllCollidingObjects.Clear(); break;   mMap.CheckCollisions(); 

Das ist es! Jetzt sollten alle unsere Objekte die Daten über die Kollisionen haben.

Um zu testen, ob alles richtig funktioniert, lassen Sie es so aussehen, dass, wenn ein Charakter mit einem Objekt kollidiert, das Sprite des Charakters halbtransparent wird.

Wie Sie sehen, scheint die Erkennung gut zu funktionieren!

Zusammenfassung

Das war es für einen anderen Teil der einfachen 2D-Plattform-Physik-Serie. Es ist uns gelungen, einen sehr einfachen räumlichen Partitionierungsmechanismus zu implementieren und die Kollisionen zwischen den einzelnen Objekten zu erkennen. 

Wenn Sie eine Frage haben, einen Tipp, wie Sie etwas Besseres tun können oder einfach nur eine Meinung zu diesem Tutorial haben, können Sie mich gerne im Kommentarbereich informieren!