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

Chapter 4. User Input and Collision Detection

As cool as it was to see a nice-looking set of rings spinning around and realize that you’d made that happen yourself, there’s a long way to go with XNA. Although the animated object looked nice, it didn’t do anything, and you had no control over its movement. What fun is a game where there’s no interaction on the part of the player? In this chapter, we explore user input and collision detection as ways to make your game actually do something besides look nice and pretty.

This chapter uses the code that you built at the end of Chapter 3 (the animated three rings sprite). Open that project and make the changes discussed in this chapter there.

More Sprites

If you’re going to have a user-controlled object and build in some collision detection against other objects, you’re going to need at least one more object on the screen. Let’s add another animated sprite to your project.

Instead of using the same three rings image, we’ll use a different image for the second animated sprite. Along with the source code for this book, you’ll find the code for this chapter. In the AnimatedSprites\AnimatedSprites\AnimatedSpritesContent\Images folder, you’ll find an image called skullball.png. Add that image file to the project in the same way you’ve added previous image files (right-click the Content\Images folder in Solution Explorer, select AddExisting Item, and then browse to the skullball.png image and add it to the solution).

Next, you’ll need to create a number of variables that will allow you to draw and animate the skull ball sprite. These variables should look somewhat familiar to you, as they are very similar to the ones you used in Chapter 3 to draw and animate the three rings sprite. Add the following class-level variables at the top of your Game1 class:

Texture2D skullTexture;
Point skullFrameSize = new Point(75, 75);
Point skullCurrentFrame = new Point(0, 0);
Point skullSheetSize = new Point(6, 8);
int skullTimeSinceLastFrame = 0;
const int skullMillisecondsPerFrame = 50;

The skull ball image frames are 75×75 pixels, and there are six columns and eight rows in the sprite sheet. You’ll want to change the names for the variables you’re using in this game to draw and animate the three rings now, to avoid confusion due to having multiple sprites in your game. Add the word “rings” at the beginning of each variable name, and change all the references to those variables; this will help you keep things straight as you move through this chapter. The rings variables should now be declared as:

Texture2D ringsTexture;
Point ringsFrameSize = new Point(75, 75);
Point ringsCurrentFrame = new Point(0, 0);
Point ringsSheetSize = new Point(6, 8);
int ringsTimeSinceLastFrame = 0;
int ringsMillisecondsPerFrame = 50;

Compile the project and make sure that you don’t have any compilation errors due to the renaming of these variables. If you do, remember that the variable names should be the same as in the previous project; you’ve just added the word “rings” to the beginning of each name. Fix any errors until the game compiles properly.

Chapter 5 will walk you through some basic object-oriented design principles that will make adding new sprites much easier. For now, you just want to get to some user input and collision detection, so let’s add the code for the skull ball animation.

Load your skull ball image into the skullTexture variable in the LoadContent method in the same way you loaded your three rings image:

skullTexture = Content.Load<Texture2D>(@"Images\skullball");

Next, add the code that will move the current frame through the sequence of frames on the sprite sheet. Remember, this is done in the Update method. Because you’re already doing this with the three rings sprite, you can just copy the code for the three rings animation and rename the variables to make it work:

skullTimeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
if (skullTimeSinceLastFrame > skullMillisecondsPerFrame)
{
    skullTimeSinceLastFrame −= skullMillisecondsPerFrame;
    ++skullCurrentFrame.X;
    if (skullCurrentFrame.X >= skullSheetSize.X)
    {
        skullCurrentFrame.X = 0;
        ++skullCurrentFrame.Y;
        if (skullCurrentFrame.Y >= skullSheetSize.Y)
            skullCurrentFrame.Y = 0;
    }
}

Finally, you need to draw the sprite on the screen. Remember that all drawing takes place in the Draw method. Once again, you already have code in that method that draws the three rings sprite, and you can just copy that code and change the variable names to draw the skull ball image. So that the two sprites aren’t drawn on top of each other, change the second parameter of the skull ball’s Draw call to draw the image at (100, 100) rather than at (0, 0). Your skull ball Draw call should look like this:

spriteBatch.Draw(skullTexture, new Vector2(100, 100),
    new Rectangle(skullCurrentFrame.X * skullFrameSize.X,
        skullCurrentFrame.Y * skullFrameSize.Y,
        skullFrameSize.X,
        skullFrameSize.Y),
        Color.White, 0, Vector2.Zero,
        1, SpriteEffects.None, 0);

Compile and run the application at this point and you’ll see both images animating, as shown in Figure 4-1.

Two animated sprites doing their thing
Figure 4-1. Two animated sprites doing their thing

