Kollisionserkennungsprobleme mit AABBs

8

Ich habe eine einfache Kollisionserkennungsroutine unter Verwendung von AABBs zwischen meinem Hauptspiel-Sprite und verschiedenen Plattformen implementiert (siehe Code unten). Es funktioniert großartig, aber ich führe jetzt die Schwerkraft ein, um meinen Charakter fallen zu lassen, und es zeigt sich einige Probleme mit meiner CD-Routine.

Ich denke, der Kern der Sache ist, dass meine CD-Routine das Sprite entlang der Achse zurückbewegt, in der es am wenigsten in das andere Sprite eingedrungen ist . Wenn es sich also mehr in der Y-Achse als in der X-Achse befindet, wird es entlang der X-Achse zurückbewegt.

Jetzt fällt mein (Helden-) Sprite jedoch mit einer Geschwindigkeit von 30 Pixel pro Frame (es ist ein Bildschirm mit einer Höhe von 1504 - ich muss so schnell fallen, da ich versuchen möchte, die "normale" Schwerkraft zu simulieren, jedes langsamere sieht einfach komisch aus ) Ich bekomme diese Probleme. Ich werde versuchen, mit ein paar Bildern zu zeigen, was passiert (und was meiner Meinung nach es verursacht - obwohl ich nicht sicher bin): (Code unter den Bildern).

Ich würde mich über einige Vorschläge freuen, wie Sie dieses Problem umgehen können.

Geben Sie hier die Bildbeschreibung ein

Zur Verdeutlichung ist es im obigen rechten Bild vielleicht etwas irreführend, wenn ich sage, dass die Position "falsch" korrigiert wurde. Der Code selbst funktioniert korrekt für die Art und Weise, wie er geschrieben wurde, oder anders ausgedrückt, der Algorithmus selbst funktioniert, wenn er sich so verhält, wie ich es erwarten würde, aber ich muss sein Verhalten ändern, um dieses Problem zu verhindern. Ich hoffe, das verdeutlicht meine Kommentare im Bild .

Geben Sie hier die Bildbeschreibung ein

Mein Code

public boolean heroWithPlatforms(){

    //Set Hero center for this frame
    heroCenterX  = hero.xScreen+(hero.quadWidth/2);
    heroCenterY  = hero.yScreen+(hero.quadHeight/2);

    //Iterate through all platforms to check for collisions
    for(x=0;x<platformCoords.length;x+=2){

    //Set platform Center for this iteration
    platformCenterX = platformCoords[x]+(platforms.quadWidth/2);
    platformCenterY = platformCoords[x+1]+(platforms.quadHeight/2);

    // the Dif variables are the difference (absolute value)
    // of the center of the two sprites being compared (one for X axis difference
    //and on for the Y axis difference)
    difX = Math.abs(heroCenterX-platformCenterX);
    difY = Math.abs(heroCenterY-platformCenterY);

    //If 1
    //Check the difference between the 2 centers and compare it to the vector (the sum of
    //the two sprite's widths/2.  If it is smaller, then the sprites are pverlapping along
    //the X axis and we can now proceed to check the Y axis 
    if (difX<vecXPlatform){

    //If 2
    //Same as above but for the Y axis, if this is also true, then we have a collision
    if(difY<vecYPlatform){
        //we now know sprites are colliding, so we now need to know exactly by how much
        //penX will hold the value equal to the amount one sprite (hero, in this case)
        //has overlapped with the other sprite (the platform)
        penX = vecXPlatform-difX;
        penY = vecYPlatform-difY;
        //One sprite has penetrated into the other, mostly in the Y axis, so move sprite
        //back on the X Axis
        if (penX < penY){hero.xScreen-=penX*(heroCenterX-platformCenterX>=0 ? -1 : 1);}
        //Sprite has penetrated into the other, mostly in the X asis, so move sprite
        //back on the Y Axis
        else if (penY < penX) {hero.yScreen-=penY*(heroCenterY-platformCenterY>=0 ? -1 : 1);}
        return true;
        }//Close 'If' 2
        } //Close 'If' 1
    }
    //Otherwise, no collision

    return false;
    }
BungleBonce
quelle
Um es näher zu erläutern, warum bewegen Sie Sprites auf die andere Achse zurück: //One sprite has penetrated into the other, mostly in the Y axis, so move sprite //back on the X Axis
Tom 'Blue' Piddock
Hi @Blue, da das Sprite entlang der am wenigsten durchdringenden Achse korrigiert werden sollte. Wenn das Sprite also die Plattform mehr in der X-Achse als in der Y-Achse durchdrungen hat, sollte das Y korrigiert werden, da es das kleinere der beiden ist.
BungleBonce
Mögliches Duplikat der Kollisionserkennung, Spielerkorrektur
Anko
Ich habe diese Frage vor @Anko gelesen - sie gibt mir nicht die Antworten, die ich brauche. Daher habe ich meine eigene Frage verfasst (nicht überzeugt, dass der Fragesteller genau dasselbe fragt, obwohl es schwer zu sagen ist) :-)
BungleBonce

Antworten:

1

Während meiner Experimente mit HTML5 Canvas und AABB habe ich genau das gefunden, was Sie erleben. Dies geschah, als ich versuchte, eine Plattform aus benachbarten Boxen mit 32 x 32 Pixel zu erstellen.

Lösungen, die ich in der Reihenfolge meiner persönlichen Präferenz versucht habe

1 - Teilen Sie die Bewegungen durch die Achse

Mein aktuelles, und ich denke, ich werde mein Spiel damit fortsetzen. Überprüfen Sie jedoch unbedingt , was ich Phantom AABB nenne, wenn Sie eine schnelle Lösung benötigen, ohne Ihr aktuelles Design stark ändern zu müssen.

Meine Spielwelt hat eine sehr einfache Physik. Es gibt (noch) kein Konzept von Kräften, nur Verschiebung. Die Schwerkraft ist eine konstante Verschiebung nach unten. (Noch) keine Beschleunigung.

Die Verschiebung erfolgt um die Achse. Und darin besteht diese erste Lösung.

Jeder Spielrahmen:

player.moves.y += gravity;
player.moves.x += move_x;

move_x wird gemäß der Eingabeverarbeitung eingestellt. Wenn die Pfeiltaste nicht gedrückt wird, ist move_x = 0. Werte unter Null bedeuten links, andernfalls rechts.

Verarbeiten Sie alle anderen sich bewegenden Sprites und bestimmen Sie die Werte ihrer "Moves" -Komponente. Sie haben auch die "Moves" -Komponente.

Hinweis: Nach der Kollisionserkennung und -reaktion muss die Bewegungskomponente auf Null gesetzt werden, sowohl die x- als auch die y-Eigenschaften, da beim nächsten Häkchen die Schwerkraft erneut hinzugefügt wird. Wenn Sie nicht zurücksetzen, enden Sie mit einer Bewegung, die das Zweifache der gewünschten Schwerkraft beträgt, und im nächsten Tick dreimal.

Geben Sie dann den Code für die Anwendung der Verschiebung und die Kollisionserkennung ein.

Die Bewegungskomponente ist nicht wirklich die aktuelle Position des Sprites. Das Sprite hat sich noch nicht bewegt, auch wenn sich movs.x oder y geändert haben. Die "Moves" -Komponente ist eine Möglichkeit, die Verschiebung zu speichern, die auf die Sprite-Position im richtigen Teil der Spielschleife angewendet werden soll.

Verarbeiten Sie zuerst die y-Achse (move.y). Wende mov.y auf sprite.position.y an. Wenden Sie dann die AABB-Methode an, um festzustellen, ob das sich bewegende bewegte Sprite eine Plattform-AABB überlappt (Sie haben bereits verstanden, wie das geht). Wenn es sich überlappt, bewegen Sie das Sprite durch Anwenden von penetration.y zurück in die y-Achse. Ja, im Gegensatz zu meiner anderen Antwort ignoriert diese von der Entwicklung entwickelte Methode jetzt die Penetration.x für diesen Schritt des Prozesses. Und wir verwenden die Penetration.y, selbst wenn sie größer als die Penetration.x ist, überprüfen wir sie nicht einmal.

Verarbeiten Sie dann die x-Achse (move.x). Wenden Sie move.x auf sprite.position.x an. Und mach das Gegenteil als zuvor. Sie bewegen das Sprite diesmal nur in der horizontalen Achse, indem Sie penetration.x anwenden. Nach wie vor ist es Ihnen egal, ob penetration.x kleiner oder größer als penetration.y ist.

Das Konzept hier ist, dass wenn sich das Sprite nicht bewegt und anfänglich nicht kollidierte, es im nächsten Frame so bleibt (sprite.moves.x und y sind Null). Der Sonderfall, in dem sich ein anderes Spielobjekt auf magische Weise an eine Position teleportiert, die den Sprite-Start überlappt, wird später behandelt.

