3
\$\begingroup\$

I've read all around the net, watched videos, tried things and in the end I can't make a decent platform collision detection.

I've followed this tutorial.

It didn't work out at 100%. My object stops above ground and can't hit the ground. Anyway, I think the guy got fed up with me asking questions. And his way seems too complicated. In short it's about constant calculations how far are you from a wall/ground and how much steps can you make("step" is also defined), how much DID you make, etc. Too complicated if you ask me. So let's forget about that.

I began anew on my own. Here's what I got:

private void ApplyCollision()
    {
        if (PlayerIsOnPlatform())
        {
            this.Position -= new Vector2(0, 5);  // Holds Player on platform. 
        }
    }

private bool PlayerIsOnPlatform()
    {
        int playerBottom = this.Bounds.Bottom;
        int platformTop = Platform.CurrentPlatform.Bounds.Top;
        bool playerIntersectsPlatform = 
            this.Bounds.Intersects(Platform.CurrentPlatform.Bounds);

        if ((playerBottom >= platformTop) && (playerIntersectsPlatform))
        {
            return true;
        }
        return false;
    }

And gravity and friction:

private void Friction()
    {
        Movement -= Movement * new Vector2(.1f, .1f);
    }

private void Gravity()
    {
        Movement += Vector2.UnitY * .5f;   
    }

Like this, the player bounces a bit and stays about a pixel above ground. And the gravity is always trying to pin the object down even when I somehow manage to land on the platform.

=======

EDIT:

The player stops at the platform top correctly. But now I have another problem:

private void ApplyCollision()
    {
        // Object is above
        if (playerBottom > platformTop && playerBottom < platformBottom && playerIntersectsPlatform)
        {
            this.Position = new Vector2(currentPlayerPosX, platformTop - playerHeight);
        }
        // Object below
        else if (playerTop < platformBottom && playerTop > platformTop && playerIntersectsPlatform)
        {
            this.Position = new Vector2(currentPlayerPosX, platformBottom);
        }
        // Object to the left !! Conflicts with Vertical detection
        if (playerLeft < platformLeft && playerRight > platformLeft && playerIntersectsPlatform &&
            this.Bounds.Center.X <= platformLeft) // Not perfect
        {
            this.Position = new Vector2(platformLeft - playerWidth, currentPlayerPosY);
        }
        // Object to the right !! Conflicts with Vertical detection
        else if (playerRight > platformRight && playerLeft < platformRight && playerIntersectsPlatform)
        {
            this.Position = new Vector2(platformRight, currentPlayerPosY);
        }
    }

This methods works. It's shorter, better and doesn't need my initial boolean methods I had for top, bottom, sides check. But the Left and Right checks still need work. When the player is near the Platform border, he gets pushed/teleported off the edge. The Left check actually allows half of the Player to go off the edge without falling instantly - thanks to this.Bounds.Center.X <= platformLeft. Not perfect. Here's a picture to illustrate the problem: Screenshot

I know why this is happening. It's due to the conditions in the if statements. I guess this calls for collision depth calculation. I find Jon's implementation kinda confusing. And I wonder if that's the only solution. I tried adding another condition. Pseudocode for left check: if 1/4 of Player.Width <= platformLeft >THEN> this.Position = new Vector2(platformLeft - playerWidth, currentPlayerPosY); But due to the friction and speed I have, it doesn't work.

\$\endgroup\$
  • 1
    \$\begingroup\$ What is new Vector2(0, 5) supposed to be ? If player intersects with the platform, then move him directly on the top edge of the platform. So in pseudocode: Player.Bounds.Bottom = Platform.Bounds.Top \$\endgroup\$ – dimitris93 Aug 1 '15 at 16:54
  • \$\begingroup\$ Since MovY is 5.00, the new Vector2(0, 5) subtracts 5 to keep the player from falling through the platform. It's a temporary thing. Currently I'm trying to make what you suggested. I'll write back later. It might take me some time. \$\endgroup\$ – Bruno Aug 1 '15 at 17:16
  • \$\begingroup\$ That's odd! this.Position = new Vector2(currentPosX, platformTop); locks the player in the middle of the platform:screen I think this.Position means that the position coordinates start from the top left corner. But then again, shouldn't the Player top corner overlap the top platform border and not be below it? \$\endgroup\$ – Bruno Aug 1 '15 at 17:51
  • \$\begingroup\$ You are clearly doing something wrong. Why does the player have a Position variable when you have a Rectangle variable (bounds) which contains the position ? If rectangle's x,y is the top left corner, then set the player.y to platform.y - player.height... I think you are doing 2 things wrong. 1) you are updating the position wrongly (not considering that rectangle's top-left corner is x,y) and 2) you are drawing it using the Rectangle and not the position. So in the end player.y is (5 + player.height) pixels below from where it should be \$\endgroup\$ – dimitris93 Aug 1 '15 at 18:12
  • \$\begingroup\$ Position is a property which I use to determine and change the Sprite position. The properties of Rectangle(Top, Bottom, Width, Height) determine only the parameters of the sprite. Not the position! They have only get methods. And they work with integers. The position is a Vector2 data type. But you know what? Your guesses at what's wrong have helped me realize that I didn't know well the values of the Rectangle properties. Your suggestions were spot on. Here's how it should be: this.Position = new Vector2(currentPlayerPosX, platformPosY - playerHeight); This is amazing! Thank you so much!!! \$\endgroup\$ – Bruno Aug 1 '15 at 23:02
2
\$\begingroup\$

I figured I'd come in and explain to you how I used to do my Rectangle collisions in XNA/Monogame. Firstly, I declare an enum that will serve as a way to indicate what axis we are going to be working with. This is important, as our calculations will be done one axis at a time.

public enum Direction
{
    Horizontal,
    Vertical
}

You will also need a function that can determine if two Rectangles are intersecting, and if they are, you are going to want to know how deep this intersection is. The depth will be important when we get to collision response.

The below functions, will do these calculations for you.

// A helper function for the next two
public static bool Intersects(Rectangle player, Rectangle block, Direction direction, out Vector2 depth)
{
    if (direction == Direction.Vertical)
        depth =  new Vector2(0, GetVerticalIntersectionDepth(player, block));
    else 
        depth = new Vector2(GetHorizontalIntersectionDepth(player, block), 0);

    return depth.Y != 0 || depth.X != 0;
}


public static float GetHorizontalIntersectionDepth(Rectangle rectA, Rectangle rectB)
{
    // Calculate half sizes.
    float halfWidthA = rectA.Width / 2.0f;
    float halfWidthB = rectB.Width / 2.0f;

    // Calculate centers.
    float centerA = rectA.Left + halfWidthA;
    float centerB = rectB.Left + halfWidthB;

    // Calculate current and minimum-non-intersecting distances between centers.
    float distanceX = centerA - centerB;
    float minDistanceX = halfWidthA + halfWidthB;

    // If we are not intersecting at all, return (0, 0).
    if (Math.Abs(distanceX) >= minDistanceX)
        return 0f;

    // Calculate and return intersection depths.
    return distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
}


public static float GetVerticalIntersectionDepth(Rectangle rectA, Rectangle rectB)
{
    // Calculate half sizes.
    float halfHeightA = rectA.Height / 2.0f;
    float halfHeightB = rectB.Height / 2.0f;

    // Calculate centers.
    float centerA = rectA.Top + halfHeightA;
    float centerB = rectB.Top + halfHeightB;

    // Calculate current and minimum-non-intersecting distances between centers.
    float distanceY = centerA - centerB;
    float minDistanceY = halfHeightA + halfHeightB;

    // If we are not intersecting at all, return (0, 0).
    if (Math.Abs(distanceY) >= minDistanceY)
        return 0f;

    // Calculate and return intersection depths.
    return distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
}

Now, in your player class (or whatever class you want to have collisions check done in, you would utilize these above functions, in order to check for collisions, and then handle its response.

The following function will take a direction, and calculate to see if you need to provide a collision response or not.

private void CheckCollisions(Direction direction)
{   
    //Get a list of objects to test against
    //You probably already have a mechanism for this
    List<Tile> tiles = quadtree.GetObjects(this.Rectangle);

    //Loop and check for collisions on these objects
    for (int i = 0; i < tiles.Count; i++)
    {
        if (!tiles[i].Collidable) continue;

        //Calculate intersection depth
        Vector2 depth;
        if (Intersects(this.Rectangle, tiles[i].Rectangle, direction, out depth))
        {
            //If an intersection was found - adjust position
            Position += depth;

            //Adjust velocity based on intersection direction
            if (direction == Direction.Horizontal)
                Velocity.Y = 0;
            else
                Velocity.X = 0;
        }
    }
}

Then finally, in your Update method, you will re-position and check for your collisions one axis at a time.

public void Update(GameTime gametime)
{
    float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

    //Velocity Calcs
    Velocity = new Vector2(100, 50);

    //Update one axis at a time
    this.Position.Y += Velocity.Y * elapsed;
    CheckCollisions(Direction.Vertical);

    this.Position.X += Velocity.X * elapsed;
    CheckCollisions(Direction.Horizontal);
}

Hopefully this will help you, or someone else, having similar issues like this.

\$\endgroup\$
  • \$\begingroup\$ I'm getting errors cannot modify the return value of Vectors because they're not variables. Here: this.Position.Y += Velocity.Y * elapsed; and here: Velocity.Y = 0;. Your 'velocity' is 'movement' in my case. I've seen this error a lot lately but don't know how to work around it. So, I get that your collision is finer - calculating the exact penetration and subtracting it. Nice to see another take on collision detection. I've heard there are a lot of ways to go about it. \$\endgroup\$ – Bruno Aug 2 '15 at 16:04
1
\$\begingroup\$

The

if (PlayerIsOnPlatform())
{
    this.Position -= new Vector2(0, 5);  // Holds Player on platform. 
}

is innocorrect. You need to calculate the depth penetration. IE: Calculate how far the player intercepted the other item. XNA has a method for this that returns the rectangle formed when two rectangles intersect. It returns the purple rectangle in this picture: enter image description here

Rectangle collisionRect = Rectangle.Intersects(player.Rect, platform.Rect);
if(intersection.Height > 0)
{
    player.Rect -= intersection.Height;
}

This will push the player outside the other rectangle. Depending on how you programmed gravity it might create a jittery effect on the player where he falls, gets pushed back up, falls, gets pushed back up, falls, etc. To fix this you need to simulate (everything, particularly gravity) and THEN push back up, then draw.

Edit: If you want to check the bounds yourself. These you will have to think about carefully. I think I got them all right.

if(player.Bottom > platform.Top && player.Bottom < platform.Bottom) // Object is above
    player.Rect.Pos += new Vector2(0, platform.Top - player.Bottom);
else if(player.Top < platform.Bottom && player.Top > platform.Top) // Object below
    player.Rect.Pos += new Vector2(0, platform.Bottom - player.Top);
if(player.Left < platform.Right && player.Left > platform.Left) // Object to the left
    player.Rect.Pos += new Vector2(platform.Right - player.Left, 0);
else if(player.Right > platform.Left && player.Right < platform.Right) // Object to the right
    player.Rect.Pos += new Vector2(platform.Left - player.Right, 0);
\$\endgroup\$
  • \$\begingroup\$ Thanks for the suggestion! I do use rectangles and the intersection method. And I got things working. Shiro helped me see what I got wrong. But now I have another problem. I edited the top comment. Care to check it out? \$\endgroup\$ – Bruno Aug 2 '15 at 8:37
  • \$\begingroup\$ @Bruno using my previous suggestion you can just throw and extra if statement saying if(player.Center.Y > platform.Center.Y) { player is above } else { below } if he is above then subtract the depth and if he is below add the depth. (IE: Intersection.Height is the depth. intersection is the purple rectangle) I suppose you could also use the velocity of the player before collision. to tell whether he was below or above. If(collision) { if(v is positive) { moving upwards so he is below} else { moving downwards so he is above. } } \$\endgroup\$ – Andrew Wilson Aug 3 '15 at 9:03
  • \$\begingroup\$ Just before you wrote this comment, I found where my error was. PlayerIsOnPlatform() and PlayerHitsPlatformBottom() were stepping into each other. If playerBottom >= platformTop >>> put player on top. If playerTop <= platformBottom >>> prevent player from passing through platformBottom. In the second condition playerBottom is still greater than platformTop. Thanks for bringing the Center property to my attention. I never thought about it. It was a crucial part in fixing this. All that's left is to finish the side collision. I'll need to refine my conditions even more. Thanks! \$\endgroup\$ – Bruno Aug 3 '15 at 11:22
  • \$\begingroup\$ Ahh, but not that easy. Center.X(or Y) messes with the horizontal collision on the Platform sides. Whatever condition I think of, overlaps with the Vertical Checks. Can't find a way to have both Vertical and Horizontal collision at the same time. *sigh* \$\endgroup\$ – Bruno Aug 3 '15 at 14:47
  • \$\begingroup\$ @Burno I edited something in the answer about bounds checking. \$\endgroup\$ – Andrew Wilson Aug 4 '15 at 8:31

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.