Not bad! In only a few moments, you’ve added a completely new animated sprite to your game. Software development often yields moments of excitement and accomplishment, but that feeling seems to be amplified with game development because of the added visual (and later auditory) senses involved. These two animating objects look pretty cool, but things are only just getting interesting. Now you’ll learn how to control the objects on the screen and give your application some user interaction.

User input in XNA is done via a combination of multiple device options: the keyboard, the mouse, the Xbox 360 controller, Xbox 360 peripherals, and the Windows Phone 7 Series touch screen and accelerometer. Mouse input is never available on the Xbox 360 or Windows Phone 7.

In this chapter, you’ll add support for the keyboard, the mouse, and the Xbox 360 controller into your game.

In the previous chapter, we discussed polling versus registering for events. The difference between the two strategies is really never more visible than when dealing with input devices. Traditional Windows programmers are used to registering for events such as key-down events or mouse-move events. With this programming model, the application performs some actions and then, when the application has idle time, the messages are pumped into the application and the events are processed.

In game development, there is no idle time, so it would be too expensive to enable developers to register for events. Instead, it is up to you as the developer to constantly poll input devices asking whether the player has performed any actions on those devices.

That’s a slightly different way of looking at input and other messages and events, but once you figure it out, game development will make a lot more sense.

Keyboard Input

Keyboard input is handled via the Keyboard class, which is in the Microsoft.XNA.Framework.Input namespace. The Keyboard class has a static method called GetState that retrieves the current state of the keyboard in the form of a KeyboardState structure.

The KeyboardState structure contains three key methods that will give you most of the functionality you’ll need, as shown in Table 4-1.

Table 4-1. Key methods in the KeyboardState structure

Method

Description

Keys[] GetPressedKeys( )

Returns an array of keys pressed at the time of the method call

bool IsKeyDown(Keys key)

Returns true or false depending on whether or not the key represented by the parameter is down at the time of the method call

bool IsKeyUp(Keys key)

Returns true or false depending on whether the key represented by the parameter is up at the time of the method call

As an example of the use of the Keyboard class, if you wanted to check to see whether the “A” key was pressed, you’d use the following line of code:

if(Keyboard.GetState(  ).IsKeyDown(Keys.A))
    // BAM!!! A is pressed!

In this game, you’ll modify the code to allow the user to control the three rings sprite, moving it up, down, left, or right with the arrow keys.

The three rings sprite is currently hardcoded to be drawn at (0, 0), but to move it around the screen, you need to be able to change the position at which you draw the sprite. You’ll need to use a Vector2 variable to represent the current position at which to draw the three rings sprite. You’ll also want to add a variable to represent the speed at which the three rings sprite will move. Because the speed at which you move the three rings sprite won’t change during the course of the game, you can make that variable a constant. Add these class-level variables at the top of your class:

Vector2 ringsPosition = Vector2.Zero;
const float ringsSpeed = 6;

Also, make sure that you change the second parameter of the SpriteBatch.Draw method you’re using to draw the three rings sprite to use your new ringsPosition variable rather than the hardcoded position indicated by Vector2.Zero. The second parameter of that Draw method represents the position at which to draw the sprite.

Now you’ll need to add code to check whether the up, down, left, or right arrow keys are pressed and, if any of them are, move the sprite’s position by changing the value of the ringsPosition variable.

Where should you put the code to check for input? Remember that there are only two methods to choose from when determining where to put certain logic into an XNA game loop: Draw, which is for drawing objects, and Update, which is essentially for everything else (keeping score, moving objects, checking collisions, etc.). So, go ahead and add the following code to the end of your Update method, just before the call to base.Update:

KeyboardState keyboardState = Keyboard.GetState(  );
if (keyboardState.IsKeyDown(Keys.Left))
    ringsPosition.X −= ringsSpeed;
if (keyboardState.IsKeyDown(Keys.Right))
    ringsPosition.X += ringsSpeed;
if (keyboardState.IsKeyDown(Keys.Up))
    ringsPosition.Y −= ringsSpeed;
if (keyboardState.IsKeyDown(Keys.Down))
    ringsPosition.Y += ringsSpeed;

What about the code itself? Wouldn’t an if/else statement be more efficient than four if statements? Well, yes, it would. But an if/else statement would only allow you to move in one direction at a time, whereas using four separate if statements allows you to move diagonally as well (for example, combining the up and left key inputs).

Also note that instead of calling the GetState method of the Keyboard class in each if statement, you’re calling it only once and then reusing the result from that call. This is because the call to GetState is fairly expensive, and this approach reduces the number of times you have to make that call.