Wenn sich ein Sprite zu bewegen beginnt. Wenn es sich nur in der x-Achse bewegt, sind wir nur interessiert, wenn es etwas von links oder rechts durchdringt. Da sich das Sprite nicht nach unten bewegt und wir dies wissen, weil die y-Eigenschaft der Bewegungskomponente Null ist, überprüfen wir nicht einmal den berechneten Wert für die Penetration.y, sondern nur bei penetration.x. Wenn eine Penetration in der Bewegungsachse vorhanden ist, wenden wir eine Korrektur an, andernfalls lassen wir das Sprite einfach bewegen.

Was ist mit der diagonalen Verschiebung, nicht unbedingt in einem Winkel von 45 Grad?

Das vorgeschlagene System kann damit umgehen. Sie müssen nur jede Achse einzeln bearbeiten. Während Ihrer Simulation. Auf das Sprite werden unterschiedliche Kräfte ausgeübt. Auch wenn Ihr Motor die Kräfte noch nicht versteht. Diese Kräfte führen zu einer beliebigen Verschiebung in der 2D-Ebene. Diese Verschiebung ist ein 2D-Vektor in jeder Richtung und beliebiger Länge. Aus diesem Vektor können Sie die y-Achse und die x-Achse extrahieren.

Was tun, wenn der Verschiebungsvektor zu groß ist?

Du willst das nicht:

|
|
|
|
|
|
|
|_ _ _ _ _ _ _ _ _

Da unsere neuen Bewegungen, bei denen der Logikprozess angewendet wird, zuerst eine Achse und dann die andere verarbeiten, wird eine große Verschiebung möglicherweise vom Kollisionscode wie ein großes L erkannt. Dadurch werden Kollisionen mit Objekten, die Ihren Verschiebungsvektor schneiden, nicht korrekt erkannt.

Die Lösung besteht darin, große Verschiebungen in kleinen Schritten zu teilen, idealerweise Ihre Sprite-Breite oder -Höhe oder die Hälfte Ihrer Sprite-Breite oder -Höhe. So etwas erhalten:

|_
  |_
    |_
      |_
        |_
          |_
            |_
              |_

Was ist mit dem Teleportieren von Sprites?

Wenn Sie Teleportation als große Verschiebung darstellen und dieselbe Bewegungskomponente wie eine normale Bewegung verwenden, ist dies kein Problem. Wenn Sie dies nicht tun, müssen Sie sich überlegen, wie Sie das Teleportations-Sprite markieren können, das vom Kollisionscode verarbeitet werden soll (Hinzufügen zu einer Liste, die diesem Zweck gewidmet ist?). Während des Antwortcodes können Sie das stehende Sprite, das sich darin befand, direkt verschieben die Art und Weise, als die Teleportation stattfand.

2 - Phantom AABB

Haben Sie einen sekundären AABB für jedes von der Schwerkraft betroffene Sprite, das am unteren Ende des Sprites beginnt, die gleiche Breite des AABB des Sprites hat und 1 Pixel hoch ist.

Die Idee hier ist, dass dieses Phantom-AABB immer mit der Plattform unter dem Sprite kollidiert. Nur wenn sich dieses Phantom-AABB nicht überlappt, darf die Schwerkraft auf das Sprite angewendet werden.

Sie müssen den Penetrationsvektor für das Phantom AABB und die Plattform nicht berechnen. Sie interessieren sich nur für einen einfachen Kollisionstest. Wenn er eine Plattform überlappt, kann die Schwerkraft nicht angewendet werden. Ihr Sprite kann eine boolesche Eigenschaft haben, die angibt, ob es sich über einer Plattform oder in der Luft befindet. Sie sind in der Luft, wenn Ihr Phantom-AABB nicht mit einer Plattform unter dem Player kollidiert.

Diese:

player.position.y += gravity;

Wird so etwas:

if (player.onGround == false) player.position.y += gravity;

Sehr einfach und zuverlässig.

Sie lassen Ihre AABB-Implementierung unverändert.

Der Phantom-AABB verfügt möglicherweise über eine dedizierte Schleife, die diese verarbeitet, nur nach einfachen Kollisionen sucht und keine Zeit mit der Berechnung des Penetrationsvektors verschwendet. Diese Schleife muss vor dem normalen Kollisions- und Antwortcode stehen. Sie können während dieser Schleife die Schwerkraft anwenden, da diese Sprites in der Luft die Schwerkraft anwenden. Wenn das Phantom AABB an sich eine Klasse oder Struktur ist, kann es einen Verweis auf das Sprite haben, zu dem es gehört.

Dies ist der anderen vorgeschlagenen Lösung sehr ähnlich, bei der Sie überprüfen, ob Ihr Spieler springt. Ich gebe eine mögliche Möglichkeit zu erkennen, ob der Spieler seine Füße über einer Plattform hat.

