2
\$\begingroup\$

I'm writing a breakout clone where blocks to hit can be of various sizes and the projectile can move at different speeds, and I seem to be having some trouble getting the collision detection perfectly right.

I have a side-detection algorithm working just fine (projectile reflects in the proper direction when hitting top/side of a block), but sometimes the projectile seems to get stuck inside the blocks, especially when the projectile is moving fast. The function I have simply tells the projectile to reflect it's X or Y axis, but, when the projectile can't get away from the block in time, it gets trapped constantly reflecting on its axes, causing it to "freak out" and get trapped bouncing around inside the block itself.

I attempted to fix this by disabling collision detection for a very short time after each detected collision (defined as PROJECTILE_TICK = 0.05f in the code below), but this doesn't seem to solve the problem, and the projectile still gets trapped.

Is there a standard way this sort of problem is typically handled? I know I'm far from the first person to try and make a breakout clone, so someone must have run into this problem before.

My collision detection function is below. The blocks are referred to as "ptfm" in the code below.

 private void checkPlatformCollisions(float deltaTime) {
    tickTime += deltaTime;

    for(int i = 0; i < projectiles.size(); i++) {
        Projectile proj = projectiles.get(i);

        for(int j = 0; j < platforms.size(); j++) {
            Platform ptfm = platforms.get(j);
            if (OverlapTester.overlapCircleRectangle(proj.boundingRadius,
                    ptfm.bounds)) {

                if(ptfm.breakable) {
                    //this part works just fine; it's the else clause that's screwy

                    }
                } else {

                    //tick is so projectile wont get stuck in a reflection loop
                    //move these to the projectile class later
                    if(tickTime > PROJECTILE_TICK) {

                        //reset tick time
                        tickTime = 0;

                        if(ptfm.type == Platform.PLATFORM_TYPE.WALL) {
                            proj.reflectX();
                            continue;
                        }

                        float angle = (float) Math.atan2(ptfm.pos().y - proj.pos().y,
                                ptfm.pos().x - proj.pos().x);


                        //atan2 returns in radians
                        angle *= Vector2.TO_DEGREES;

                        if(angle < 0) {
                            angle = 360 - (-angle);
                        }

                        if ((angle >= 0 && angle <= 45) ||
                                (angle >= 315 && angle <= 359)) {
                            //hit from top
                            proj.reflectX();
                        } else if ((angle >= 45 && angle <= 135)) {
                            //hit from left
                            proj.reflectY();
                        } else if ((angle >= 135 && angle <= 225)) {
                            //hit from bottom
                            proj.reflectX();
                        } else if ((angle >= 225 && angle <= 315)) {
                            //hit from right
                            proj.reflectY();
                        } else {
                            proj.reflectX();
                        }


                    }

                }

            }

        }
    }
\$\endgroup\$
  • \$\begingroup\$ Where exactly are ptfm.pos() and proj.pos()? The centers? The contact points? Are your blocks all squares? My first guess is that your assumption that the angle between the positions tells you which side of the block the ball hit is wrong. \$\endgroup\$ – Dane Feb 28 '15 at 3:33
  • \$\begingroup\$ The projectile has a bounding circle, and the blocks have bounding rectangles. I know my circle/rectangle detection is fine. Yes, the .pos() methods are their center points, and the atan2 method detects the angle between the projectile and block to see what side it's coming at it from. \$\endgroup\$ – RGrun Feb 28 '15 at 3:44
  • \$\begingroup\$ If your blocks are rectangles and the ball hits the far corner from the bottom, your angle calculation will erroneously detect a side hit. \$\endgroup\$ – Dane Feb 28 '15 at 3:52
  • \$\begingroup\$ I'm having a little trouble visualizing that; at what angle=? degrees would that occur? At angle = 225? \$\endgroup\$ – RGrun Feb 28 '15 at 4:06
3
\$\begingroup\$

While you may have other problems to solve, and I have to assume that there is a best practice for bouncing balls off blocks, one thing that stands out to me is the test for an angle between centers. The current code determines a bottom vs side collision based on four equal quadrants. When the ball is near a corner, that test will give an undesirable reading.

 Rectangle with X pattern division and possible ball position

I really think you can skip the trigonometry and just do a few greater-than comparisons of x and y.

Rectangle with sides extended to divide space