Compile the project and run it. You should see that now you can move your three rings sprite object around the screen, as shown in Figure 4-2.

Look out—spinning rings on the move!
Figure 4-2. Look out—spinning rings on the move!

Mouse Input

XNA provides a Mouse class to interact with the mouse that behaves very similarly to the Keyboard class. The Mouse class also has a GetState method that you can use to get data from the mouse in the form of a MouseState struct. The Mouse class also has another method worthy of note: void SetPosition(int x, int y). This method will—you guessed it—allow you to set the position of the mouse. This position is relative to the upper-left corner of the game window.

The MouseState struct has several properties that will help you understand what is happening with the mouse at the particular moment in time when you called GetState. These properties are detailed in Table 4-2.

Table 4-2. Important properties of the MouseState struct

Property

Type

Description

LeftButton

ButtonState

Returns the state of the left mouse button.

MiddleButton

ButtonState

Returns the state of the middle mouse button.

RightButton

ButtonState

Returns the state of the right mouse button.

ScrollWheelValue

int

Returns the total accumulated movement of the scroll wheel since the game started. To find out how much the scroll wheel has moved, compare this value to the previous frame’s scroll wheel value.

X

int

Returns the value of the horizontal position of the mouse in relation to the upper-left corner of the game window. If the mouse is to the left of the game window, the value is negative. If the mouse is to the right of the game window, the value is greater than the width of the game window.

XButton1

ButtonState

Returns the state of additional buttons on some mice.

XButton2

ButtonState

Returns the state of additional buttons on some mice.

Y

int

Returns the value of the vertical position of the mouse in relation to the upper-left corner of the game window. If the mouse is above the game window, the value is negative. If the mouse is below the game window, the value is greater than the height of the game window.

You may have noticed that by default the mouse cursor is hidden when the mouse is dragged over an XNA game window. If you want to display the cursor in an XNA window, you can do so by setting the IsMouseVisible property of the Game class to true.

Regardless of whether or not the mouse is visible, the MouseState struct returned from a call to GetState will always hold the current state of the mouse device.

Let’s make the movement of the mouse control the three rings sprite’s movement around the game window. Leave the keyboard controls added in the previous section in place, and you’ll end up with multiple ways to control the sprite.

Because the MouseState’s X and Y properties tell you the current position of the mouse cursor, you can just assign the position of the three rings sprite to the current position of the mouse.

However, because you’re allowing the player to use the keyboard as well, you can’t always just set the three rings sprite’s position to the position of the mouse. If you did, the three rings sprite would stay where the mouse is, regardless of whether the player moved the mouse.

In order to determine whether the mouse has moved, add a class-level MouseState variable at the top of your class:

MouseState prevMouseState;

This variable will keep track of the mouse state from the previous frame. You’ll use it to compare the previous state to the current state of the mouse in each frame. If the values of the X and/or Y properties are different, you know the player has moved the mouse and you can move the three rings sprite to the new mouse position.

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

MouseState mouseState = Mouse.GetState(  );
if(mouseState.X != prevMouseState.X ||
    mouseState.Y != prevMouseState.Y)
    ringsPosition = new Vector2(mouseState.X, mouseState.Y);
prevMouseState = mouseState;

This code will move the three rings sprite to the position of the mouse, but only if the mouse has been moved. If you compile and run at this point, you should see that you are now able to control the rings sprite with the mouse or the keyboard.

Gamepad Input

If you’re developing a game for Windows, you can still program for an Xbox 360 controller. You’ll have to have a wired controller, or you can purchase an Xbox 360 Wireless Receiver for around $20, which will allow you to connect up to four wireless controllers to a PC.

Tip

The wireless Xbox 360 controller actually does come with a wire if you buy the charge pack for that controller. However, there is no data transfer over that cable, so even when it’s plugged in, it’s still a wireless controller. The cable on the charge pack transfers electricity for the charge and nothing more.

Just as XNA provides a Mouse class for mouse input and a Keyboard class for keyboard input, it provides a GamePad class for reading input from an Xbox 360 gamepad. And yes, that’s right, there’s a GetState method for the GamePad class, just as there is for the other devices. There’s something to be said for standards, and Microsoft’s XNA Framework is, for the most part, a superb example of how standardization across a large-scale system (in this case, a framework and API) can be of such great benefit. Most of the time, you can tell how to use an object just by understanding the type of the object and knowing how similarly typed objects function. That’s a tribute to a great design by the XNA team—kudos to them.

The GetState method for the GamePad class accepts an enum parameter called PlayerIndex that indicates which player’s controller you want to access, and it returns a GamePadState struct that you can use to get data from the selected controller. Key properties of the GamePadState struct are listed in Table 4-3.

