Collision detection problem

Started by
5 comments, last by Vendetta8247 9 years, 7 months ago

Hello,

So I have two boxes, a blue box and a red box. The blue represents a player and the red box represent a wall. What I'm trying to do is, when the blue collide with the red box, the blue box should stop moving. This is working for the most part when the blue box collide with the red box from the left side. However its not working from the right side. I don't know how to fix it. Here is a video showing the problem.


and here is my code in the update function


if (PlayerRectangle.Intersects(BoxRectangle))
{
    if (Position.X + 32 >= BoxRectangle.X )
       Position.X = BoxRectangle.X - 32;
    else if (Position.X <= BoxRectangle.X + BoxRectangle.Width)
       Position.X = BoxRectangle.X + BoxRectangle.Width;
}

KeyboardState keyboardState = Keyboard.GetState();

if (keyboardState.IsKeyDown(Keys.W))
    Position.Y -= (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);

if (keyboardState.IsKeyDown(Keys.S))
    Position.Y += (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);

if (keyboardState.IsKeyDown(Keys.D))
    Position.X += (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);

if (keyboardState.IsKeyDown(Keys.A))
    Position.X -= (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);

PlayerRectangle = new Rectangle((int)Position.X, (int)Position.Y, (int)32, (int)32);

--------------------------------------------------------------------------------------------------------------------------------

[Update 22/8/2014]

Ok so I updated my code and I used this method to detect collision. But I still can not get rid of the stutter problem where the player goes into the wall if the user holds the keyboard key. Also there is another problem where if the player collide from the upper or bottom side of wall and then the user presses the right or left key, the player will jump in the opposite direction instead of sliding to the right or left.

here is the code:-


Vector2 Position;
Rectangle PlayerRectangle, BoxRectangle;
float Speed = 0.25f;

enum Direction { Up, Right, Down, Left };
Direction direction;

protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    KeyboardState keyboardState = Keyboard.GetState();

   if (keyboardState.IsKeyDown(Keys.Up))
   {
       Position.Y -= (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
       direction = Direction.Up;
   }

   if (keyboardState.IsKeyDown(Keys.Down))
   {
       Position.Y += (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
       direction = Direction.Down;
   }

   if (keyboardState.IsKeyDown(Keys.Right))
   {
       Position.X += (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
       direction = Direction.Right;
   }

   if (keyboardState.IsKeyDown(Keys.Left))
   {
        Position.X -= (float)(Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
        direction = Direction.Left;
    }

    if (PlayerRectangle.Intersects(BoxRectangle))
    {
         if (direction == Direction.Right)
             Position.X = BoxRectangle.Left - PlayerRectangle.Width;
         else if (direction == Direction.Left)
             Position.X = BoxRectangle.Right;

         if (direction == Direction.Down)
             Position.Y = BoxRectangle.Top - PlayerRectangle.Height;
         else if (direction == Direction.Up)
             Position.Y = BoxRectangle.Bottom;
     }

     PlayerRectangle = new Rectangle((int)Position.X, (int)Position.Y, (int)32, (int)32);

     base.Update(gameTime);
}

and here is the video

--------------------------------------------------------------------------------------------------------------------------------

[Update 23/8/2014]

finally I fixed all my issues, I just wanted to share my final code.


Vector2 Velocity, Position;
Rectangle PlayerRectangle, BoxRectangle;
float Speed = 0.01f;

protected override void Update(GameTime gameTime)
{
     // Allows the game to exit
     if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
         this.Exit();

     KeyboardState keyboardState = Keyboard.GetState();

     if (keyboardState.IsKeyDown(Keys.Up))
         Velocity.Y -= (float)(Speed);
     else if (keyboardState.IsKeyDown(Keys.Down))
         Velocity.Y += (float)(Speed);
     else
         Velocity.Y = 0;

     if (keyboardState.IsKeyDown(Keys.Right))
         Velocity.X += (float)(Speed );
     else if (keyboardState.IsKeyDown(Keys.Left))
         Velocity.X -= (float)(Speed);
     else
         Velocity.X = 0;

     if (PlayerRectangle.Intersects(BoxRectangle))
     {
         float rightEdgeDistance = BoxRectangle.X - (Position.X + PlayerRectangle.Width);
         float leftEdgeDistance = BoxRectangle.X + BoxRectangle.Width - Position.X;

         float TopEdgeDistance = BoxRectangle.Y - (Position.Y + PlayerRectangle.Height);
         float BottomEdgeDistance = BoxRectangle.Y + BoxRectangle.Height - Position.Y;


         float Left_Right_SmallerDistance = Math.Min(Math.Abs(rightEdgeDistance), Math.Abs(leftEdgeDistance));
         float Top_Bottom_SmallerDistance = Math.Min(Math.Abs(TopEdgeDistance), Math.Abs(BottomEdgeDistance));

         float smallerDistance = Math.Min(Math.Abs(Left_Right_SmallerDistance), Math.Abs(Top_Bottom_SmallerDistance));

         if (smallerDistance == Math.Abs(leftEdgeDistance))
         {
              Position.X = BoxRectangle.X + BoxRectangle.Width;
              Velocity.X = 0;
         }
         else if (smallerDistance == Math.Abs(rightEdgeDistance))
         {
              Position.X = BoxRectangle.X - PlayerRectangle.Width;
              Velocity.X = 0;
         }
         else if (smallerDistance == Math.Abs(BottomEdgeDistance))
         {
              Position.Y = BoxRectangle.Y + BoxRectangle.Height;
              Velocity.Y = 0;
         }
         else if (smallerDistance == Math.Abs(TopEdgeDistance))
         {
              Position.Y = BoxRectangle.Y - PlayerRectangle.Height;
              Velocity.Y = 0;
         }
     }

     Position += new Vector2((float)(Velocity.X * gameTime.ElapsedGameTime.TotalMilliseconds), (float)(Velocity.Y * gameTime.ElapsedGameTime.TotalMilliseconds));
     PlayerRectangle = new Rectangle((int)Position.X, (int)Position.Y, (int)32, (int)32);

     base.Update(gameTime);
}
Advertisement

When you're checking which side of the player is intersecting with the rectangle the first part will always evaluate to true because if the player is at the right side of the rectangle then
his position will always be greater than the rectangle's position. So when you check for collision it always executes the first half of your if statement.


if (PlayerRectangle.Intersects(BoxRectangle))
{
    if (Position.X + 32 >= BoxRectangle.X )

    //this part will always be true because if the player is at the right side of the rectangle
    //then his position is already greater than the box's position so this is the only part that executes

       Position.X = BoxRectangle.X - 32;
    else if (Position.X <= BoxRectangle.X + BoxRectangle.Width)

    //this part won't execute because the first statement is always true if there is an intersection.
       Position.X = BoxRectangle.X + BoxRectangle.Width;
}


Alternatively if you switch the two statements and run the second test first then the collision will only work from the right side of the box. If there is ever a collision between the box and the player then both of those statements will always be true, whichever one is checked first is the one that's executed. You checked the collision from the left side of the box first so only the left side is executed. when you try to collide from the right side of the rectangle you get pushed to the left.


Instead of doing the above, an easy way to decide where to move the player is to calculate the smallest distance required to move the player in order for the two rectangles to stop intersecting.

I don't know XNA so i don't know if the functions abs and min are available to you.


//calculate the smallest distance required to move the player in order to escape collision

//good to have named constants so you know what the numbers refer to
const int playerWidth = 32;

//distance from the right edge of the player to the left edge of the rectangle
float rightEdgeDistance = BoxRectangle.x - (Position.x + playerWidth);

//distance from the left edge of the player to the right edge of the rectangle
float leftEdgeDistance = BoxRectangle.x + BoxRectangle.Width - Position.x;

//figure out the smaller distance, use the absolute values of both calculations because
//the distance might be negative due to the calculations
float smallerDistance = min(abs(rightEdgeDistance), abs(leftEdgeDistance));

//move the player towards the edge of the rectangle that requires the smallest distance to move
if(smallerDistance == abs(leftEdgeDistance))
{
    //distance from right edge of rectangle to the left edge of player is the smallest
    Position.X = BoxRectangle.X + BoxRectangle.Width;

} else if(smallerDistance == abs(rightEdgeDistance))
{
    //distance from left edge of rectangle to right edge of player is smallest
    Position.X = BoxRectangle.X - playerWidth;
}

This usually works if your character is moving in small increments that way when you collide with something only a small portion of the player is actually intersecting with the other object. if your character moves too fast then he risks teleporting to the other side of the rectangle.

Thank you that helped. But I feel there is a better way to implement this. The problem I have with this method is as you said, if the player runs too fast he will just go through and the collision will not work.

Also there is the problem of the player moving half way though the box when you keep pressing right or left key. Its really annoying. If you don't know what I'm talking about, you can see it in the video. when I start pressing the right key the player goes into the box before stopping. and when I stop pressing the right key the position of the player gets corrected.

again thank you very much. I really appreciate your help.

I think the reason why the player is going halfway into the rectangle before being pushed out is because of when you handle the collision detection. Let's try stepping through the code one at a time, lets say you start to the left of the rectangle and you press and hold the right key.

At the beginning of the code you gave us you do the collision handling. Because you're not intersecting with the rectangle right now, the collision handling section is skipped because there is no collision.

Next you have your keystates. The game detects that you are holding the right key so it moves your player to the right, then it draws the player in this position. Now this is important to note that the player is drawn right after he moved, meaning the collision detection has not happened yet.

Next you go back to the beginning of the loop and here you run the collision handling because, this time, your player is inside the rectangle. After the collision handling is finished the player is no longer in the rectangle.

But once you reach the input handling you move the player back inside the rectangle and then when you draw the player, he is drawn inside the rectangle because you won't handle collision until the next frame.

If you let go of the right key, then the collision handling will occur like always, but this time the player won't move because you are no longer handling any input. The player is drawn outside the rectangle because you haven't moved him.

So in short the player is drawn right after he moves, before the collision handling can occur so he is drawn inside the rectangle and his position isn't corrected until the next frame. But even if his position is corrected the next frame, if you are still holding down the right key he moves into the rectangle again so in the end you're still in the rectangle.

if you restructure your code so the collision handling occurs after the player actually moves then your problem with the player going inside the rectangle should be fixed.

As for the problem with things moving too fast you'll have to find a way to deal with it yourself. For the most part you can just limit the speed of everything so they can't go through objects, but if thats not possible there are other methods. Since you're using axis-aligned rectangles (non rotated rectangles) you could check out http://www.gamedev.net/page/resources/_/technical/game-programming/swept-aabb-collision-detection-and-response-r3084 to learn about swept AABB collision detection. This is basically where you predict where the player traveled during the frame and check if the course he traveled through intersects with any objects and handle collision accordingly. I think that limiting the speed of the objects are good enough if you're just starting out with game development, but if you want you can always implement more advance techniques if your game needs it.

I see, ok thank you. I will check it out.

Update.

I also had the same problem as it is shown in your second video. The problem is that it handles two different collisions at a time. A top-bottom and left-right collison. As long as I understand it right. I have managed to solve it by checking it this way:


!player.collisionRect.isOnBotOf(a.rectangle) && !player.collisionRect.isOnTopOf(a.rectangle))

It is my checking for the collision isOnRight.

This topic is closed to new replies.

Advertisement