Microsoft XNA Platformer Beispiel: Ist die Kollisionserkennung korrekt implementiert?

11

Das von Microsoft bereitgestellte Beispiel scheint, als ob die Kollisionserkennung (soweit ich sehen kann) einen kleinen Fehler aufweist. Wenn der Benutzer mit einer nicht passierbaren Kachel kollidiert, wird die Tiefe der Kreuzung berechnet. Der kleinere der Tiefenwerte X und Y wird verwendet, um die Position des Benutzers so festzulegen, dass er nicht mehr mit der Kachel kollidiert. Aber wenn der Benutzer diagonal unterwegs wäre, könnte dies dazu führen, dass der Benutzer nicht genau an dem Punkt landet, an dem der Charakter zuerst mit der Kachel kollidieren würde?

Ich liege wahrscheinlich falsch, aber so sehe ich das auch.

   private void HandleCollisions()
        {
            // Get the player's bounding rectangle and find neighboring tiles.
            Rectangle bounds = BoundingRectangle;
            int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
            int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
            int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
            int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;

            // Reset flag to search for ground collision.
            isOnGround = false;

            // For each potentially colliding tile,
            for (int y = topTile; y <= bottomTile; ++y)
            {
                for (int x = leftTile; x <= rightTile; ++x)
                {
                    // If this tile is collidable,
                    TileCollision collision = Level.GetCollision(x, y);
                    if (collision != TileCollision.Passable)
                    {
                        // Determine collision depth (with direction) and magnitude.
                        Rectangle tileBounds = Level.GetBounds(x, y);
                        Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
                        if (depth != Vector2.Zero)
                        {
                            float absDepthX = Math.Abs(depth.X);
                            float absDepthY = Math.Abs(depth.Y);

                            // Resolve the collision along the shallow axis.
                            if (absDepthY < absDepthX || collision == TileCollision.Platform)
                            {
                                // If we crossed the top of a tile, we are on the ground.
                                if (previousBottom <= tileBounds.Top)
                                    isOnGround = true;

                                // Ignore platforms, unless we are on the ground.
                                if (collision == TileCollision.Impassable || IsOnGround)
                                {
                                    // Resolve the collision along the Y axis.
                                    Position = new Vector2(Position.X, Position.Y + depth.Y);

                                    // Perform further collisions with the new bounds.
                                    bounds = BoundingRectangle;
                                }
                            }
                            else if (collision == TileCollision.Impassable) // Ignore platforms.
                            {
                                // Resolve the collision along the X axis.
                                Position = new Vector2(Position.X + depth.X, Position.Y);

                                // Perform further collisions with the new bounds.
                                bounds = BoundingRectangle;
                            }
                        }
                    }
                }
            }

            // Save the new bounds bottom.
            previousBottom = bounds.Bottom;
        }
PriestVallon
quelle
3
Warum das Minus 1, ppl? Die Frage ist für mich gültig. Aber hier ist eine kurze Antwort: Die mit XNA gelieferte Platformer-Demo ist sowieso nur ein Beispiel. Nicht unbedingt als Vorbild für Ihre Spiele zu beachten. Es soll Ihnen zeigen, dass ein Spiel gemacht werden kann. Sie sollten sich nicht darum kümmern, ob die Implementierung überhaupt nicht die beste ist.
Gustavo Maciel
Danke, ich habe nur mit dem Beispiel angenommen, dass das, was sie getan hatten, der beste Weg war und dass mir etwas fehlte. Danke, dass du es für mich geklärt hast.
PriestVallon

Antworten:

12

Du hast absolut recht . Ich hatte einige Probleme mit den Kollisionsroutinen im XNA-Platformer-Beispiel. Aber ich habe es geschafft, mit dem im Beispiel angegebenen Code zu beginnen und ihn ein wenig zu ändern, bis ich in jedem Testszenario, das ich darauf werfen konnte, konsistente Ergebnisse erzielt habe.

Das Problem, das ich hatte, war insbesondere, als ich versuchte, an einer Wand entlang zu gleiten, indem ich mich diagonal dagegen bewegte. Aufgrund der Annahme, die die Probe macht, um Kollisionen basierend auf der kleinsten Verschiebungsachse aufzulösen, führte dies dazu, dass sich der Charakter nicht bewegen konnte, wenn er gegen eine Wand in eine Richtung drückte . Wenn ich zum Beispiel ein Schild benutze, stecke ich fest, wenn ich mich an die Decke drücke und versuche, mich von links nach rechts dagegen zu bewegen (ich kann mich nicht an die Einzelheiten erinnern). Ein Wechsel des Vorzeichens würde diese Situation lösen, aber im umgekehrten Szenario würde ein Problem auftreten. Fazit ist, dass ich mit der bereitgestellten Implementierung nicht in allen Seiten und aus allen Richtungen richtig funktionieren konnte - es würde immer in mindestens einem Fall fehlschlagen.

Der Kern der Änderungen, die ich vorgenommen habe, bestand darin, die Bewegung auf der X-Achse unabhängig von der Bewegung auf der Y-Achse in zwei getrennten Schritten zu handhaben. Ich habe bereits in dieser Antwort darüber geschrieben, gehen Sie also zu den Details.

Und wenn ich mich richtig erinnere, war der eigentliche Grund dafür ungefähr so:

Geben Sie hier die Bildbeschreibung ein

David Gouveia
quelle
1
David pwning immer auf XNA!
Gustavo Maciel
1
@ Gustavo-Gtoknu Ich hatte das Gefühl, dass es immer noch eine Zeichnung des Problems braucht: P
David Gouveia
1
Ich bin gerade auf diese Antwort gestoßen - großartige Arbeit! Danke David.
Austin Brunkhorst
1

Wenn Sie mehrere Kollisionen haben, wenn Sie Ihre Kollisionen von der nächstgelegenen zur entferntesten Entfernung von der Mitte jedes betroffenen Rechtecks ​​korrigieren, tritt das Problem des "Hängens" nicht auf.

1) Finden Sie alle kollidierenden Rechtecke

2) Wenn es mehr als eine gibt (abhängig von Ihrem Anwendungsfall kann dies häufig oder selten sein), suchen Sie die nächstgelegene.

3) Lösen Sie Kollisionen nacheinander und prüfen Sie, ob die anderen Kollisionen noch gültig sind

In der akzeptierten Antwort sind die Kollisions- und Eingabelogik schlammig; Es werden Überprüfungen durchgeführt, um den Kurs usw. zu bestimmen. Durch die Implementierung in der beschriebenen Weise wird die Kollisionslogik von der Eingangslogik getrennt, und zwar auf Kosten der Berechnung der Entfernung, falls erforderlich.

Akkordeonordner
quelle