Table 4-3. Key properties of the GamePadState struct

Property

Type

Description

Buttons

GamePadButtons

Returns a struct that tells which buttons are currently pressed. Each button is represented by a ButtonState enum that specifies whether the button is pressed or not pressed.

DPad

GamePadDPad

Returns a struct that tells which directions on the DPad are pressed. The DPad struct has four buttons (up, down, left, and right), each of which is represented by a ButtonState enum that specifies whether the button is pressed or not pressed.

IsConnected

boolean

Indicates whether the controller is currently connected to the Xbox 360.

ThumbSticks

GamePadThumbSticks

Returns a struct that determines the directions of the thumbsticks. Each thumbstick (left and right) is a Vector2 object with X and Y values that have limits of −1 to 1 (e.g., for the left thumbstick, if you push it all the way left, its X value will be −1; if you don’t push it at all, the X value will be 0; and if you push it all the way to the right, the X value will be 1).

Triggers

GamePadTriggers

Returns a struct that tells whether the triggers are pressed. The Triggers struct contains two float values (left and right). A value of 0 means the trigger is not pressed at all, whereas a value of 1 means the trigger is fully pressed.

The GamePadState struct contains two methods that will give you most of the functionality you need. These methods are listed in Table 4-4.

Table 4-4. Key methods of the GamePadState struct

Method

Description

bool IsButtonDown(Buttons)

Pass in a button or multiple buttons with a bitwise OR. Returns true if all buttons are down, and false otherwise.

bool IsButtonUp(Buttons)

Pass in a button or multiple buttons with a bitwise OR. Returns true if all buttons are up, and false otherwise.

Looking at the properties in Table 4-3, you’ll notice that some of the controls are represented by Boolean or two-state values (either on or off), and others are represented by values that fluctuate between a range of numbers (0 to 1, or −1 to 1). These ranged properties are referred to as analog controls, and because they don’t have a simple on or off value, they offer more accuracy and more precision in a gaming control. You might have noticed that in some games on an Xbox 360 you can move at different speeds with the triggers or thumbsticks. This is because as you press either button in a given direction, the controller will send a signal to the application in varying strengths. This is an important concept to remember when programming against an Xbox 360 controller and a feature that you’ll want to incorporate into games that you develop. We cover how to do that in this section.

All right, let’s add some code that will let you control your sprite with your Xbox 360 gamepad. Just as before, leave the code for the mouse and keyboard there, too, and you’ll have three ways to control your sprite.

Because the thumbsticks can contain X and Y values ranging from −1 to 1, you’ll want to multiply those values of the ThumbSticks property by the ringsSpeed variable. That way, if the thumbstick is pressed all the way in one direction, the sprite will move at full speed in that direction; if the thumbstick is only slightly pushed in one direction, it will move more slowly in that direction.

The following code will adjust your sprite’s position according to how much and in which direction the left thumbstick on player one’s controller is pressed. Add this code to the Update method, just below the code for the keyboard and mouse input:

GamePadState gamepadState = GamePad.GetState(PlayerIndex.One);
ringsPosition.X += ringsSpeed * gamepadState.ThumbSticks.Left.X;
ringsPosition.Y −= ringsSpeed * gamepadState.ThumbSticks.Left.Y;

Compile and run the application now, and you’ll have full control of your three rings sprite using your Xbox 360 controller.

Let’s spice things up a bit. Using an Xbox 360 controller should be a bit more fun than it currently is. Let’s add a turbo functionality that doubles your movement speed when active. Of course, when moving so rapidly around the screen in turbo mode, you should feel some vibration in your controller due to the dangerous velocity at which you’ll be moving your sprite. You’ve probably felt the vibrations in an Xbox 360 controller before. This type of mechanism is referred to as force feedback, and it can greatly enhance the gameplay experience because it adds yet another sense that pulls the user into the game.

The method SetVibration will set vibration motor speeds for a controller. The method returns a Boolean value indicating whether it was successful (false means that either the controller is disconnected or there is some other problem). The method accepts a player index, and a float value (from 0 to 1) for the left and right motors of the controller. Set the values to zero to stop the controller from vibrating. Anything above zero will vibrate the controller at varying speeds. Modify the code you just added to move the sprite with the Xbox 360 controller to include the following:

