11
\$\begingroup\$

The example provided by Microsoft seems as if the collision detection (from what I can see) will have a small error. When the user collides with an Unpassable tile, the depth of the intersection is calculated. The smaller of the depth values X and Y is used to fix the position of the user so it no longer collides with the tile. But if the user was travelling diagonally could this result in the user not ending up in precisely the point where the character would first collide with the tile?

I'm probably wrong but that's just the way I see it.

   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;
        }
\$\endgroup\$
2
  • 3
    \$\begingroup\$ Why the minus 1, ppl? The question is valid for me. But here's a short answer: The platformer demo that comes with XNA is just an example, anyway. Not to be followed strictly as a model for your games. It is to show you that a game CAN be done. You shouldn't bother if it's implementation isn't the best at all. \$\endgroup\$ Commented Mar 6, 2012 at 2:35
  • \$\begingroup\$ Thanks, I just assumed with the example that what they had done was the best way to do and that I was missing something. Thanks for clearing it up for me. \$\endgroup\$ Commented Mar 6, 2012 at 2:42

2 Answers 2

12
\$\begingroup\$

You are absolutely right. I've had my share of problems with the collision routines on the XNA platformer sample. But I've managed to start from the code as provided in the sample, and modified it a bit until I achieved consistent results in every test scenario I could throw at it.

In particular, the sort of problem I was having was when trying to slide along a wall by moving diagonally against it. Because of the assumption the sample makes in order to resolve collisions based on the smallest axis of displacement, this resulted in the character not being able to move when pushing against a wall in some direction. For instance, using one sign, I would get stuck when hugging the ceiling and trying to move against it from left to right (can't remember the specifics). Switching the sign would solve that situation but a problem would appear in the opposite scenario. Bottom line is that with the provided implementation I couldn't get it to work correctly in all sides and from every direction - it would always fail on at least one case.

So the core of the changes I did were all about starting to handle movement on the X axis independently from movement on the Y axis, on two separate steps. I've written about it before in this answer so head there for the details.

And if I remember correctly, the actual reason for it was something like this:

enter image description here

\$\endgroup\$
3
  • 1
    \$\begingroup\$ David always pwning on XNA! \$\endgroup\$ Commented Mar 6, 2012 at 3:15
  • 1
    \$\begingroup\$ @Gustavo-Gtoknu I kinda felt it still needed a drawing of the problem :P \$\endgroup\$ Commented Mar 6, 2012 at 3:24
  • 1
    \$\begingroup\$ Just came across this answer - great work! Thanks David. \$\endgroup\$ Commented Jan 16, 2013 at 5:38
1
\$\begingroup\$

When you have multiple collisions if you rectify your collisions from closest to farthest away from the center of each rectangle involved, you will not have the issue of "hanging."

1) Find all the colliding rectangles

2) If there are more than one (depending on your use case this can be frequent or infrequent), find the closest.

3) Resolve collisions one at a time and check if the others are still valid collisions

In the accepted answer the collision and input logic are muddy; it has checks to determine the heading, etc. Implementing it in the described manner keeps the collision logic separate from the input logic at the cost of calculating distance when necessary.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .