0
\$\begingroup\$

I'm working on a 2D SDL C++ project, and I'm having a bit of trouble with collision detection. Currently the player collides with the edges of the level and with the SDL_Rect objects in the level, however it's not reacting how I want it to. This is the code I've got so far:

void CollisionManager::playerCollision(float xVelocity, float yVelocity, float* xNewPos, float* yNewPos, SDL_Rect currentPos)
{
    // --- Test collision with level boundaries. ---
    if (((*xNewPos) < 0) || ((*xNewPos) + currentPos.w > currWorldWidth))
        (*xNewPos) -= xVelocity;

    if (((*yNewPos) < 0) || ((*yNewPos) + currentPos.h > currWorldHeight))
        (*yNewPos) -= yVelocity;

    SDL_Rect currentRect;
    bool collisionX, collisionY;

    // --- Test collision with game objects. ---
    for (std::vector<Shape>::iterator it = cObjects->begin(); it != cObjects->end(); ++it)
    {
        currentRect = it->GetRect();

        // X-Axis collision
        collisionX = ((*xNewPos) + currentPos.w > currentRect.x) && (currentRect.x + currentRect.w > (*xNewPos));

        // Y-Axis collision
        collisionY = ((*yNewPos) + currentPos.h > currentRect.y) && (currentRect.y + currentRect.h > (*yNewPos));

        if (collisionX && collisionY)
        {
            (*xNewPos) -= xVelocity;
            (*yNewPos) -= yVelocity;
        }
    }
}

First it tests the player against the level boundaries. If it detects collision on the X-axis, it subtracts xVelocity from the new X position, and likewise with the Y-Axis and Y position. This way when the player collides with the level boundaries, it slides along the edges.

When testing collision with the level objects, I have to test both axis at the same time to test for a collision, but this way if for example, the top of the player is colliding with the bottom of the object, and I'm pressing Up and Left, the player won't move until I let go of the Up button. I can't test for collision with just one axis as before, because I might be colliding with several other objects on the screen on the same Y-axis, but on the other side of the screen.

I'm thinking what I need to do is find out which side of the object I'm on by testing the intersection depth of the two SDL_Rect objects, then only revert the position on that axis back to what it was, so if I'm colliding on the left or right side, set xNewPos back to its old position, and if it's colliding on the top or bottom, set yNewPos back to its old position. I'm having trouble getting my head around how to do this though, especially if I'm colliding with two objects at once. Can anyone give me any ideas, or if there's a better way of doing this?

Edit - Okay, I think I've made progress. I've replaced the two lines of code in the if (collisionX && collosionY) statement with this:

objWidth = currentRect.x + currentRect.w;
            objHeight = currentRect.y + currentRect.h;

            // Collision from top or bottom
            if (((playerHeight > currentRect.y) || (currentPos.y < objHeight)) &&
                (currentPos.x > currentRect.x && currentPos.x < objWidth)
                || (playerWidth > currentRect.x && playerWidth < objWidth))
                    (*yNewPos) -= yVelocity;

            // Collision from side
            if (((playerWidth > currentRect.x) || (currentPos.x < objWidth)) &&
                (currentPos.y > currentRect.y && currentPos.y < objHeight)
                || (playerHeight > currentRect.y && playerHeight < objHeight))
                    (*xNewPos) -= xVelocity;

It seems to be working for the most part. If I collide with one of the objects, the player slides along the edges instead of just stopping. However when I'm sliding up the right side, the player gets stuck on the top right corner. It looks like it happens when the bottom of the player reaches the top of the rectangle. The same thing happens if I'm sliding along the bottom towards the left. When the player reaches the bottom left corner, it gets stuck, just as the right side of the player reaches the corner. I'm looking at this now, but can anyone see any immediate problems with my code?

Edit - Got it. This code gives me the results I'm after:

if (collisionX && collisionY)
        {
            objWidth = currentRect.x + currentRect.w;
            objHeight = currentRect.y + currentRect.h;

            // Collision from side
            if (
                (currentPos.x <= objWidth || playerWidth >= currentRect.x) 
                && (currentPos.y > currentRect.y && currentPos.y < objHeight) 
                || (playerHeight >= currentRect.y && playerHeight < objHeight)
                )
                verticalColl = true;

            // Collision from top or bottom
            if (
                (currentPos.y <= objHeight || playerHeight >= currentRect.y) 
                && (currentPos.x > currentRect.x && currentPos.x < objWidth) 
                || (playerWidth >= currentRect.x && playerWidth < objWidth)
                )
                horizontalColl = true;

            if (horizontalColl)
                (*yNewPos) -= yVelocity;

            if (verticalColl)
                (*xNewPos) -= xVelocity;
        }

I also had to make sure I was testing for >= rather than just > or <.

\$\endgroup\$
  • \$\begingroup\$ To clarify: previously, you've handled collisions by stepping just Y-axis motion first, checking and resolving collisions, and then stepping X-axis motion, and resolving those collisions? Why exactly does this approach not work anymore? \$\endgroup\$ – Tyyppi_77 Dec 25 '18 at 17:41
  • \$\begingroup\$ @Tyyppi When working out collision with the edge of the level, all I have to do is test if the left or right side of the player is touching the left or right side of the level, which gives me the X-axis collision, then test if the top or bottom of the player is touching the top or bottom of the level for Y-axis collision. I can do them separately. With objects that are inside the level, I need to test both the X and Y axis simultaneously to work out if the player is colliding. \$\endgroup\$ – st_nick5 Dec 26 '18 at 13:55
  • \$\begingroup\$ Ah I see what you mean by that, thanks for the clarification. \$\endgroup\$ – Tyyppi_77 Dec 26 '18 at 14:04
  • \$\begingroup\$ If I try to test one axis at a time, it's going to prevent me from going anywhere where I'm colliding with any object on any axis. So in this image, the player can go anywhere in the bottom right square, but any further than that and it's colliding with another object, and can't move any further. i.imgur.com/6TL8vE7.png \$\endgroup\$ – st_nick5 Dec 26 '18 at 14:07
0
\$\begingroup\$

Got it. This code gives me the results I'm after:

if (collisionX && collisionY)
        {
            objWidth = currentRect.x + currentRect.w;
            objHeight = currentRect.y + currentRect.h;

            // Collision from side
            if (
                (currentPos.x <= objWidth || playerWidth >= currentRect.x) 
                && (currentPos.y > currentRect.y && currentPos.y < objHeight) 
                || (playerHeight >= currentRect.y && playerHeight < objHeight)
                )
                verticalColl = true;

            // Collision from top or bottom
            if (
                (currentPos.y <= objHeight || playerHeight >= currentRect.y) 
                && (currentPos.x > currentRect.x && currentPos.x < objWidth) 
                || (playerWidth >= currentRect.x && playerWidth < objWidth)
                )
                horizontalColl = true;

            if (horizontalColl)
                (*yNewPos) -= yVelocity;

            if (verticalColl)
                (*xNewPos) -= xVelocity;
        }

I also had to make sure I was testing for >= rather than just > or <. This is working for me, but does anyone have any other/better ways of doing this for future reference?

\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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