Trouble with Collision Detection & Response in Monogame

Started by
2 comments, last by Shaarigan 6 years, 7 months ago

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.IsColliding())
    {	    
        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.  

The "spaz" out seems to happen when detecting collision from 2 seperate rectangles, it works "fine" when just going straight down onto a single platform if there are no other platforms around it, but up and sides don't work correctly and if it's close to other platforms then the player might get teleported to another side.

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.

I also tried to do it by "requesting" input, like checking where the player would be the next frame, then if it was going to intersect with the rectangle, don't move, and if it wouldn't intersect, it was free to move, but that was really buggy as well with pretty much the same results as this.

Here's a GIF of what it looks like in action. ( not sure why the actual rectangles are off position, I assume something to do with casting the positions from double to int when reading them in )

 

Advertisement

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: http://hamaluik.com/posts/simple-aabb-collision-using-minkowski-difference/

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: Example: http://scarymazezgame.org/scary-maze/scary-maze-game-7

A better and more reliable approach would be that you do not move the player by default but calculate a prediction vector first pointing to where the player would be after frame update when there would be no collisions between the just state and the predicted state.

Check any collsion rects against this vector using a simple rectangle->line intersection test to see if there would be a collision occuring. If a collision was detected you need to adjust the vector to point to the closest AABB point that is on but not inside the rectangle. Do this for any possible collision target on that vector.

After any collisions were calculated move the player to its new position.

I did this in an AI driver simulation to move vehicles outside of the players field of view that leads to less penetration of the physics cycle.

This topic is closed to new replies.

Advertisement