If the ball's center is between the x values of the rectangle's sides, you have either a top or bottom hit. Same goes for y values and side hits. If you have a top or a bottom hit, you do the same reflection. If you're in one of the four corners, which would be vanishingly rare, you have a few options:

  • Default to a top/bottom bounce.
  • Reflect on both x and y (for a surprising reaction that emphasizes the corner hit)
  • Calculate if it is the x or y axis with the greater difference to more accurately determine if you have a top/bottom or side hit.

For this last option, let's say that ax and ay are the x and y of the ball, bx and by are the x and y of the brick, bw is half the width of the brick, and bh is half of the height of the brick. I'm going to use abs() for getting the absolute value. I believe that Java uses Math.abs().

If we define dx to be abs(ax - bx) - bw and dy to be abs(ay - by) - bh, then we check to see if dx > dy. If so, perform a side-hit reflection. Else perform a top-bottom reflection.

In the following image, dx is greater than dy in the blue and magenta areas. dy is greater than dx in the green and yellow areas. dx and dy are both positive values in the yellow and magenta areas. In the green area, dx is negative. In the blue area, dy is negative.

Black rectangle for the brick. Green above an below. Blue to either side. Corners are squares cut in half with the yellow half bordering the green and the magenta half bordering the blue.

\$\endgroup\$
  • \$\begingroup\$ This worked great, thanks very much for your solution. The problem that remains however, is that the ball still gets stuck when hitting a corner, and the "reflect both" option doesn't work very well at strange angles. Your third solution is very interesting, could you post a link to somewhere where I can read more about it and perhaps see an example? \$\endgroup\$ – RGrun Feb 28 '15 at 21:26
  • \$\begingroup\$ Again, thank you. Your explanation is very helpful to a beginner like myself. \$\endgroup\$ – RGrun Mar 1 '15 at 22:12
0
\$\begingroup\$

Oh, hi! I also run into some troubles of the sort when handling collisions. Your problem is that the ball is going too fast, so, by the time your program realised that it collided, it's already inside the brick. Then, in the next iteration collides again, and bounces in the opposite direction, and so on. :D

Solution: Just move the ball outside the box ball.set(x,y);//or something of the sort The easiest way would be to simply

proj.x -=proj.xSpeed; //or whatever way you have to produce this result
proj.y -=proj.ySpeed; //with functions and the sort.

after you detect a collision, and then do the reflectX()//or reflectY() thing. Of course, that'd move your ball too far sometimes (it would look as if it's bouncing on air), so, another approach would be

if(/*collision came from the bottom*/){
    proj.x =ptfm.x + ptfm.xSize + proj.xSize;
}

and 4 more changes depending on the direction of the hit. That'd "pull" the ball out of the brick in the direction it came! Then reflectX()//or reflectY() and you got it bouncing!

There are of course more complex solutions. But I assumed this is probably one of your first attempts, so I thought it was cooler to keep it simple.(Maybe other answers with super cool, accurate ways will appear soon ^^) I hope I helped a little!

Cheers!

\$\endgroup\$
  • \$\begingroup\$ Thanks for the reply. Would it be simpler if I subtracted the ball and brick's positions from each other to get their offset vector, and then do something with that? I know that vector would give me the distance from the ball's center and the brick's center. \$\endgroup\$ – RGrun Feb 28 '15 at 3:01
  • \$\begingroup\$ @RGrun Well, I'm not sure about that. Even if you know the distance, where do you have to put the ball still depends on a bunch of other stuff, like the sizes of both the brick and the ball, and the place where it collided. Hmmm, let me check, I'll come up with something better. \$\endgroup\$ – Alfro Feb 28 '15 at 20:26
  • 1
    \$\begingroup\$ Your help is very appreciated, but I think I managed to figure out something that works. For every frame where the ball doesn't collide with anything, I record it's lastNonCollidingPosition, then, when it does collide, I set its position back to that lastNonCollidingPosition. That seems to have fixed it, as far as I can tell. \$\endgroup\$ – RGrun Feb 28 '15 at 20:45
  • \$\begingroup\$ @RGrun oh, that'll fix it. That will actually do the same as my first suggestion. But, you will get "interesting" bounces (Example) In the previous image you'll see in blue the "accurate" trajectory, and in pink the trajectory using your fix. \$\endgroup\$ – Alfro Feb 28 '15 at 23:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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