Physics for a Block Breaker Game

Laser Frenzy EndA friend and I were discussing recently some of the special physics requirements of block breaker / Breakout / Arkanoid style games. When I put together the physics for Breaking Block, I already had a pretty strong idea of how I wanted the physics to work, though I noticed that the internet had mostly half-answers. I found that many people were asking about Breakout-style physics. Since complete information is so scarce, I’d like to share some lessons that I’ve learned while developing the physics for Breaking Block.

Be Wary of Using Physics Engines for Such a Simple Case

Physics engines are by their nature generalized, and a block breaker game, when implemented properly, has some very specific physics rules. The generalized rules of engines get in the way. Beyond the obvious things like needing to disable gravity and ensuring preservation of kinetic energy, sometimes, no matter what you do, an object takes a weird bounce in an engine-based rigidbody collision, and the angle of deflection is nowhere near acceptable (i.e. ball bounces off corner of block and travels horizontally, killing the game).
You can try to hack around this stuff all you want. At the end of the day, though, just applying movement in script using angle and velocity is cleaner, more performant, and more correct for this case. Using a physics engine for a basic block breaking game is the proverbial “Shooting a fly with an elephant gun”. Consider that the physics rules for the original arcade Breakout weren’t even implemented in a computer program at all; the rules were hard-wired into logic circuits.

Simple Ball Reflection for blocks

Handling deflections off of blocks and walls is trivial if you keep track of your ball’s angle as a direction vector, and keep your blocks orthogonal. On a vertical hit, just invert the y component of the direction vector, and on a horizontal hit, invert the x component. One common question is how to tell whether a hit is vertical or horizontal. You could inspect the collision details and look at surface normals, but with orthogonal blocks, that’s a bit more complex than what is needed. Just take the difference in position (delta) between the ball and block, scale it by the inverse aspect ratio of the block (for example, if the block is twice as long as it is high, divide the x component of the delta by 2), and see which component of the delta is smaller. A smaller y means a vertical hit, and a smaller x means a horizontal hit.
Here’s an example, with an additional Sign check added to make sure the ball is traveling toward the block. That check prevents the ball from destroying two adjacent blocks simultaneously.

var delta = (ball.transform.position - block.transform.position);
// apply aspect ratio via the scaleFactor vector
// For a horizontal block twice a wide as high, use Vector3(0.5f, 1f, 1f)
// For a vertical block twice as high as wide, use Vector3(1f, 0.5f, 1f)
// For a square block, use Vector3(1f, 1f, 1f), and so forth.
delta.Scale(scaleFactor); 
if(Mathf.Abs(delta.x) >= Mathf.Abs(delta.y))
{
    // scaled delta x was larger than delta y. This is a horizontal hit.
    if(Mathf.Sign(-direction.x) == Mathf.Sign(delta.x))
    {
        ballDirection.x = -ballDirection.x;
        ScoreBlockHit();
    }
}
else
{
    // scaled delta y was larger than delta x. This is a vertical hit.
    if(Mathf.Sign(-direction.y) == Mathf.Sign(delta.y))
    {
        ballDirection.y = -ballDirection.y;
        ScoreBlockHit();
    }
}

Keep the Ball In-Bounds, No Matter What

Don’t rely on colliders and other softer barriers to keep the ball in-bounds. For instance, a ball might get stuck between a moving block and a wall and pop through. Things get chaotic. Include a hard bounds check, and reflect the ball in the appropriate direction if it leaves the bounds of play.

if((direction.x > 0f && transform.position.x > fieldMinimumX) ||
    (direction.x < 0f && transform.position.x < fieldMaximumX))
{
    direction.x = -direction.x;
}

if(direction.y > 0f && transform.position.y > fieldMaximumY + 0.1f)
{
    direction.y = -direction.y;
}

Let the Paddle Control the Ball Angle

It is very common for the contact position between the ball and the paddle to govern the angle the ball will take. This is very easy to do using the horizontal difference in position between the ball and the angle. Remember to take into account any change in paddle size if your game allows it. More about that ClampBallAngle() function in a moment.

var newAngle = (
        -(ball.transform.position.x - paddle.transform.position.x) * 60f / enlargeScale
    ) + 90f;
newAngle = ClampBallAngle(newAngle);

Prevent Degenerate Angles

In every FUN block breaking game I’ve played, I’ve never seen the ball travel at an angle that was close to horizontal or close to vertical. Horizontal angles are not fun because inevitably the player winds up waiting for the ball to trolley-car endlessly between the two walls. Vertical angles are not fun because they make it too easy to aim shots and volley balls with minimal effort. Steps need to be taken to prevent the ball from taking one of these angles.
If you are only using reflections to modify the angle everywhere else, then this only needs to be done when the ball is launched or collides with the paddle. This function is written with that in mind, a more general solution would be needed if the angle needs to be fixed in other situations. Note that in this case, it is much more convenient to express the ball angle as an actual angle rather than a direction vector.

private float ClampBallAngle(float angle)
{
    if(angle > 150f)
    {
        return 150f;
    }
    if(angle < 30f)
    {
        return 30f;
    }
    if(angle <= 90f && angle > 75f)
    {
        return 75f;
    }
    if(angle >= 90f && angle < 105f)
    {
        return 105f;
    }
    return angle;
}

Make Sure Your Collisions Happen

One drawback to not using a physics engine for movement is that you lose the engine's ability to do some of the more dynamic collision detection for cases where an object completely passes through another between updates. There are several ways to compensate for this:

  • Use a shorter update loop, and make sure it is a loop with a guaranteed time interval. In Unity I am using FixedUpdate for the really important physics, and have the fixed update rate set at 100fps.
  • Limit your top speeds. Do the math to ensure that an object at its top speed cannot move more than 25% (arbitrary number, adjust as necessary) of the way through another object in a single update frame.
  • Use raycasting. Cast a ray from an object's previous position to its current position to see if it passed through anything. I did not wind up having to take this approach with Breaking Block, but it is viable and I have used it in past games. Unity actually has a very handy SphereCast() function that is like a raycast, but accounts for an object's radius.

Track Dead-Object State to Prevent Incorrect Simultaneous Collisions

This is critical to things like multi-ball play, laser power ups, anything that makes it so that a block can be hit by two separate objects in the same frame. It may not apply to all engines, but in Unity at least, when you request that an object be destroyed, the actual destruction is deferred until the end of the update loop. This causes a situation where a block can be scored as destroyed twice, by two different balls, by a ball and a laser, by a ball and an alien, etc.
To prevent this, the first collision should set a death flag on the block. The collision code should check this flag, and take no action if the block is already dead.

In Conclusion

Hopefully these tips will help you in your quest to build a block breaker game. A block breaker game is a good exercise for any aspiring game developer, with a strongly-defined problem, limited scope, and lots of room for creativity.