Search code examples
xna2dphysicsmonogamerectangles

2D physics / collision formula not working as intended


I have a 2D tile matrix (of walls for now) and for each wall I check if my player's next Rectangle (next as in where it will be in next frame) is colliding with that wall, and if it is, I want my player to stand right next to it, without going inside of it.

Here is the formula I came up with (this is just for left and right collision):

//*move entity
if (KState.Down(Keys.W)) en.AddForce(new Vector2(0, -10));
if (KState.Down(Keys.S)) en.AddForce(new Vector2(0, 10));
if (KState.Down(Keys.A)) en.AddForce(new Vector2(-10, 0));
if (KState.Down(Keys.D)) en.AddForce(new Vector2(10, 0));

foreach(Item i in map.Items)
{
    //resolve left-right collision
    if (en.NextBody(elapsedTime).Intersects(i.Body))
    {
        //check distance between top left corners
        int distance = (int)Math.Abs(i.Position.X - en.Position.X);

        //stop player in his horisontal movement
        en.AddForce(new Vector2(-en.Force.X, 0));

        //place player next to the wall his about to collide with
        en.Position = new Vector2(en.Position.X - distance + i.Body.Width, en.Position.Y);
    }
}

//stop player in place
if (KState.Clicked(Keys.Space)) en.AddForce(en.Force*-1);

en.Update(elapsedTime);
**

Which gave me this result (note that fps is not stable and that currently rectangles are squares 32x32):

description

Top line of text is players Rectangle, and bottom one is Rectangle of the wall that player collided with last.
Now, when the rocks (player) is moving to the left, I'm holding left key for some time, and when going to the right, I'm holding right key for some time.

Problem: As you can see in gif, when player's going left, he's moving 1 pixel to and from left wall. And when he's moving right, he bounces once, and then stays right next to the right wall. I don't want any bouncing but I don't know how to fix this in logic that I'm using.

Also, if you know good logic for how to resolve Rectangle collision so that they are not intersecting from any sides, I'd appriciate sharing it with me.

Edit 1: I managed to resolve collision to the right problem. Instead of the code above I've put

int sign = Math.Sign(i.Position.X - en.Position.X);
en.AddForce(new Vector2(-en.Force.X, 0));
//wall is right and player is left
if (sign > 0) en.Position = new Vector2(i.Body.Left - en.Body.Width, en.Position.Y);
//wall is left and player is right
if (sign < 0) en.Position = new Vector2(i.Body.Right, en.Position.Y);

but the problem with the left side collision is the same. I tried
if (sign < 0) en.Position = new Vector2(i.Body.Right + 1, en.Position.Y); and
if (sign < 0) en.Position = new Vector2(i.Body.Right - 1, en.Position.Y); just in case, but the problem is the same (on "...-1..." case player gets stuck in wall).

Edit 2: As @paste requested, here is the code for NextBody(float elapsedTime) from player class.

public Rectangle NextBody(float elapsedTime)
{ return new Rectangle((int)(force.X * elapsedTime) + body.X, (int)(force.Y * elapsedTime) + body.Y, body.Width, body.Height); }

Solution

  • This is what's happening:

    The first time the objects collide while the rocks are moving left, the algorithm works as it should. It sees that the objects are overlapping, it sets the force to Vector2.Zero, and it moves the en so that it is touching the item's right side. Good. Your object is stopped and in the right place.

    What happens in the next frame is where things go wrong. You set up your force vector, and then call NextBody(). Let's just look at the X-coordinate. It gets set to:

    (int)(force.X * elapsedTime) + body.X
    

    What does this evaluate to? force.X is non-zero, and elapsedTime is also non-zero, but they're less than 1, so when they get converted to int, the result is zero. Therefore, your body is in the same place as it was before, which means the Rectangles do not overlap. This means your collision response code gets skipped and your force vector is still not zero!

    Then, further down, you call your player's Update() method. I'm guessing that in that code you update the player's Position and Body. But since force is not zero, and you are using a Vector2 for Position, the x-coordinate will end up being something like 31.841304... instead of 32. If you use that number to calculate the new Body and use it in your Draw method, you'll end up drawing it at x = 31. The next time you check, it'll detect a collision, and the player will bounce out again.

    There are a number of ways to fix this. What I do in my collision code is to actually move the objects first and keep a record of where they were in the previous frame. That way, the last adjustment to their position is when they are moved out of the collision area.