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 <.