Ball and brick collision handling

2019-06-13 01:20发布

I have made the game, "Breakout". A small fun side-project.

Now, I usually do not make games, so collision-handling is not something I normally think about.

I have a paddle, a ball and some bricks.

For now, when there is a collision (I draw rectangles around each of the objects mentioned), I simply change the Y value of the ball to -Y.

This works fine, EXCEPT if the ball hits a brick from the side (either East or West). The side-effect is not pretty and ruins the gameplay.

I think I can safely assume that instead of the above technique, I need to change the X value to -X when this happens.

So far I have: if (ballRect.IntersectsWith(brickRect))

ballRect and brickRect being rectangles around each object.

Now, what if I created a rectangle around the eastern border of the brick, the western border, etc? I guess the width would be about a pixel.

If collision happens with western or eastern rectangle, then the balls X value should be -X. And vice versa.

What about the corners though? Should I just randomly choose which rectangle to control of x corner?

Or perhaps should I make a rectangle around each corner? the rectangle being 1*1 in side. If there is a collision => -x AND -y values of the ball?

Please share your thoughts.

Here is the process so far:

    foreach (var brick in Bricks)
    {
        if (brick.IsAlive)
        {
            var brickRect = new Rectangle(brick.X, brick.Y, BrickWidth, BrickHeight);
            if (ballRect.IntersectsWith(brickRect)) //Ball has hit brick. lets find out which side of the brick
            {
                var brickRectNorth = new Rectangle(brick.X, brick.Y + BrickHeight, BrickWidth, 1);
                var brickRectSouth = new Rectangle(brick.X, brick.Y, BrickWidth, 1);

                var brickRectEast = new Rectangle(brick.X, brick.Y, 1, BrickHeight);
                var brickRectWest = new Rectangle(brick.X + BrickWidth, brick.Y, 1, BrickHeight);

                if (ballRect.IntersectsWith(brickRectNorth) || ballRect.IntersectsWith(brickRectSouth))
                {
                    //STUFF that makes ball.y = -ball.y
                }
                if (ballRect.IntersectsWith(brickRectWest) || ballRect.IntersectsWith(brickRectEast))
                {
                    //STUFF that makes ball.x = -ball.x
                }
            }
        }
    }

1条回答
劫难
2楼-- · 2019-06-13 01:48

Rather than looking for rectangle intersections, I'd intersect the actual edges. At the corner, your ball is touching two edges simultaneously, so its motion vector should be affected by both.

I would keep the single rectangle for collision detection, since that reduces the number of rectangles you need to test in your outer loop, but then once a collision with a brick has been detected, go into an inner loop to detect which edge it was that was hit. If you just test each edge and adjust the vector accordingly for each one, the corner will come for free (as long as you don't break out of the loop when you find the first intersecting edge).

Edit: In response to your updated question:

Actually, this is how I would do it (given your code, this appears to be C# 3.0, so that's what I've assumed below):

foreach(var brick in Bricks) {
    if(brick.IsAlive) {
        var brickRect = new Rectangle(brick.X, brick.Y, BrickWidth, BrickHeight);
        if(ballRect.IntersectsWith(brickRect)) {
            // Ball has hit brick.  Now let's adjust the ball's vector accordingly

            // Convenience variables.  Compiler will probably inline.
            var brickLeft = brick.X;
            var brickRight = brick.X + BrickWidth;
            var brickTop = brick.Y;
            var brickBottom = brick.Y + BrickHeight;

            var ballLeft = ball.X - ball.Radius;
            var ballRight = ball.X + ball.Radius;
            var ballTop = ball.Y - ball.Radius;
            var ballBottom = ball.Y + ball.Radius;

            // Test which vector(s) we need to flip
            bool flipX = (ballRight >= brickLeft || ballLeft <= brickRight);
            bool flipY = (ballTop >= brickBottom || ballBottom <= brickTop);

            // Flip the vectors (there are probably ways to optimize this,
            // too, but without seeing your code I can't tell).
            if(flipY) {
                // Stuff that makes ball.y = -ball.y
            }

            if(flipX) {
                // Stuff that makes ball.x = -ball.x
            }
        }
    }
}

Basically, the point is that since you already know the ball actually intersects the brick, you can simplify to a simple box test, which is much faster. Also, there's no need to create extra rectangles for the edges -- just use the edges of the rectangle you already have.

查看更多
登录 后发表回答