GamePadState gamepadState = GamePad.GetState(PlayerIndex.One);
if (gamepadState.Buttons.A == ButtonState.Pressed)
{
    ringsPosition.X += ringsSpeed * 2 * gamepadState.ThumbSticks.Left.X;
    ringsPosition.Y −= ringsSpeed * 2 * gamepadState.ThumbSticks.Left.Y;
    GamePad.SetVibration(PlayerIndex.One, 1f, 1f);
}
else
{
    ringsPosition.X += ringsSpeed * gamepadState.ThumbSticks.Left.X;
    ringsPosition.Y −= ringsSpeed * gamepadState.ThumbSticks.Left.Y;
    GamePad.SetVibration(PlayerIndex.One, 0, 0);
}

The code first checks to see if the A button on the controller is pressed. If it is, turbo mode is activated, which means that you’ll move the sprite at twice the normal speed and activate the vibration mechanism on the controller. If A is not pressed, you deactivate the vibration and move at normal speed.

Compile and run the game to get a sense of how it works.

As you can see, the gamepad adds a different dimension of input and gives a different feel to the game itself. It’s a powerful tool, but it won’t work well with all game types. Make sure you think about what type of input device is best for the type of game you are creating, because the input mechanism can go a long way toward determining how fun your game is to play.

Keeping the Sprite in the Game Window

You have probably noticed that the rings sprite will disappear off the edge of the screen if you move it far enough. It’s never a good idea to have the player controlling an object that is offscreen and unseen. To rectify this, update the position of the sprite at the end of the Update method. If the sprite has moved too far to the left or the right or too far up or down, correct its position to keep it in the game window. Add the following code at the end of the Update method, just before the call to base.Update:

if (ringsPosition.X < 0)
    ringsPosition.X = 0;
if (ringsPosition.Y < 0)
    ringsPosition.Y = 0;
if (ringsPosition.X > Window.ClientBounds.Width - ringsFrameSize.X)
    ringsPosition.X = Window.ClientBounds.Width - ringsFrameSize.X;
if (ringsPosition.Y > Window.ClientBounds.Height - ringsFrameSize.Y)
    ringsPosition.Y = Window.ClientBounds.Height - ringsFrameSize.Y;

Compile and run the game at this point, and you should be able to move the rings sprite around the screen just as before; however, it should always stay within the game window rather than disappearing off the edge of the screen.

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.

What You Just Did

Great job! You have some cool animation, and now you’re checking for collisions while moving the sprites around the screen. Very impressive. Here’s a recap of what you did in this chapter:

  • You implemented a sprite controlled by a user via a keyboard, a mouse, and an Xbox 360 gamepad.

  • You implemented force feedback using the Xbox 360 controller.

  • You implemented collision detection for two animated sprites.

  • You learned about the balance between accuracy and performance in collision detection.

Summary

  • Input devices supported in XNA include the keyboard, mouse, and Xbox 360 controller.

  • The Xbox 360 has several analog inputs that allow for varying degrees of input from a single button.

  • Collision detection is a constant balance between performance and accuracy. The more accurate an algorithm is, the more of a performance hit is usually incurred.

  • The bounding-box algorithm for collision detection is one of the simplest and most straightforward algorithms. If you “draw” an imaginary box around each object, you can easily tell which box is colliding with another box.

  • You can speed up collision detection while improving accuracy by combining methods. Use a large box to determine whether it’s worth the time to check the smaller boxes surrounding sections of an object, or implement a grid-based system to avoid unnecessary collision checks between objects that are not close together.

  • Do you notice anything that tyrannical dictators, drug lords, and gangbangers have in common? That’s right, typically they don’t use XNA. That’s proof right there that XNA spreads world peace. Make XNA, not war.

Test Your Knowledge: Quiz

  1. What object is used to read input from a mouse?

  2. Fact or fiction: the X and Y coordinates from a mouse as read in an XNA application represent how much the mouse has moved since the previous frame.

  3. What is the difference between an analog input control and a digital input control?

  4. Describe the bounding-box collision-detection algorithm.

  1. Describe the pros and cons of the bounding-box collision-detection algorithm.

  2. What is the ratio of unicorns to leprechauns?

Test Your Knowledge: Exercise

Let’s combine some aspects of this chapter and the previous one. Take the code where we left off at the end of this chapter and modify it to include another nonuser-controlled sprite (use the plus.png image, which is located with the source code for this chapter in the AnimatedSprites\AnimatedSprites\AnimatedSpritesContent\Images folder). Add movement to both nonuser-controlled sprites, as you did in Chapter 3, so that each sprite moves in both the X and Y directions and bounces off the edges of the screen. Add collision detection to the newly added sprite as well. The end result will be a game where you try to avoid two moving sprites. When you hit either sprite, the game ends.

For clarity in working with the plus.png image, the frame size of the sprite sheet is 75×75 pixels, and it has six columns and four rows (note that the rings and skull ball sprite sheets each had six columns and eight rows).

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