Saturday, March 12, 2011

Breakout Week 3: When Balls Collide

This week was all about collision detection and collision response.  The plan was to add the ball to the Breakout game, get it moving around the screen and bouncing off the walls and the paddle.  I probably did much more research than was required this week since I spent a lot of time reading about various collision detection algorithms and the physics of collision response.  It turns out that didn't actually use any of the techniques I read about since a very simple technique works well enough for Breakout.  It was worthwhile reading all that though, if for no other reason than I now have a much better appreciation for what I need to learn.

So, research aside, the coding up of the ball itself was very easy, it only took about an hour and half and most of that was spent tweaking various parameters to get the "feel" right. So on to the code.

As with the paddle, there is a class for the Ball with LoadContent, Update and Draw methods that are called by the Breakout class.  The Ball tracks it's own state in these instance variables:

private Texture 2D sprite;
private Vector2 position;
private Vector2 direction;
private int speed;

This is slightly different to the paddle in that we store the speed as a scalar value and the direction as a vector.  The direction is a unit vector so it contains no speed information, which is why we need the speed as a separate variable.  The reason I did it this way is that the ball can move in both x and y directions, unlike the paddle, so using a unit vector allows use to calculate directional changes for the ball without needing to worry about the length of the vector, since it will always be 1.

The ball also has a few other instance variables that hold initial states, ranges of speed and position and whether the ball is currently active or dead.

So, once again, all the interesting stuff happens in the Update method:

internal void Update(GameTime gameTime) {
    if (state == State.Active) {
        UpdatePosition(gameTime);

        if (position.Y > screenHeight) {
            state = State.Dead;
        } else {
            HandleCollisions();
        }
    } else if (state == State.Dead && 
           Keyboard.GetState().IsKeyDown(Keys.Space)) {
        LaunchBall();
    }
}



The first thing the update method does is check if the ball is active or not.  If it is isn't the player can launch a new ball by pressing the space key. The LaunchBall method just sets the state to active and initialises the position and velocity of the ball, so I won't cover it here.  If the ball is active, the first thing we do is update it's position:

private void UpdatePosition(GameTime gameTime) {
    float time = gameTime.ElapsedGameTime.TotalSeconds;
    position = position + direction * (float)(speed * time);
}

This is the familiar motion equation we used for the paddle, so nothing new here, except that the velocity vector is split up into direction and speed components so we need to multiply them back together here.

Once that is done, the Update method checks if the ball has gone of the bottom of the screen and should become dead, if not it then checks for the collisions. The HandleCollisions method first calls HandleBoardCollisions which checks if the ball has hit either the top, left or right edges of the screen.

private void HandleBoardCollisions() {
    if (position.Y < 0) {
        position.Y = 0;
        direction.Y = -direction.Y;
    }

    if (position.X < 0) {
        position.X = 0;
        direction.X = -direction.X;
    } else if (position.X + sprite.Width > screenWidth) {
        position.X = screenWidth - sprite.Height;
        direction.X = -direction.X;
    }
}

So here all we do is check if the ball has gone off the screen on each axis and if so we reverse the direction of the ball along that axis and move it's position back into the screen.  This will ensure that the ball doesn't get stuck on the edge and reversing the direction on the axis gives us a realistic reflection off the wall so that the angle of incidence is maintained.

Once this is done, when then check to see if the ball hits the paddle.  This was what most of my reading and my research went into. I already new that it was pretty easy to check if a rectangle intersects another but I was worried about the movement of the ball. For example, consider this case:


The ball starts the frame at position A but after it's position is updated it ends the frame at position B.  If we do a bounding box check against the paddle at either position we are not going to get a collision, but clearly the ball has hit the paddle.  There are algorithms for solving this called sweep tests where basically you create a geometry for the moving object that covers its movement through the entire frame, so the above situation becomes this:

And we check whether those two geometries intersect and at what time within the frame they would.  The most appropriate algorithm for this particular case seems to be the Axis-Aligned Bounding Box sweep test.

