Directional collisions with rectangles and circles

So taking advice from fellow jgo’ers I ditched the Box2d for my breakout clone. I’m just trying to learn and further my experience with java and libgdx. I’ve setup a simple basic collision system which uses the center of the ball to determine which side the ball is hitting the brick on. It still doesn’t seem right though. It does work, but some of the collisions mess up as in going through bricks sometimes, bouncing the wrong direction, or sliding through the brick. The same happens with my ball and paddle. I know there are many ways to do this and i’ve done a bit of research on it but i’m having a hard time determining which to use and how to implement some of them.

I’ll include my collision code. I have a lot of refactoring to do for the whole project really, but i’ll post the link to my git as well.


public void CheckCollision() {
        if (playerBall.getRect().overlaps(playerPaddle.getRect())) {
            int first = (int) playerPaddle.getPos().x;
            int second = (int) playerPaddle.getPos().x + 16;
            int third = (int) playerPaddle.getPos().x + 32;
            int fourth = (int) playerPaddle.getPos().x + 48;


            if (playerBall.getPos().x + 16 > first && playerBall.getPos().x < second) {
                playerBall.setSpeed(new Vector2(playerBall.getSpeed().x * -1, playerBall.getSpeed().y * -1));
            }

            if (playerBall.getPos().x > second && playerBall.getPos().x < third) {
                playerBall.setSpeed(new Vector2(playerBall.getSpeed().x, playerBall.getSpeed().y * -1));
            }

            if (playerBall.getPos().x > third && playerBall.getPos().x < fourth) {
                playerBall.setSpeed(new Vector2(playerBall.getSpeed().x, playerBall.getSpeed().y * -1));
            }

            if (playerBall.getPos().x > fourth && playerBall.getPos().x < third + 16) {
                playerBall.setSpeed(new Vector2(playerBall.getSpeed().x * -1, playerBall.getSpeed().y * -1));
            }
        }

        for (Brick tBrick : mManager.getBricks()) {
            /*
            if (tBrick.isAlive == false) {
                mManager.destroyBrick(tBrick.getRect(),mManager.getBricks().indexOf(tBrick));
            }
            */
            if (playerBall.getRect().overlaps(tBrick.getRect())) {
                System.out.println("Collision!");
                int ballCenterX = (int)playerBall.getRect().x + (int)playerBall.getRect().getWidth() / 2;
                int ballCenterY = (int)playerBall.getRect().y + (int)playerBall.getRect().getHeight() / 2;
                

                Rectangle brickTopLeft = new Rectangle(
                        tBrick.getRect().x,
                        tBrick.getRect().y + tBrick.getRect().getHeight() / 2,
                        tBrick.getRect().getWidth(),
                        tBrick.getRect().getHeight()
                        );
                
                Rectangle brickTopRight = new Rectangle(
                        tBrick.getRect().x + tBrick.getRect().getWidth() / 2,
                        tBrick.getRect().y + tBrick.getRect().getHeight() / 2,
                        tBrick.getRect().getWidth() / 2,
                        tBrick.getRect().getHeight() / 2
                );

                Rectangle brickBottomLeft = new Rectangle(
                        tBrick.getRect().x,
                        tBrick.getRect().y,
                        tBrick.getRect().getWidth() / 2,
                        tBrick.getRect().getHeight() / 2
                );

                Rectangle brickBottomRight = new Rectangle(
                        tBrick.getRect().x + tBrick.getRect().getWidth() / 2,
                        tBrick.getRect().y,
                        tBrick.getRect().getWidth() / 2,
                        tBrick.getRect().getHeight() / 2
                );

                //Ball collides with top left of brick
                if (playerBall.getRect().overlaps(brickTopLeft)) {
                    if (ballCenterX > brickTopLeft.x && ballCenterY > brickTopLeft.y + brickTopLeft.height) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x, playerBall.getSpeed().y * -1));
                        tBrick.isAlive = false;
                        continue;
                    } else if (ballCenterY < brickTopLeft.y + brickTopLeft.height && ballCenterX < brickTopLeft.x) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x * -1, playerBall.getSpeed().y));
                        tBrick.isAlive = false;
                        continue;
                    }
                }

                //Ball collides with top right of brick
                if (playerBall.getRect().overlaps(brickTopRight)) {
                    if (ballCenterX < brickTopRight.x + brickTopRight.width && ballCenterY > brickTopRight.y + brickTopRight.height) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x, playerBall.getSpeed().y * -1));
                        tBrick.isAlive = false;
                        continue;
                    }else if (ballCenterY < brickTopRight.y + brickTopRight.height && ballCenterX > brickTopRight.x + brickTopRight.width) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x * -1, playerBall.getSpeed().y));
                        tBrick.isAlive = false;
                        continue;
                    }
                }
                //Ball collides with bottom left of brick
                if (playerBall.getRect().overlaps(brickBottomLeft)) {
                    if (ballCenterX > brickBottomLeft.x && ballCenterY < brickBottomLeft.y) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x,playerBall.getSpeed().y * -1));
                        tBrick.isAlive = false;
                        continue;
                    }else if (ballCenterX < brickBottomLeft.x && ballCenterY > brickBottomLeft.y) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x * -1, playerBall.getSpeed().y));
                        tBrick.isAlive = false;
                        continue;
                    }
                }

                //Ball collides with bottom right of brick
                if (playerBall.getRect().overlaps(brickBottomRight)) {
                    if (ballCenterX < brickBottomRight.x + brickBottomRight.width && ballCenterY < brickBottomRight.y) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x, playerBall.getSpeed().y * - 1));
                        tBrick.isAlive = false;
                        continue;
                    }else if (ballCenterX > brickBottomRight.x + brickBottomRight.width && ballCenterY > brickBottomRight.y) {
                        playerBall.setSpeed(new Vector2(playerBall.getSpeed().x * -1, playerBall.getSpeed().y));
                        tBrick.isAlive = false;
                        continue;
                    }
                }

                }
            }
        mManager.bricksToDestroy();

        }

