2D Platformer AABB Kollisionsprobleme

51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

Ich habe ein Problem mit der AABB-Kollisionsauflösung.


Ich löse AABB-Schnittpunkte auf, indem ich zuerst die X-Achse und dann die Y-Achse auflöse. Dies geschieht, um diesen Fehler zu vermeiden: http://i.stack.imgur.com/NLg4j.png


Die aktuelle Methode funktioniert einwandfrei, wenn sich ein Objekt in den Player bewegt und der Player horizontal verschoben werden muss. Wie Sie im GIF sehen können, drücken die horizontalen Stacheln den Player richtig.


Wenn sich die vertikalen Stacheln in den Player hineinbewegen, wird die X-Achse immer noch zuerst aufgelöst. Dies macht die "Verwendung der Spikes als Aufzug" unmöglich.

Wenn sich der Spieler in die vertikalen Stacheln bewegt (von der Schwerkraft beeinflusst, fällt er hinein), wird er auf die Y-Achse gedrückt, da es anfangs keine Überlappung auf der X-Achse gab.


Ich habe versucht, die in der ersten Antwort dieses Links beschriebene Methode zu verwenden: 2D-Rechteckobjekt-Kollisionserkennung

Die Spikes und sich bewegenden Objekte bewegen sich jedoch, indem ihre Position geändert wird, nicht die Geschwindigkeit, und ich berechne ihre nächste vorhergesagte Position erst, wenn die Update () -Methode aufgerufen wird. Natürlich hat diese Lösung auch nicht funktioniert. :(


Ich muss die AABB-Kollision so lösen, dass beide oben beschriebenen Fälle wie beabsichtigt funktionieren.

Dies ist mein aktueller Kollisionsquellcode: http://pastebin.com/MiCi3nA1

Ich wäre wirklich dankbar, wenn jemand dies untersuchen könnte, da dieser Fehler von Anfang an in der Engine vorhanden war und ich mich bemüht habe, eine gute Lösung zu finden, ohne Erfolg. Dies bringt mich dazu, Nächte damit zu verbringen, mir den Kollisionscode anzuschauen und zu verhindern, dass ich zum "lustigen Teil" komme und die Spiellogik codiere :(


Ich habe versucht, dasselbe Kollisionssystem wie in der XNA AppHub-Plattform-Demo zu implementieren (indem ich die meisten Elemente kopiere und einfüge). Der "springende" Fehler tritt jedoch in meinem Spiel auf, während er in der AppHub-Demo nicht auftritt. [Jumping Bug: http://i.stack.imgur.com/NLg4j.png ]

Zum Springen überprüfe ich, ob der Spieler "onGround" ist und addiere -5 zu Velocity.Y.

Da die Velocity.X des Players höher ist als die Velocity.Y (siehe viertes Feld im Diagramm), wird onGround auf true gesetzt, wenn dies nicht der Fall sein sollte, und der Player springt in die Luft.

Ich glaube, dass dies in der AppHub-Demo nicht der Fall ist, da Velocity.X des Players niemals höher sein wird als Velocity.Y, aber ich kann mich irren.

Ich habe das zuvor gelöst, indem ich zuerst auf der X-Achse, dann auf der Y-Achse aufgelöst habe. Aber das schadet der Kollision mit den Spikes, wie ich oben sagte.

Vittorio Romeo
quelle
5
Da läuft eigentlich nichts schief. Die von mir verwendete Methode drückt zuerst auf die X-Achse und dann auf die Y-Achse. Deshalb wird der Spieler auch auf den vertikalen Stacheln horizontal geschoben. Ich muss eine Lösung finden, die das "Sprungproblem" vermeidet und den Spieler auf die flache Achse (die Achse mit der geringsten Durchdringung) drückt, aber ich kann keine finden.
Vittorio Romeo
1
Sie können feststellen, welches Gesicht des Hindernisses der Spieler berührt, und dieses Problem beheben
Ioachim
4
@ Johnathan Hobbs, lesen Sie die Frage. Vee weiß genau, was sein Code tut, kann aber ein bestimmtes Problem nicht lösen. Das Durchlaufen des Codes wird ihm in dieser Situation nicht helfen.
AttackingHobo
4
@Maik @Jonathan: Mit dem Programm läuft nichts "schief" - er versteht genau, wo und warum sein Algorithmus nicht das tut, was er will. Er weiß einfach nicht, wie er den Algorithmus ändern soll, um das zu tun, was er will. Daher ist der Debugger in diesem Fall nicht nützlich.
BlueRaja - Danny Pflughoeft
1
@AttackingHobo @BlueRaja Ich bin damit einverstanden und habe meinen Kommentar gelöscht. Das war ich nur zu Schlussfolgerungen darüber springen, was los war. Ich entschuldige mich wirklich dafür, dass Sie das erklären und mindestens eine Person irreführen müssen. Ehrlich gesagt, können Sie sich darauf verlassen, dass ich die Frage wirklich richtig aufnehme, bevor ich das nächste Mal eine Antwort hinterlasse.
Doppelgreener

Antworten:

9

OK, ich habe herausgefunden, warum die XNA AppHub-Plattform-Demo nicht den "springenden" Fehler aufweist: Die Demo testet die Kollisionskacheln von oben nach unten . Wenn sich der Spieler an einer "Wand" befindet, überlappt er möglicherweise mehrere Kacheln. Die Auflösungsreihenfolge ist wichtig, da das Auflösen einer Kollision auch andere Kollisionen auflösen kann (jedoch in eine andere Richtung). Die onGroundEigenschaft wird nur festgelegt, wenn die Kollision behoben wird, indem der Player auf der y-Achse nach oben gedrückt wird. Diese Auflösung tritt nicht auf, wenn der Player durch die vorherigen Auflösungen nach unten und / oder horizontal gedrückt wurde.

Ich konnte den "springenden" Fehler in der XNA-Demo reproduzieren, indem ich diese Zeile änderte:

for (int y = topTile; y <= bottomTile; ++y)

dazu:

for (int y = bottomTile; y >= topTile; --y)

(Ich habe auch einige physikalische Konstanten angepasst, aber das sollte keine Rolle spielen.)

Vielleicht bodiesToCheckbehebt das Sortieren auf der y-Achse vor dem Beheben der Kollisionen in Ihrem Spiel den "springenden" Fehler. Ich schlage vor, die Kollision auf der "flachen" Durchdringungsachse zu beheben, wie es die XNA-Demo und Trevor vorschlagen . Beachten Sie auch, dass der XNA-Demo-Player doppelt so groß ist wie die kollidierbaren Kacheln, wodurch die Wahrscheinlichkeit für Mehrfachkollisionen steigt.

Leftium
quelle
Das hört sich interessant an ... Glauben Sie, dass das Ordnen aller Elemente nach der Y-Koordinate vor dem Erkennen von Kollisionen alle meine Probleme lösen könnte? Gibt es einen Haken?
Vittorio Romeo
Und ich habe den "Haken" gefunden. Mit dieser Methode wird der Sprungfehler behoben, aber wenn ich Velocity.Y nach dem Erreichen einer Obergrenze auf 0 setze, tritt er erneut auf.
Vittorio Romeo
@Vee: Setze Position = nextPositionsofort innerhalb der forSchleife, sonst onGroundtreten die unerwünschten Kollisionsauflösungen (Einstellung ) immer noch auf. Der Player sollte beim Aufprall auf die Decke senkrecht nach unten (und niemals nach oben) gedrückt onGroundwerden. So macht es die XNA-Demo, und ich kann den "Decken" -Fehler dort nicht wiederholen.
Leftium
pastebin.com/TLrQPeBU - das ist mein Code: Ich arbeite tatsächlich an der Position selbst, ohne eine "nextPosition" -Variable zu verwenden. Es sollte wie in der XNA-Demo funktionieren, aber wenn ich die Zeile, in der Velocity.Y auf 0 gesetzt ist (Zeile 57), unkommentiert lasse, tritt der Sprungfehler auf. Wenn ich es entferne, schwimmt der Player weiter, wenn er in eine Decke springt. Kann das behoben werden?
Vittorio Romeo
@Vee: Ihr Code zeigt nicht an, wie onGroundeingestellt ist, daher kann ich nicht untersuchen, warum das Springen falsch erlaubt ist. Die XNA-Demo aktualisiert ihre analoge isOnGroundEigenschaft im Inneren HandleCollisions(). Velocity.YWarum bewegt die Schwerkraft den Player nach dem Einstellen auf 0 nicht wieder nach unten? Ich vermute, die onGroundEigenschaft ist falsch eingestellt. Schauen Sie sich an, wie die XNA-Demo aktualisiert wird previousBottomund isOnGround( IsOnGround).
Leftium
9

Die einfachste Lösung für Sie besteht darin, beide Kollisionsrichtungen für jedes Objekt auf der Welt zu überprüfen, bevor Sie Kollisionen auflösen, und nur die kleinere der beiden resultierenden "zusammengesetzten Kollisionen" aufzulösen. Dies bedeutet, dass Sie so wenig wie möglich auflösen, anstatt immer zuerst x oder immer zuerst y aufzulösen.

Ihr Code würde ungefähr so ​​aussehen:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

große Überarbeitung : Beim Lesen von Kommentaren zu anderen Antworten habe ich wohl endlich eine unausgesprochene Vermutung bemerkt, die dazu führt, dass dieser Ansatz nicht funktioniert (und erklärt, warum ich die Probleme, die einige - aber nicht alle - haben, nicht verstehen konnte. Leute sahen mit diesem Ansatz). Um dies zu erläutern, hier ist ein weiterer Pseudocode, der genauer zeigt, was die Funktionen, auf die ich zuvor verwiesen habe, eigentlich tun sollen:

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

Dies ist kein "Pro-Objekt-Paar" -Test. Es funktioniert nicht, wenn das sich bewegende Objekt für jede Kachel einer Weltkarte einzeln getestet und aufgelöst wird (dieser Ansatz funktioniert nie zuverlässig und scheitert mit abnehmender Kachelgröße auf immer katastrophalere Weise). Stattdessen wird das sich bewegende Objekt gegen jedes Objekt auf der Weltkarte gleichzeitig getestet und dann basierend auf Kollisionen gegen die gesamte Weltkarte aufgelöst.

Auf diese Weise stellen Sie sicher, dass (zum Beispiel) einzelne Wandkacheln innerhalb einer Wand den Spieler niemals zwischen zwei benachbarten Kacheln auf und ab hüpfen lassen, was dazu führt, dass der Spieler in einem nicht vorhandenen Raum zwischen ihnen eingeschlossen wird. Die Abstände der Kollisionsauflösung werden immer bis zum leeren Weltraum berechnet, nicht nur bis zur Grenze einer einzelnen Kachel, auf der sich möglicherweise eine weitere feste Kachel befindet.

Trevor Powell
quelle
1
i.stack.imgur.com/NLg4j.png - dies passiert, wenn ich die oben beschriebene Methode verwende.
Vittorio Romeo
1
Vielleicht verstehe ich Ihren Code nicht richtig, aber lassen Sie mich das Problem im Diagramm erläutern: Da der Player auf der X-Achse schneller ist, ist die X-Penetration> als die Y-Penetration. Kollision wählt die Y-Achse (da die Penetration geringer ist) und schiebt den Spieler vertikal. Dies tritt nicht auf, wenn der Spieler langsam genug ist, so dass die Y-Penetration immer> als die X-Penetration ist.
Vittorio Romeo
1
@Vee Auf welchen Bereich des Diagramms beziehen Sie sich hier, wenn Sie mit diesem Ansatz ein falsches Verhalten angeben? Ich gehe von Bereich 5 aus, in dem der Spieler deutlich weniger in X als in Y eindringt, was dazu führen würde, dass er entlang der X-Achse herausgedrückt wird - was das richtige Verhalten ist. Das schlechte Verhalten tritt nur auf, wenn Sie eine Achse für die Auflösung basierend auf der Geschwindigkeit (wie in Ihrem Bild) und nicht basierend auf der tatsächlichen Durchdringung (wie ich vorgeschlagen habe) auswählen.
Trevor Powell
1
@ Trevor: Ah, ich verstehe, was du sagst. In diesem Fall könnten Sie es einfach schaffenif(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Danny Pflughoeft
1
@ BlueRaja Toller Punkt. Ich habe meine Antwort bearbeitet, um weniger Bedingungen zu verwenden, wie Sie vorschlagen. Vielen Dank!
Trevor Powell,
6

Bearbeiten

Ich habe es verbessert

Das Wichtigste, was ich geändert habe, scheint die Klassifizierung der Kreuzungen zu sein.

Schnittpunkte sind entweder:

  • Deckenunebenheiten
  • Bodentreffer (aus dem Boden drücken)
  • Kollisionen in der Luftwand
  • Geerdete Wandkollisionen

und ich löse sie in dieser Reihenfolge

Definiere eine Bodenkollision als Spieler, der mindestens 1/4 Weg auf dem Plättchen ist

Das ist also eine Bodenkollision und der Spieler ( blau ) wird oben auf dem Plättchen sitzen ( schwarz ) Bodenkollision

Dies ist jedoch KEINE Bodenkollision und der Spieler "rutscht" auf der rechten Seite des Plättchens, wenn er darauf landet Wer kein Bodenkollisionsspieler sein will, wird vorbeigehen

Auf diese Weise bleibt der Spieler nicht mehr an den Wänden hängen

Bobobobo
quelle
Ich habe deine Methode ausprobiert (siehe meine Implementierung: pastebin.com/HarnNnnp ), aber ich weiß nicht, wo ich die Velocity des Players auf 0 setzen soll. Ich habe versucht, sie auf 0 zu setzen, wenn encrY <= 0, aber das lässt den Player in der Luft anhalten während er an einer Wand entlanggleitet und ihn auch immer wieder an der Wand springen lässt.
Vittorio Romeo
Dies funktioniert jedoch ordnungsgemäß, wenn der Player mit den Spikes interagiert (siehe GIF-Datei). Ich würde mich sehr freuen, wenn Sie mir helfen könnten, das Problem des Wandspringens zu lösen (auch wenn ich in Wänden stecke). Denken Sie daran, dass meine Wände aus mehreren AABB-Fliesen bestehen.
Vittorio Romeo
Es tut uns leid, dass Sie einen weiteren Kommentar gepostet haben, aber nachdem Sie Ihren Code in ein brandneues Projekt kopiert und sp in 5 geändert haben und die Kacheln in einer Wandform erscheinen, tritt der springende Fehler auf (siehe folgende Abbildung: i.stack.imgur.com) /NLg4j.png)
Vittorio Romeo
Ok, versuch es jetzt.
Bobobobo
+1 für ein funktionierendes Codebeispiel (es fehlen jedoch horizontal bewegte "Spike"
-Blöcke
3

Forenote:
Meine Engine verwendet eine Kollisionserkennungsklasse, die Kollisionen mit allen Objekten in Echtzeit überprüft, und zwar nur dann, wenn sich ein Objekt bewegt, und nur auf den Gitterquadraten, die es derzeit einnimmt.

Meine Lösung:

Als ich in meinem 2d-Plattformer auf solche Probleme stieß, habe ich Folgendes getan:

- (Die Engine erkennt mögliche Kollisionen schnell)
- (Das Spiel weist die Engine an, die genauen Kollisionen für ein bestimmtes Objekt zu überprüfen.) [Gibt den Vektor des Objekts zurück. *]
- (Das Objekt betrachtet die Eindringtiefe sowie die vorherige Position im Verhältnis zur vorherigen Position des anderen Objekts. um zu bestimmen, von welcher Seite herauszugleiten ist)
- (Objekt bewegt sich und mittelt seine Geschwindigkeit mit der Geschwindigkeit des Objekts (wenn sich das Objekt bewegt))

ultifinitus
quelle
"(Das Objekt betrachtet die Eindringtiefe sowie die vorherige Position im Verhältnis zur vorherigen Position des anderen Objekts, um zu bestimmen, von welcher Seite aus es herausrutschen soll.)" Dies ist der Teil, an dem ich interessiert bin. Können Sie dies näher erläutern? Gelingt es in der Situation, die durch das GIF und das Diagramm dargestellt wird?
Vittorio Romeo
Ich habe noch keine Situation (außer hohen Geschwindigkeiten) gefunden, in der diese Methode nicht funktioniert.
Ultifinitus
Hört sich gut an, aber können Sie tatsächlich erklären, wie Sie Penetrationen auflösen? Lösen Sie immer auf der kleinsten Achse? Prüfen Sie die Seite der Kollision?
Vittorio Romeo
Nein, eigentlich ist die kleinere Achse kein guter (primärer) Weg. MEINER BESCHEIDENEN MEINUNG NACH. Normalerweise entscheide ich mich basierend darauf, welche Seite sich zuvor außerhalb der Seite des anderen Objekts befand. Dies ist die häufigste. Wenn das fehlschlägt, überprüfe ich anhand von Geschwindigkeit und Tiefe.
Ultifinitus
2

In Videospielen, die ich programmiert habe, sollte der Ansatz eine Funktion haben, die angibt, ob Ihre Position gültig ist, d. H. bool ValidPos (int x, int y, int wi, int he);

Die Funktion prüft den Begrenzungsrahmen (x, y, wi, he) gegen alle Geometrien im Spiel und gibt false zurück, wenn es eine Schnittmenge gibt.

Wenn Sie sich bewegen möchten, sagen Sie nach rechts, nehmen Sie die Spielerposition, addieren Sie 4 zu x und prüfen Sie, ob die Position gültig ist. Wenn nicht, prüfen Sie mit + 3, + 2 usw., bis dies der Fall ist.

Wenn Sie auch die Schwerkraft benötigen, benötigen Sie eine Variable, die wächst, solange Sie nicht auf dem Boden aufschlagen (Auftreffen auf den Boden: ValidPos (x, y + 1, wi, he) == true, y ist hier nach unten positiv). Wenn Sie diese Strecke zurücklegen können (dh ValidPos (x, y + Schwerkraft, wi, er) gibt true zurück), fallen Sie. Dies ist manchmal nützlich, wenn Sie Ihren Charakter beim Fallen nicht kontrollieren können.

Ihr Problem ist nun, dass Sie Objekte in Ihrer Welt haben, die sich bewegen. Überprüfen Sie also zunächst, ob Ihre alte Position noch gültig ist!

Wenn dies nicht der Fall ist, müssen Sie eine Position suchen, bei der es sich um eine handelt. Wenn sich Objekte im Spiel nicht schneller als beispielsweise 2 Pixel pro Spielumdrehung bewegen können, müssten Sie prüfen, ob die Position (x, y-1) gültig ist, dann (x, y-2), dann (x + 1, y) usw usw. Der gesamte Abstand zwischen (x-2, y-2) und (x + 2, y + 2) sollte überprüft werden. Wenn es keine gültige Position gibt, bedeutet dies, dass Sie "gequetscht" wurden.

HTH

Valmond

Valmond
quelle
Ich habe diesen Ansatz in meinen Game Maker-Tagen verwendet. Ich kann mir überlegen, wie ich es durch bessere Suchvorgänge beschleunigen / verbessern kann (z. B. binäre Suche auf dem zurückgelegten Raum, obwohl dies abhängig von der zurückgelegten Entfernung und der Geometrie möglicherweise langsamer ist), aber der Hauptnachteil dieses Ansatzes ist Anstatt nur eine Prüfung auf alle möglichen Kollisionen durchzuführen, werden alle Positionsänderungen geprüft.
Jeff
"Sie tun bis zu was auch immer Ihre Änderung in der Position überprüft" Sorry, aber was genau bedeutet das? Übrigens werden Sie Raumpartitionierungstechniken (BSP, Octree, Quadtree, Tilemap usw.) verwenden, um das Spiel zu beschleunigen, aber Sie müssen das trotzdem immer tun (wenn die Karte groß ist), die Frage ist jedoch nicht danach über den Algorithmus, mit dem der Spieler (korrekt) bewegt wird.
Valmond
@Valmond Ich denke, @Jeff bedeutet, dass Sie bis zu 10 verschiedene Kollisionserkennungen haben können, wenn Sie den Player um 10 Pixel nach links bewegen.
Jonathan Connell
Wenn es das ist, was er meint, dann hat er Recht und es sollte so sein (wer kann sonst sagen, ob wir bei +6 und nicht bei +7 angehalten werden?). Computer sind schnell, ich habe dies auf dem S40 (~ 200mhz und ein nicht so schnelles kvm) verwendet und es hat wie ein Zauber funktioniert. Sie können immer versuchen, das anfängliche Algo zu optimieren, aber das gibt Ihnen immer Eckfälle wie die des OP.
Valmond
Genau das habe ich gemeint
Jeff
2

Ich habe ein paar Fragen, bevor ich anfange, diese zu beantworten. Waren diese Kacheln in dem ursprünglichen Käfer, in dem Sie in den Wänden stecken geblieben sind, auf den linken einzelnen Kacheln im Gegensatz zu einer großen Kacheln? Und wenn ja, steckte der Spieler zwischen ihnen fest? Wenn Sie beide Fragen mit Ja beantworten, vergewissern Sie sich, dass Ihre neue Position gültig ist . Das bedeutet, dass Sie überprüfen müssen, ob eine Kollision vorliegt, an der Sie dem Spieler mitteilen, dass er sich bewegen soll. Löse also die minimale Verschiebung wie unten beschrieben und bewege deinen Spieler danach nur, wenn er kanndorthin ziehen. Fast zu tief unter der Nase: P Dies führt tatsächlich zu einem weiteren Fehler, den ich "Eckfälle" nenne. Im Wesentlichen in Bezug auf Ecken (wie links unten, wo die horizontalen Spitzen in Ihrem GIF herauskommen, aber wenn es keine Spitzen gäbe) würde eine Kollision nicht aufgelöst, da davon ausgegangen wird, dass keine der von Ihnen generierten Auflösungen zu einer gültigen Position führt . Behalten Sie zur Behebung dieses Problems einfach einen Überblick darüber, ob die Kollision behoben wurde, sowie eine Liste aller Mindestauflösungen für die Penetration. Wenn die Kollision nicht behoben wurde, durchlaufen Sie anschließend jede von Ihnen erzeugte Auflösung und verfolgen Sie die maximale X- und Y-Auflösung (die Maximalwerte müssen nicht aus derselben Auflösung stammen). Beheben Sie dann die Kollision mit diesen Maximalwerten. Dies scheint alle Ihre Probleme sowie die Probleme zu lösen, auf die ich gestoßen bin.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

Eine andere Frage: Sind die Stacheln, die Sie zeigen, eine Kachel oder einzelne Kacheln? Wenn es sich um einzelne dünne Kacheln handelt, müssen Sie möglicherweise einen anderen Ansatz für die horizontalen und vertikalen Kacheln verwenden als im Folgenden beschrieben. Aber wenn es ganze Kacheln sind, sollte das funktionieren.


Okay, im Grunde ist es das, was @ Trevor Powell beschrieb. Da Sie nur AABBs verwenden, müssen Sie nur feststellen, wie weit ein Rechteck in das andere eindringt. Dies gibt Ihnen eine Größe auf der X-Achse und der Y-Achse. Wählen Sie das Minimum aus den beiden aus und bewegen Sie Ihr kollidierendes Objekt entlang dieser Größe. Das ist alles, was Sie brauchen, um eine AABB-Kollision zu beheben. Sie müssen sich bei einer solchen Kollision NIEMALS entlang mehr als einer Achse bewegen, daher sollten Sie sich niemals darüber im Klaren sein, welche zuerst bewegt werden soll, da Sie nur das Minimum bewegen.

Metanet Software verfügt über ein klassisches Tutorial auf einen Ansatz hier . Es geht auch in andere Formen.

Hier ist eine XNA-Funktion, die ich erstellt habe, um den Überlappungsvektor zweier Rechtecke zu finden:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(Ich hoffe, es ist einfach zu befolgen, da ich mir sicher bin, dass es bessere Möglichkeiten gibt, es umzusetzen ...)

isCollision (Rectangle) ist buchstäblich nur ein Aufruf von Rectangle.Intersects (Rectangle) von XNA.

Ich habe dies mit beweglichen Plattformen getestet und es scheint gut zu funktionieren. Ich werde weitere Tests durchführen, die Ihrer .gif-Datei ähneln, um sicherzustellen, dass sie nicht funktioniert, und einen entsprechenden Bericht erstatten.


Jeff
quelle
Ich bin etwas skeptisch gegenüber dem Kommentar, den ich dazu gemacht habe und der nicht mit dünnen Plattformen funktioniert. Ich kann mir keinen Grund vorstellen, warum es nicht so wäre. Auch wenn Sie Quelle möchten, kann ich ein XNA-Projekt für Sie hochladen
Jeff
Ja, ich habe das nur noch ein wenig getestet und es geht auf dasselbe Problem zurück, in der Wand hängen zu bleiben. Es liegt daran, dass die beiden Kacheln aneinandergereiht sind und Sie anweisen, sich wegen des unteren nach oben und dann (in Ihrem Fall) nach oben zu bewegen, um die Schwerkraftbewegung effektiv zu negieren, wenn Ihre y-Geschwindigkeit langsam genug ist . Ich muss eine Lösung dafür finden, ich erinnere mich an dieses Problem.
Jeff
Okay, ich habe mir einen extrem abgedrehten Weg ausgedacht, um dein erstes Problem zu umgehen. Die Lösung besteht darin, die minimale Penetration für jeden AABB zu ermitteln, indem nur derjenige mit dem größten X und dann ein zweiter Durchgang mit dem größten Y ermittelt werden. Es sieht schlecht aus, wenn Sie zerdrückt werden, hebt jedoch die Arbeit und Sie können geschoben werden horizontal, und Ihr ursprüngliches Haftproblem ist verschwunden. Es ist hackey und ich habe keine Ahnung, wie es sich auf andere Formen übertragen würde. Eine andere Möglichkeit könnte sein, die berührenden Geometrien zu verschweißen, aber ich habe es nicht einmal versucht
Jeff,
Wände bestehen aus 16x16 Fliesen. Spikes sind ein 16x16-Plättchen. Wenn ich auf der minimalen Achse auflöse, tritt der springende Fehler auf (siehe Diagramm). Würde Ihre "extrem hackige" Lösung mit der in der GIF-Datei gezeigten Situation funktionieren?
Vittorio Romeo
Ja, arbeitet in meinem. Wie groß ist dein Charakter?
Jeff
1

Es ist einfach.

Schreiben Sie die Spikes neu. Es ist die Schuld der Spikes.

Ihre Kollisionen sollten in diskreten, greifbaren Einheiten stattfinden. Das Problem ist meines Erachtens:

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

Das Problem ist mit Schritt 2, nicht 3!

Wenn Sie versuchen, die Dinge solide zu machen, sollten Sie nicht zulassen, dass Gegenstände auf diese Weise ineinander gleiten. Sobald Sie sich in einer Kreuzungsposition befinden und Ihren Platz verlieren, wird es schwieriger, das Problem zu lösen.

Die Stacheln sollten idealerweise die Existenz des Spielers überprüfen und ihn bei Bedarf schieben , wenn sie sich bewegen .

Eine einfache Möglichkeit , dies zu erreichen , ist für die Spieler haben moveXund moveYFunktionen , die die Landschaft zu verstehen und werden den Spieler durch ein bestimmtes Delta schieben oder so weit wie möglich , ohne ein Hindernis zu treffen .

Normalerweise werden sie von der Ereignisschleife aufgerufen. Sie können jedoch auch von Objekten aufgerufen werden, um den Player zu schieben.

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

Offensichtlich muss der Spieler immer noch auf die Stacheln reagieren, wenn er in sie eindringt .

Die übergeordnete Regel lautet, dass ein Objekt , nachdem es sich bewegt hat, an einer Position ohne Schnittpunkt enden soll. Auf diese Weise ist es unmöglich, die Pannen zu bekommen, die Sie sehen.

Chris Burt-Brown
quelle
Im Extremfall, wenn moveYdie Kreuzung nicht entfernt werden kann (weil eine andere Wand im Weg ist), können Sie erneut nach einer Kreuzung suchen und entweder moveX versuchen oder ihn einfach töten.
Chris Burt-Brown