From 589b989b366baec0f05977e6108c36087194965c Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 9 Jan 2018 18:36:05 +0000 Subject: [PATCH] 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) --- breakout/breakout.c | 52 ++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/breakout/breakout.c b/breakout/breakout.c index 878ac2d..6564eff 100644 --- a/breakout/breakout.c +++ b/breakout/breakout.c @@ -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; }