Hatoru Hansou
quelle
Ich mag die Phantom AABB-Lösung hier. Ich habe bereits etwas Ähnliches angewendet, um das Problem zu umgehen. (Aber ohne Phantom-AABB) - Ich bin daran interessiert, mehr über Ihre erste Methode zu erfahren, aber ich bin ein wenig verwirrt darüber. Ist die Moves.x- (und .y-) Komponente im Grunde der Betrag, um den das Sprite verschoben wird (wenn es sich bewegen darf)? Ich bin mir auch nicht sicher, wie es in meiner Situation direkt helfen würde. Wäre dankbar, wenn Sie Ihre Antwort bearbeiten könnten, um eine Grafik zu zeigen, wie sie in meiner Situation helfen könnte (wie in meiner Grafik oben) - danke!
BungleBonce
Über die Verschiebungskomponente. Es stellt eine zukünftige Verschiebung dar, ja, aber Sie überprüfen nicht, ob Sie eine Bewegung zulassen oder nicht. Sie wenden die Bewegung tatsächlich nach Achsen an, zuerst y, dann x, und sehen dann, ob Sie mit etwas kollidiert sind, wenn Sie die Penetration verwenden Vektor zurück zu bewegen. Warum unterscheidet sich das Speichern der Verschiebung für die spätere Verarbeitung von der tatsächlichen Anwendung der Verschiebung, wenn Benutzereingaben zum Verarbeiten vorliegen oder etwas in der Spielwelt passiert ist? Ich werde versuchen, dies in Bilder einzufügen, wenn ich meine Antwort bearbeite. In der Zwischenzeit wollen wir sehen, ob jemand anderes eine Alternative hat.
Hatoru Hansou
Ah OK - ich glaube ich verstehe was du jetzt sagst. Anstatt also x & y zu verschieben und dann die Kollision zu überprüfen, verschieben Sie mit Ihrer Methode zuerst X und dann die Kollision. Wenn sie kollidiert, korrigieren Sie sie entlang der X-Achse. Bewegen Sie dann Y und überprüfen Sie die Kollision. Wenn es eine Kollision gibt, bewegen Sie sich dann entlang der X-Achse? Auf diese Weise kennen wir immer die richtige Aufprallachse? Habe ich das richtig verstanden?! Vielen Dank!
BungleBonce
Warnung: Sie haben geschrieben: "Bewegen Sie dann Y und überprüfen Sie die Kollision. Wenn es eine Kollision gibt, bewegen Sie sich dann entlang der X-Achse?" Seine Y-Achse. Wahrscheinlich ein Tippfehler. Ja, das ist mein aktueller Ansatz. Bei sehr großen Verschiebungen, die größer sind als Ihre Sprite-Breite oder -Höhe, müssen Sie in kleinere Schritte unterteilen, und das meine ich mit der L-Analogie. Achten Sie darauf, dass Sie keine große Verschiebung in der y-Achse und dann ein großes x anwenden. Sie müssen beide in kleine Schritte teilen und interkalieren, um ein korrektes Verhalten zu erzielen. Etwas, das Sie nur interessieren sollten, wenn in Ihrem Spiel große Verschiebungen zulässig sind. In meinem weiß ich, dass sie auftreten werden. Also habe ich mich darauf vorbereitet.
Hatoru Hansou
Brilliant @HatoruHansou, ich habe nie daran gedacht, Dinge so zu machen, ich werde es auf jeden Fall versuchen - vielen Dank für Ihre Hilfe, ich werde Ihre Antwort als akzeptiert markieren. :-)
BungleBonce
5

Ich würde die Plattformkacheln zu einer einzigen Plattformeinheit kombinieren.

Angenommen, Sie nehmen die 3 Kacheln aus den Bildern und kombinieren sie zu einer einzigen Entität. Ihre Entität hätte eine Kollisionsbox mit:

Left   = box1.left
Right  = box3.right
Top    = box1.top
Bottom = box1.bottom

Wenn Sie dies für Kollisionen verwenden, erhalten Sie das y als die am wenigsten durchdringende Achse auf dem größten Teil der Plattform, während Sie dennoch bestimmte Kacheln innerhalb der Plattform haben können.

UnderscoreZero
quelle
Hallo @UnderscoreZero - danke, dies ist ein Weg, der einzige Weg ist, dass ich meine Kacheln derzeit in 3 separaten Arrays speichere, eines für Kacheln am linken Rand, eines für Kacheln am mittleren Rand und eines für Kacheln am rechten Rand - ich denke, ich könnte Kombinieren Sie sie alle zu einem Array für CD-Zwecke. Sie sind sich jedoch nicht sicher, wie ich dazu Code schreiben könnte.
BungleBonce
Eine andere Option, die Sie versuchen könnten, die viele Physik-Engines verwenden, besteht darin, dass das Objekt, sobald es auf einer flachen Ebene gelandet ist, die Schwerkraft für das Objekt deaktiviert, um zu verhindern, dass es versucht, durchzufallen. Sobald das Objekt genügend Kraft erhält, um es von der Ebene wegzuziehen, oder wenn es von der Seite der Ebene fällt, aktivieren sie die Schwerkraft wieder und führen die Physik wie gewohnt durch.
UnderscoreZero
0

Probleme wie diese treten häufig bei neuen Kollisionserkennungsmethoden auf.

Ich weiß nicht viel über Ihr aktuelles Kollisionssetup, aber hier ist, wie es gehen sollte:

Machen Sie zunächst diese Variablen:

  1. Machen Sie eine X- und Y-Geschwindigkeit
  2. Mach eine Schwerkraft
  3. Machen Sie eine Sprunggeschwindigkeit
  4. Machen Sie ein isJumping

Erstellen Sie als Zweites einen Timer, um das Delta zu berechnen, das die Geschwindigkeit des Objekts beeinflusst.

Drittens machen Sie dies:

  1. Überprüfen Sie, ob der Spieler die Sprungtaste drückt und nicht springt. Wenn ja, fügen Sie der Y-Geschwindigkeit die Sprunggeschwindigkeit hinzu.
  2. Subtrahieren Sie die Schwerkraft bei jeder Aktualisierung von der Y-Geschwindigkeit, um die Schwerkraft zu simulieren
  3. Wenn die y + -Höhe des Spielers kleiner oder gleich dem y der Plattform ist, setzen Sie die Y-Geschwindigkeit auf 0 und setzen Sie das Y des Spielers auf das y + der Spielerhöhe der Plattform.

Wenn Sie dies tun, sollte es kein Problem geben. Hoffe das hilft!

LiquidFeline
quelle
Hallo @ user1870398, danke, aber mein Spiel hat im Moment noch nicht einmal Springen. Die Frage bezog sich auf die Probleme, die durch die Verwendung der Methode der am wenigsten durchdringenden Achse zur Korrektur der Position des Sprites entstehen. Das Problem ist, dass die Schwerkraft meinen Charakter entlang der Y-Achse weiter in meine Plattformen hineinzieht als auf der X-Achse. Daher korrigiert meine Routine (wie es für diese Art von Algorithmus korrekt ist) die X-Achse, daher kann ich nicht Bewegen Sie sich entlang der Plattform (da die CD-Routine die einzelnen Kacheln sieht und sie als solche behandelt, obwohl sie als eine Plattform dargestellt werden).
BungleBonce
Ich verstehe dein Problem. Es ist nur so, dass Sie Ihre Bewegung nicht richtig korrigieren. Sie benötigen eine isJumping- Variable. Wenn Sie die Sprungtaste drücken und nicht springen, setzen Sie sie auf wahr. Wenn Sie auf den Boden treffen, setzen Sie sie auf falsch, bewegen Sie das Y des Spielers an die richtige Position ( Plattform.y + Spielerhöhe ) und setzen Sie isJumping auf false. Führen Sie als Nächstes, nachdem Sie nach diesen Dingen gesucht haben, if (left_key) player.velocity.x - = (player.isJumping? Player.speed: player.speed - player.groundFriction) aus . Ich weiß, dass Sie das Springen noch nicht implementiert haben, also haben Sie isJumping einfach immer auf false gesetzt.
LiquidFeline
Ich habe kein Springen, aber wenn eine 'ifJumping'-Variable immer auf false gesetzt ist, wie unterscheidet sich das davon, keine zu haben? (sagen wir in Spielen, in denen es kein Springen gibt) Ich bin nur ein wenig verwirrt darüber, wie ein "springender" Boolescher Wert in meiner Situation helfen könnte - wäre dankbar, wenn Sie etwas mehr erklären könnten - danke!
BungleBonce
Es wird nicht benötigt. Einfach besser implementieren, während Sie es noch programmieren. Der Grund für isJumping ist jedenfalls, Ihren Code zu organisieren und Probleme zu vermeiden, indem Sie nur die Schwerkraft hinzufügen, während der Spieler springt. Tu einfach, was ich empfohlen habe. Diese Methode verwenden die meisten Menschen. Sogar Minecraft benutzt es! So wird die Schwerkraft effektiv simuliert! :)
LiquidFeline