1
\$\begingroup\$

So, I currently have a Rectangle that updates with player position, and is the size of the sprite. I'm using Tiled as my tile editor, and have an Object layer called "collision" which I loop over when loading the map, and store each object as a Rectangle in a list. Then in my Update I check if the player is colliding with any of those rectangles (via looping over the list) with Rectangle.Intersects(Rectangle). And if a collision is detected, I handle it with this function:

private static void HandleCollision()
{
    if (_currentLevel.IsCollisionTile())
    {
        float deltaLeft = Math.Abs((Player.Instance.Position.X + Player.Instance.PlayerCollider.Width) - (_currentLevel.collidingRectangle.X));
        float deltaRight = Math.Abs((Player.Instance.Position.X) + (_currentLevel.collidingRectangle.X + _currentLevel.collidingRectangle.Width));
        float deltaUp = Math.Abs((Player.Instance.Position.Y + Player.Instance.PlayerCollider.Height) - (_currentLevel.collidingRectangle.Y));
        float deltaDown = Math.Abs((Player.Instance.Position.Y) - (_currentLevel.collidingRectangle.Y + _currentLevel.collidingRectangle.Height));

        if (deltaLeft < deltaRight && deltaLeft < deltaUp && deltaLeft < deltaDown)
        {
            Player.Instance.Position.X -= deltaLeft;
        }
        else if (deltaRight < deltaLeft && deltaRight < deltaUp && deltaRight < deltaDown)
        {
            Player.Instance.Position.X += deltaRight;
        }
        else if (deltaUp < deltaLeft && deltaUp < deltaRight && deltaUp < deltaDown)
        {
            Player.Instance.Position.Y -= deltaUp;
        }
        else if (deltaDown < deltaLeft && deltaDown < deltaRight && deltaDown < deltaUp)
        {
            Player.Instance.Position.Y += deltaDown;
        }
    }
}

I know this is ugly, but that's not the problem, the problem is that when it detects a collision, the player "spazzes out" and just bounces in and out of the rectangle it's colliding with. I've tried a couple other ways too, like just setting the player position without using absolute values, with similar calculations. The same problem happens though. Also it allows the player to just phase through platforms from one side, and the player will get teleported to other sides sometimes, so this is very bad.

I'm not sure how else to handle it, I've done research and googled a lot, as well as searched here, but everything I find is about the actual collision detection, which I'm doing via the Rectangle.Intersects() method. I've been at this for 3-4 days, before deciding to post here.

\$\endgroup\$
1
\$\begingroup\$

Of-course it bounces. You are doing this:

  • Detect rectangle intersection using Monogame API calls (this only tells you that an intersection occurred)
  • Make the player going back/forward by the tile rectangle

The problem with this method is that the player will be pushed by the same width/height no matter where the collision did happen. With the most common implementation of player's x/y changing by a certain amount of pixels per frame/update, depending the speed of movement and where the starting position was, the collision may be detected when player is 5 pixels inside the tile, 3 pixels inside the tile, 14 pixels inside the tile, or not get detected at all, for example player is at x = 5, tile is at x = 22, tile and player box are 16x16, and you set speed at 40 pixels per frame, resulting in the player going through the tile with no collision being detected, because player was at 5 one frame, and at 45 the next frame.

[Animated gif here]

A quick fix

Calculate the penetration vector of the player rectangle into the tile rectangle. By moving the player back by the penetration vector, not the tile width, the player will be rejected and leaving touching the tile side.

This method has issues with tile based maps, that you will need to address somehow but you have a starting point now. Experiment to adapt to your needs.

This is what you want: https://vimeo.com/64923588

The formula for the penetration vector, as long as we are in Axis Aligned Bounding Box (AABB) land, is simple:

TX = Player.x - Tile.x;
TY = Player.y - Tile.y;

But you will push the player only in the direction of the axis with greater absolute value.

Read more on penetration vector for AABB here, which uses minkowski difference to solve collisions.

Possible solutions to "player stuck between tiles" or "player pushed out of an entire line of tiles":

Possible solutions to "player still miss the tile if moving too fast":

  • Don't allow player to move more pixels, per frame, than the tile width (only possible if you have a constant tile width).
  • Use a more complex AABB collision/penetration detection that account for moving AABB: Simple intersection tests for games
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.