You are on page 1of 5

XNA 4.

0 Break Out Tutorials Part 3 Collisions

I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they are read in order. You can find the list of tutorials on the XNA 4.0 Tutorials page of my web site. I will be making my version of the project available for download at the end of each tutorial. It will be included on the page that links to the tutorials. This is a beginner tutorial on creating a Break Out style game. I will be writing tutorials in the future on creating the game using object-oriented programming principles. The tutorial is more to introduce XNA with a more or less complete game. It won't go into state management but it will be a functional brick breaking game. In this tutorial I will be covering collision between the ball and the bricks. Load up your solution from last time. Checking if the ball and the brick collide is easy enough. You can use a test like you do with the ball and the paddle. The hard part is having the ball deflect in the proper direction. You need to know where the ball collides with the brick to determine how to deflect the ball. To perform the collision detection the first thing to do is to update the ball class so that you can find the rectangle that describes the ball's location. Update the Ball.cs class to the following.
using using using using System; System.Collections.Generic; System.Linq; System.Text;

using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace BreakingOut { class Ball { Vector2 motion; Vector2 position; Rectangle bounds; float ballSpeed = 4; Texture2D texture; Rectangle screenBounds; public Rectangle Bounds { get { bounds.X = (int)position.X; bounds.Y = (int)position.Y; return bounds; } } public Ball(Texture2D texture, Rectangle screenBounds) {

bounds = new Rectangle(0, 0, texture.Width, texture.Height); this.texture = texture; this.screenBounds = screenBounds;

public void Update() { position += motion * ballSpeed; CheckWallCollision(); } private void CheckWallCollision() { if (position.X < 0) { position.X = 0; motion.X *= -1; } if (position.X + texture.Width > screenBounds.Width) { position.X = screenBounds.Width - texture.Width; motion.X *= -1; } if (position.Y < 0) { position.Y = 0; motion.Y *= -1; } } public void SetInStartPosition(Rectangle paddleLocation) { motion = new Vector2(1, -1); position.Y = paddleLocation.Y - texture.Height; position.X = paddleLocation.X + (paddleLocation.Width - texture.Width) / 2; } public bool OffBottom() { if (position.Y > screenBounds.Height) return true; return false; } public void PaddleCollision(Rectangle paddleLocation) { Rectangle ballLocation = new Rectangle( (int)position.X, (int)position.Y, texture.Width, texture.Height); if (paddleLocation.Intersects(ballLocation)) { position.Y = paddleLocation.Y - texture.Height; motion.Y *= -1; } } public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, position, Color.White); } } }

I added a new field bounds that describes the rectangle of the ball. I also added a read only accessor to get the rectangle. In the accessor I set the X part of the rectangle to the X part of the position of the ball, casting it to an integer. I do something similar for the Y part of the coordinates. I then return the rectangle.

In the constructor I create a Rectangle with the height and width of the ball texture. This way I don't have to recalculate the width and height of the bounds of the ball when it is needed for collision detection. It is a small optimization that will improve performance a little. Now, in the Game1 class we need to check for collision between the bricks and the ball. Modify the Update method to the following.
protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); paddle.Update(); ball.Update(); foreach (Brick brick in bricks) { brick.CheckCollision(ball); } ball.PaddleCollision(paddle.GetBounds()); if (ball.OffBottom()) StartGame(); base.Update(gameTime); }

I added an foreach loop that loops through all of the bricks. I then call the CheckCollision method of the brick passing in the ball to see if they collide. The next step will be in the CheckCollision method of the brick class. To start with update the code to the following.
public void CheckCollision(Ball ball) { if (alive && ball.Bounds.Intersects(location)) { alive = false; } }

So, you check to see if the brick is currently in play using the alive field. You then check if the Bounds property of the ball intersects with the location field. If it does I set the alive field of the brick to be false. If you run the game now the the ball will bounce around the screen and if it hits a brick it will remove the brick from play. The ball does not bounce off the brick though, it just keeps on going. Let's try the same idea when the ball collides with the paddle on the brick. What I will do is add a method to the Ball class called Deflection that will handle deflecting the ball in the right direction. Update the CheckCollision method of the Brick class to the following.
public void CheckCollision(Ball ball) { if (alive && ball.Bounds.Intersects(location)) { alive = false; ball.Deflection(this); } }

I just call the Deflection method of the ball passing in this, which is the current object that the ball collided with. Now, add this method the Ball class.

public void Deflection(Brick brick) { motion.Y *= -1; }

Now, that sort of works but it doesn't work quite right. First problem is when the ball hits the first few bricks it isn't deflecting properly. Part of the problem is it is hitting two bricks at the same time then hitting a third so all three bricks remove but the ball isn't deflecting properly. To start with if the ball collides with two bricks at the same time you only want it to deflect once. To handle that I will add in a field to the Ball class, collided. Add this field to the Ball class with the other fields.
bool collided;

Next step is to change the Update method to set collided to be false. This way it is updated each frame of the game. Change the Update method to the following.
public void Update() { collided = false; position += motion * ballSpeed; CheckWallCollision(); }

The next step is to update the Deflection method to only deflect if collided is false and then set collided to true. Change the Deflection method to the following.
public void Deflection(Brick brick) { if (!collided) { motion.Y *= -1; collided = true; } }

That is working a little better but if the ball hits the side of a brick it is deflecting vertically instead of horizontally. I'm going to leave it to you to smooth out the kinks with the ball deflecting. I will tell you that the best way to do it is the following. If the ball intersects with a brick you find where the ball will be in the next frame and construct a line segment. You then create line segments for each of the sides of the brick the ball is colliding with. Depending which line segments the ball line segment the intersects tells you how to deflect the ball. There is all kinds of source code on the Internet on intersecting line segments. What I am going to add in this tutorial is having the ball start in a random direction and have the ball speed up as the game progresses. First step is to have the ball start in a some what random direction. The ball will still start moving right and up but not always (1, -1). Change the SetInStartPosition method of the ball class to the following and the following constant with the ballSpeed field.
const float ballStartSpeed = 8f; public void SetInStartPosition(Rectangle paddleLocation) { Random rand = new Random(); motion = new Vector2(rand.Next(2, 6), -rand.Next(2, 6)); motion.Normalize(); ballSpeed = ballStartSpeed;

position.Y = paddleLocation.Y - texture.Height; position.X = paddleLocation.X + (paddleLocation.Width - texture.Width) / 2;

In the SetInStartPosition method I create a Random object for creating random numbers. I then create the motion vector. The X value of the motion vector will be between 2 and 5 and the Y value between -2 and -5. I then call the Normalize method on motion. What this does is make the motion vector a vector with a length of 1. This means that motion becomes a direction vector. You will find this a lot in game programming. You will often need the direction but with a length of 1. It helps things have a uniform motion. I then set ballSpeed to ballStartSpeed. The last step is to have the ball speed up over time. The simplest way to do this is to increase the ballSpeed field by a small amount each frame of the game. You can do that in the Update method. Change the Update method of the Ball class to the following.
public void Update() { collided = false; position += motion * ballSpeed; ballSpeed += 0.001f; } CheckWallCollision();

I'm going to end this tutorial here, and the series. It is a pretty complete game and I've told you how to change the few things that can be improved. I encourage you to visit the news page of my site, XNA Game Programming Adventures, for the latest news on my tutorials. Good luck in your game programming adventures! Jamie McMahon