O'Reilly logo

Learning XNA 4.0 by Aaron Reed

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Collision Detection

So, you have a pretty good thing going thus far. Players can interact with your game and move the three rings around the screen—but still there’s not a lot to do. You need to add some collision detection in order to take the next step.

Collision detection is a critical component of almost any game. Have you ever played a shooter game where you seem to hit your target but nothing happens? Or a racing game where you seem to be far away from a wall but you hit it anyway? This kind of gameplay is infuriating to players, and it’s a result of poorly implemented collision detection.

Collision detection can definitely make or break a gameplay experience. The reason it’s such a make-or-break issue is because the more precise and accurate you make your collision-detection algorithms, the slower your gameplay becomes. There is a clear trade-off between accuracy and performance when it comes to collision detection.

One of the simplest and fastest ways to implement collision detection is through the bounding-box algorithm. Essentially, when using a bounding-box algorithm, you “draw” a box around each object on the screen and then check to see whether the boxes themselves intersect. If they do, you have a collision. Figure 4-3 shows the three rings and skull ball sprites with these invisible boxes surrounding the two objects.

Bounding boxes around your objects

Figure 4-3. Bounding boxes around your objects

To implement the bounding-box algorithm in the current game, you’ll need to create a rectangle for each sprite based on the position of the sprite and the width and height of the frames for that sprite. The code will make more sense if you change the position of the skull ball sprite to a variable, as you’ve done with the rings sprite. Add the following class-level variable, which will be used to hold the position of the skull ball sprite. Also, initialize the variable to the value that you’re currently setting as the position of the sprite when you draw it, (100, 100):

Vector2 skullPosition = new Vector2(100, 100);

Next, pass the skullPosition variable as the second parameter to the spriteBatch.Draw call where you actually draw the skull ball.

OK, now that you have a variable representing the position of the skull ball sprite, you can create a rectangle using that variable and the size of the skull ball frame and check to see whether it intersects with a similarly created rectangle for the rings sprite.

Add the following method to your Game1 class, which will create rectangles for each sprite using the XNA Framework Rectangle struct. The Rectangle struct has a method called Intersects that can be used to determine whether two rectangles intersect:

protected bool Collide(  )
{
    Rectangle ringsRect = new Rectangle((int)ringsPosition.X,
        (int)ringsPosition.Y, ringsFrameSize.X, ringsFrameSize.Y);
    Rectangle skullRect = new Rectangle((int)skullPosition.X,
        (int)skullPosition.Y, skullFrameSize.X, skullFrameSize.Y);

    return ringsRect.Intersects(skullRect);

}

Next, you need to use the new Collide method to determine whether the objects have collided. If so, you’ll want to perform some action. In this case, you’re just going to close down the game by calling the Exit method if the sprites collide. Obviously, this isn’t something you’d want to do in a real game, because just quitting the game when something like a collision occurs will seem like a bug to a player. But because we just want to see collision detection in action, this will work for now.

Add the following code to the end of your Update method, just before the call to base.Update:

if (Collide(  ))
    Exit(  );

Compile and run the game. If you move your rings object too close to the ball, the application will shut down.

You may notice that the ball and the rings never actually touch. Any idea why this is? If you look at the sprite sheet for the rings (see Figure 4-4), you’ll see that there’s a fair amount of space between the images of each frame. The distance is compounded even further when the large ring rotates horizontally. All that whitespace gets added to the collision check because you’re using your frame size variable as the size of the object when building the rectangle for your collision check.

One way to rectify this is to adjust your sprite sheet to not have so much whitespace. Another way is to create a smaller rectangle for use in the collision detection. This smaller rectangle must be centered on the sprite and therefore needs to be offset slightly from each edge of the actual frame.

All the whitespace in the rings image creates a less-than-accurate collision check when using the frame size for collision detection

Figure 4-4. All the whitespace in the rings image creates a less-than-accurate collision check when using the frame size for collision detection

To create a smaller rectangle, define an offset variable for each sprite, which will indicate how much smaller in each direction the collision check rectangle is than the overall frame. Add these two class-level variables to your project:

int ringsCollisionRectOffset = 10;
int skullCollisionRectOffset = 10;

Next, you’ll use these variables to construct a rectangle that is slightly smaller than the actual frame size. Adjust your Collide method as shown here, and you’ll have more accurate collision detection:

protected bool Collide(  )
{
    Rectangle ringsRect = new Rectangle(
        (int)ringsPosition.X + ringsCollisionRectOffset,
        (int)ringsPosition.Y + ringsCollisionRectOffset,
        ringsFrameSize.X − (ringsCollisionRectOffset * 2),
        ringsFrameSize.Y − (ringsCollisionRectOffset * 2));
    Rectangle skullRect = new Rectangle(
        (int)skullPosition.X + skullCollisionRectOffset,
        (int)skullPosition.Y + skullCollisionRectOffset,
        skullFrameSize.X − (skullCollisionRectOffset * 2),
        skullFrameSize.Y − (skullCollisionRectOffset * 2));

    return ringsRect.Intersects(skullRect);

}

Compile and run the game to try out the new collision detection. It should be much more accurate using this method.

Tip

There is a closely related algorithm that uses a sphere instead of a box. You could use that here as well, especially given that your current objects are circular; however, you’ll be using some noncircular objects in future chapters, so stick with the bounding-box method for now.

Even now that you’ve fine-tuned the algorithm a bit, running the application will show that the collision detection is not 100% accurate. In this limited test, the deficiencies are easy to see. The goal in any game, however, is not necessarily to get collision detection 100% accurate, but rather to get it accurate to the point where the player won’t know the difference.

This may sound like cheating, but in reality, it boils down to a performance issue. For example, let’s say you’re working with a sprite that’s not circular, such as an airplane. Drawing a single box around an airplane will yield some very inaccurate collision detection. You can get around that by adding multiple, smaller boxes to your airplane and checking for collisions between each of these smaller boxes and any other object in the game. Such a bounding-box layout is shown in Figure 4-5.

The example on the left will be fairly inaccurate, whereas the one on the right will greatly improve the accuracy of the algorithm. But what problem will you run into? Let’s say you have two planes in your game and you want to see whether they collide. Instead of one set of calculations for the two planes, you now have to compare each box in each plane against each box in the opposite plane. That’s 25 sets of calculations to compare two planes! If you added more planes to your code, the calculations required would go up exponentially and could eventually affect the speed of your game.

Airplane with single bounding box (left) and multiple bounding boxes (right)

Figure 4-5. Airplane with single bounding box (left) and multiple bounding boxes (right)

There is a way to improve performance by merging the two methods. That is, you can first check for collisions against objects using a box surrounding the entire object, such as the one shown on the left in Figure 4-5. Then, if that collision check comes back as a potential collision, you can dig deeper into the subboxes like the ones on the right in Figure 4-5 and compare those boxes for collisions.

You can tell that collision detection really is a balancing act between performance and accuracy. In spite of all the extra effort, even collision checks made with all the boxes surrounding the plane on the right side of Figure 4-5 won’t be 100% accurate. The goal once again is to make the collision-detection close enough to not adversely affect gameplay or performance.

There is yet another way to speed up collision detection that I should mention. Dividing the game window into a grid-based coordinate system allows you to do a very simple check to determine whether two objects are even close enough to warrant running a collision check. If you keep track of the current grid cell in which each object is positioned, you can check to make sure one object is in the same grid cell as another object before running the collision-detection algorithm on those two objects. This method will save a good number of calculations in each frame and can also positively affect the speed of the game.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required