Github:

Thanks for any suggestions, advice, or tips!

Here is a video of it in action. I’m using camstudio and it made the video extremely choppy. The movement in game is smooth…but the collisions aren’t working the best.

G9oJYPtMpeg

A solid and stable collision detection isn’t as trivial as it may seem, even for a relatively simple game as Breakout. Just google for “breakout collision detection”.

The first issue you have is that you only test against the ball’s bounding rectangle. It’s a circle, so you’ll need to test for circle<->rectangle (bricks) or circle<->plane (walls) intersections.

Second you don’t account for “how deep” the intersection happens. In your code, there is no difference if the ball merely touches a brick, or overlaps it for a large area. This check is also needed to calculate the “proper” reflect position, because what should happen is that the ball bounces where its border touches the brick border, not at the position it happens to be at the time of your update. This means that a collision does not only reverse the X or Y speed, but changes the ball position too, depending on how deep the intersection goes.

Which leads to the biggest problem. You are only testing discreet steps in time, limited to update ticks. If the ball goes fast enough, it may be at one side of a brick in one frame, and at the other side in the next frame, not detecting the intersection at all. What you really would need to do is to test against the full area the ball moved from one update frame to the next, which basically is an rotated 2D capsule.

Last but not least, if update ticks are slow and/or the ball moves fast enough, it may even happen that it collides with more than one brick or wall in one frame. This article describes the issue and how to solve it.

Now, some people probably would tell you to just go ahead and use a physics library. Don’t lose heart! This is both an interesting and manageable problem, one you can learn a good deal about math in 2D games.

Breakout is a simple enough game to use swept area collision detection; the maths for both swept circle<->line segment and swept circle<->swept circle are well documented.

Such a technique would give you perfectly accurate collision detection, unlike a rigid body physics engine with penetration & restitution, which sacrifices precision for universality.

Though your approach should really depend on how you want the ball to behave; anything more than perfect elasticity, and a physics engine might be the better choice.