However, this is only really an issue for small fast moving objects.  If the object is going to be moving less than it's dimensions on each axis in each frame then it is not an issue.  I wasn't sure if it was going to be an issue for my ball and paddle collisions so I decided to start with the simplest case and see how it looks. The simplest case is just a standard bounding box test and it seems to work well enough, the ball doesn't pass through the paddle even when it is travelling at pretty high speeds. Of course if the frame rate dropped or we changed size of the ball or paddle this might change, so I wouldn't be surprised if I end up implementing a sweep test anyway.

This type of collision detection was really easy to do in XNA, well it's pretty easy anyway but XNA makes it very easy. The Rectangle class has an Intersects method that checks whether the rectangle intersects with another. This is what we use in the HandlePaddleCollisions method of the Ball class:

private void HandlePaddleCollision() {
    if (direction.Y > 0 && paddle.Bounds.Intersects(this.Bounds)) {
        direction.Y = -direction.Y;
        position.Y = paddle.Bounds.Y - sprite.Height;

        direction.X = ((float)Bounds.Center.X - paddle.Bounds.Center.X) / 
                      (paddle.Bounds.Width / 2);
        direction = Vector2.Normalize(direction);

        // Increase the speed when the ball is hit
        speed += speedIncrement;
        speed = Math.Min(speed, maxSpeed);
    }
}

Line 2 of this method does the check.  Also notice that we only do this check if the ball is moving in a downward direction. This is important because we don't want to check if the ball is moving up, since it is both a waste and could lead to bugs where the ball gets trapped inside the paddle.

So once we know there is a collision, we need to do something about it. This is where I spent some time reading about collision response, in particular, the sections on collision response in my copy of Physics for Game Programmers (that I have had for almost 10 years and not read enough of) and Chris Hecker's excellent article on collision response. This stuff is really interesting and is definitely something I want to get explore more, maybe even for the Breakout game later on.  But in the interest of getting the ball working this week I opted for a simpler solution. This is implemented in the body of the HandlePaddleCollision method.

First of all we need to reverse the y direction of the ball, this "bounces" it up and sends towards to top of the screen. We also need to move the ball outside of the paddle so it doesn't get stuck. This is done on line 4.

Then comes my cheat for not having proper physics.  If you just change the y direction the ball will always be travelling straight up and down, which will lead to a very boring Breakout game. The player needs to have some control over the direction of the ball.  I did a bit of research into how other people have implemented this, I found a few references to splitting the paddle up into thirds and setting the x direction to a fixed number based on which third it hits, sort of like this:


But that would be a bit boring and predictable.

Looking at some old Breakout videos it looks like the angle of the ball is based on the distance from the paddles center, the further from the center the sharper the angle.  So I ended up using this formula:

xDirection = ballCenter - paddleCenter 
             -------------------------
                 paddleWidth / 2

This will set the x direction to a value between -1 and 1 based on the position it hit the paddle. So you get a range like this on the paddle:



Once the X value of the direction vector is set on line 6 we need to normalise the vector again on line 8 so it remains a unit vector.

There is one downside to this approach, since the Y value of the direction is always going to -1 or 1 and the range of the X value can be is -1 to 1, this means that you only have a range of angles of +/- 45 degrees. This might not be that big a deal though, the range can always by increased by adding some constant to the denominator of the xDirection formula anyway.

Once the direction has been set we increment the speed on lines 11 and 12 so it gets a bit harder as the player keeps the ball alive.  And that does it for ball movement, this is what it looks like:



I can assure the framerate is much better in real life. I might have to find some better screen capturing software, if anyone can recommend some please do in the comments.

Handling collisions like this is not without its problems though. You can have the situation where the ball hits the side of the paddle but then gets propelled out of the top of the paddle, since we are just checking that the ball is within the paddle. To resolve this you would probably need some sort of sweep test followed by an intersection test to find which edge of the paddle the ball hit and then reflect it off that edge instead of always assuming it hit the top edge. I'll leave this for later though.

Next week I'll start implementing the wall of bricks. This should cover more collision detection, dynamic drawing of sprites and maybe some particle effects.

2 comments:

Steven J. St. John said...

Really well-written and just what I was looking for (teaching my kids programming). Thanks!

chilli_501 said...

great article. helped me a lot with my game. you should do more