Fixed collision detection (game-breaking bug)

Fixed an issue with the ball occasionally clipping through the paddle/bricks or bouncing from a position inside the paddle/bricks
This should never happen, as the ball should be updated to move in a different direction if the ball is clipping in the next frame
This was being caused by a rounding error in the vertexWithinQuad function, where adding the compared volumes introduced a small imprecision and meant the test volume and original volume were not always exactly equal
Cast the two areas as integers when comparing, such that only a discrepancy > 1px would be considered a collision (perfect since we are ultimately rendering bitmaps). Could also be solved by adding a percentage margin of error or increasing precision (both more costly)
This commit is contained in:
Joe Adams 2018-01-09 18:36:05 +00:00
parent a40944d189
commit 589b989b36

View File

@ -175,8 +175,8 @@ int main(void) {
* ----------------
*/
char subSteps = 3; // Draw every N+1 frames, thereby calculating movement more precisely
int targetFPS = 144; // Limit FPS to this value, to stop unneccessary calculations
char subSteps = 2; // Draw every N+1 frames, thereby calculating movement more precisely
int targetFPS = 60; // Limit FPS to this value, to stop unneccessary calculations
char moveX=0;
@ -192,8 +192,8 @@ int main(void) {
ball theBall = {
.x = 0,
.y = 0,
.vX = 0.4f,
.vY = -3.0f,
.vX = 1.5f,
.vY = -5.0f,
.initVX = theBall.vX,
.initVY = theBall.vY,
.radius = 4,
@ -225,7 +225,6 @@ int main(void) {
Uint32 tickrate=SDL_GetTicks()-timer;
timer=SDL_GetTicks();
int fps=1000/(tickrate+1);
//printf("Time to draw: %u, fps: %i \r", tickrate, fps); fflush(stdout);
/*
* ----------------
@ -337,7 +336,7 @@ char vertexWithinQuad(coord point, coord *quad) {
testArea += heronsFormula(point, quad[1], quad[2]);
testArea += heronsFormula(point, quad[2], quad[3]);
testArea += heronsFormula(point, quad[3], quad[0]);
if (testArea == quadArea) {
if ((int)testArea == (int)quadArea) {
return 1;
} else {
return 0;
@ -353,39 +352,44 @@ void updatePaddle(Uint32 tickrate, paddle *thePaddle, char moveX, int winWidth,
void updateBall(Uint32 tickrate, ball *theBall, paddle *thePaddle, int winWidth, int winHeight, brick *bricks, int brickCount) {
double ballNX = theBall->x + theBall->vX * 0.1 * tickrate; // Calculates the position of the ball's centre on the next step
double ballNY = theBall->y + theBall->vY * 0.1 * tickrate;
double paddleRX = thePaddle->x - (double)(winWidth / 2);
double paddleRY = 20 - (double)(winHeight / 2);
double paddleRX = thePaddle->x - (double)(winWidth / 2); // Converts paddle X/Y to coordinates relative to the center of the window as the ball
double paddleRY = 20 - (double)(winHeight / 2); // (Default is absolute, offset from the top left corner of window)
coord paddle[4] = {
{.x = paddleRX, .y = paddleRY },
{.x = paddleRX, .y = paddleRY - thePaddle->height },
{.x = paddleRX + thePaddle->width, .y = paddleRY - thePaddle->height },
{.x = paddleRX + thePaddle->width, .y = paddleRY },
coord paddleQ[4] = {
{ .x = paddleRX, .y = paddleRY }, // Sets up a quad (coord array) to pass to vertexWithinQuad function later
{ .x = paddleRX, .y = paddleRY - thePaddle->height },
{ .x = paddleRX + thePaddle->width, .y = paddleRY - thePaddle->height },
{ .x = paddleRX + thePaddle->width, .y = paddleRY },
};
// Collision with paddle
// (Testing each vertex of ball)
// (Testing each vertex of ball against paddle quad)
char colliding = 0;
int mults[4] = { -1,-1,1,1 };
for (int i = 0, j = 3; i < 4; i++, j++) {
coord ballN = { .x = ballNX + (theBall->radius*mults[i % 4]),.y = ballNY + (theBall->radius*mults[j % 4]) };
if (vertexWithinQuad(ballN, paddle)) colliding = 1;
coord ballN = { .x = ballNX + (theBall->radius*mults[i % 4]), .y = ballNY + (theBall->radius*mults[j % 4]) }; // Constructs pairs of coordinates referring to each vertex of the ball
if (vertexWithinQuad(ballN, paddleQ)) colliding = 1;
}
if (colliding) {
if (theBall->isColliding == 0) {
theBall->isColliding == 1;
theBall->isColliding == 1; // This keeps track of any current attepmts to 'solve' collision
// Without this flag, if the ball ends up within another object and moving slow enough that on the next frame it has not escaped the object,
// it will repeatedly have its velocity multiplied by -1 and get stuck, effectively moving only along one axis. With this flag,
// the game will ignore subsequent collisions, until it is no longer colliding. Note this does not solve the same problem where two objects
// are very close to each other and on exiting one object it enters another, in which case the ball will move through the other objects
if (theBall->x + theBall->radius < paddleRX) { // Left side collision
theBall->vX *= -1.0;
}
else if (theBall->x - theBall->radius > paddleRX + thePaddle->width) { // Right side collision
} else if (theBall->x - theBall->radius > paddleRX + thePaddle->width) { // Right side collision
theBall->vX *= -1.0;
}
else {
} else { // Top or bottom collision
theBall->vY *= -1.0;
theBall->vX = ((ballNX - paddleRX - (double)(thePaddle->width / 2)) / ((double)thePaddle->width / 2))*0.5; // Sets X velocity to be proportional to the relative position of the ball and the paddle on collision
theBall->vX = ((ballNX - paddleRX - (double)(thePaddle->width / 2)) / ((double)thePaddle->width / 2))*theBall->initVX; // Sets X velocity to be proportional to the relative position of the ball and the paddle on collision
}
} else {
// Already solving a collision
@ -429,6 +433,8 @@ void updateBall(Uint32 tickrate, ball *theBall, paddle *thePaddle, int winWidth,
}
}
// Collision with window boundaries
if (ballNX > winWidth / 2 || ballNX < (-1.0*winWidth/2)) { // Collision with walls
theBall->vX *= -1.0;
}
@ -442,6 +448,8 @@ void updateBall(Uint32 tickrate, ball *theBall, paddle *thePaddle, int winWidth,
theBall->vY = theBall->initVY;
}
// Update the ball's position
theBall->x += theBall->vX * 0.1 * tickrate;
theBall->y += theBall->vY * 0.1 * tickrate;
}