Using XNA Input for Controllers

The XNA Framework provides support for three categories of user input devices


September 06, 2007
URL:http://drdobbs.com/windows/using-xna-input-for-controllers/201803739

Joseph Hall has worked as a software developer for more than 15 years. He has worked for IBM and Microsoft, and was a member of the original Xbox team. He is also author of XNA Game Studio Express: Developing Games for Windows and the Xbox 360, from which this article is adapted. Copyright (c) 2007 Thomson Course Technology PTR. All rights reserved.


Games are useless unless the player can interact with them. In fact, there's a special name for a game that doesn't accept input -- it's called a screen saver.

The XNA Framework provides support for three categories of user input devices: the Xbox 360 Controller, the keyboard, and the mouse. This article describes the differences between each, and presents code and design guidelines to help you use player input in your game to maximum effect. In this article , we will develop a simple game project that graphically shows the state of all the buttons of the Xbox 360 Controller and turns the rumble motors on and off to give the player tactile feedback.

GamePad Overview

The XNA Framework has support for 14 digital buttons, 4 analog buttons, and 2 vibration motors when using the Xbox 360 Controller. The media button is not supported. Up to four actively connected controllers are supported, and the Xbox 360 Controller is supported on both the Windows and the Xbox 360 game console platforms. The number and variety of input sources on an Xbox 360 Controller make it the ideal input device for your games.

(The only type of controller that is supported by the XNA Framework is the Xbox 360 Controller. If you're writing a Windows game, and you want your game to support another type of controller (like the Microsoft Sidewinder Controller), you will need to access that controller via some other API.)

Polling

Input from the controller is collected by the XNA Framework via polling. During every frame of your game, you will ask the controller what the current state of its buttons is. This state information is wrapped in an XNA Framework structure known as the GamePadState, and it represents a snapshot of the state of all the controller's buttons at the time the request was made. This state information can be saved from frame to frame (so that you can determine which buttons were pressed or released since the last frame), and you can pass the state as a parameter to your methods (so that you don't need to poll the controller repeatedly, running the risk of retrieving conflicting information).

If you've done any Windows programming in the past, you've dealt with event-driven programming. Polling may seem inefficient by comparison, and you might think that it would be easy to miss player input between GetState calls. But, you need to keep in mind that your game is running at 15, 30, 60, or more frames per second. When you're gathering input data 60 times a second, it's not very likely that a player can press and release a button between any two polling requests. If you want to test this out, run the example that we develop in this chapter and try to press and release a button without the screen registering your action.

(Per the XNA Framework documentation, the state of a controller is undefined when the controller is disconnected. The API will allow you to poll for the state of a disconnected controller, and there is a boolean property in the GamePadState structure that indicates whether the controller is connected or not. All of the other controller state information is unreliable, and should not be trusted. You may want to automatically pause your game whenever there are no controllers connected.)

GamePadState information is retrieved using the GetState member of the GamePad class. Only four Xbox 360 Controllers are supported, and each must be polled separately. The following code shows how you would get the state of the controller used by player one.

// Poll the current controller state for player one
GamePadState padState1 = GamePad.GetState(PlayerIndex.One);

Digital Buttons

The Xbox 360 Controller has 14 (accessible) digital buttons. The 10 most obvious digital buttons are the A, B, X, Y, Start, Back, left shoulder, and right shoulder buttons. The four directions of the DPad are actually four separate digital buttons (one for each direction), and the left and right thumbsticks can be pressed, each behaving as a digital button. Digital buttons have only two states_pressed and released -- so they can be represented with Boolean values in your code. The following code demonstrates how you would see if player one is pressing the A button on his controller.

// Poll the current controller state for player one
GamePadState padState1 = GamePad.GetState(PlayerIndex.One);
// make sure the controller is connected
if (padState1.IsConnected)
{
  if (padState1.Buttons.A = = ButtonState.Pressed)
  {
     // player one is pressing the A button
  }
}

Analog Buttons

Unlike their digital siblings, analog buttons can report a range of values. The Xbox 360 Controller has two triggers on the back side of the controller. The state of each trigger is represented by a float, ranging from 0.0f (not pressed) to 1.0f (fully pressed). The controller also sports two directional thumbsticks. Each thumbstick has an x- and a y-axis. Each axis is represented by a float ranging from -- 1.0f to 1.0f. For the x-axis, -- 1.0f indicates that the player is pressing the stick fully to the left, 1.0f indicates that the player is pressing the stick fully to the right, and 0.0f indicates that the stick is not being used at all. Similarly for the y-axis, -- 1.0f represents down, 1.0f represents up, and 0.0f represents no action.

The following code demonstrates how you would see if player one is using the left thumbstick on his controller.

// Poll the current controller state for player one
GamePadState padState1 = GamePad.GetState(PlayerIndex.One);
// make sure the controller is connected
if (padState1.IsConnected)
{
  if (padState1.ThumbSticks.Left.X != 0.0f)
  {
    // player one is directing the left thumbstick to the left or right
  }
  if (padState1.ThumbSticks.Left.Y != 0.0f)
  {
    // player one is directing the left thumbstick up or down
  }
}

When processing analog input from the thumbsticks, there's a concept known as "dead zone processing" that you should be aware of. There are minor variances in manufacturing from controller to controller; and over time, movable parts can wear with use. When at rest, thumbsticks will almost always be slightly off center. To account for this, the GetState method automatically disregards values that are below a certain threshold. That threshold is known as the dead zone. If the dead zone weren't taken into account, you would see what's commonly referred to as "drift" in your game -- phantom user actions, like moving left even though you're not pressing any buttons.

The overloaded version of the GetState API provides three methods of dead zone processing, each represented by a member of the GamePadDeadZone enumeration.

Vibration

It's amazing how adding simple little details to a game can make it so much more immersive. Vibration effects are an effective way to draw your player into the experience. When his car hits a wall, or a grenade explodes just a few feet away, or he's just fallen to his death from a high-rise apartment complex, shake the controller to provide tactile feedback.

The Xbox 360 Controller has two vibration motors. The left is a low-frequency rumble motor, and the right is a high-frequency rumble motor. Each motor can be activated independently of the other. And each motor supports varying speeds. You can turn the motors on and off, and adjust their speeds, using the SetVibration method of the GamePad class. The following snippet demonstrates how you would enable the vibration motors.

// Poll the current controller state for player one
GamePadState padState1 = GamePad.GetState(PlayerIndex.One);
// make sure the controller is connected
if (padState1.IsConnected)
{
  if (padState1.Buttons.A = = ButtonState.Pressed)
  {
     // shake the controller if the A button is pressed
     GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
  }
else
{
    // otherwise, disable the motors
    GamePad.SetVibration(PlayerIndex.One, 0f, 0f);
  }
}

(The preceding example simply set each motor to its maximum speed, but you can create all sorts of effects by combining various motor speeds and varying the settings over time. Imagine enabling and disabling the motors in rapid succession to simulate riding over a gravel road or firing a machine gun. Or think about using the low-frequency motor to provide constant, pulsing ambient feedback when the player enters a room filled with humming alien technology.)

Wrapper Class

It's a good idea to wrap the GamePad and GamePadState functionalities within your own custom helper class. That way, you can make global changes to how your game processes input by changing one source code file. Imagine that you've written a game that uses the controller. This game has several types of levels_ each programmed as a separate C# class and each accessing the controller via the XNA Framework's GamePad class. After play testing, you decide to provide an option so that the player can reverse the controls for looking up and down (a common option in most first-person shooter games).

Without the wrapper class, you'll need to edit and test every class that directly accesses the controller APIs. With the wrapper class, you edit one source code file, and the change is inherited by all your custom C# game classes that use it. Of course, you'll also want to provide a way to temporarily return to your default controller mappings for menus, but there is great benefit in centralizing your controller logic.

Let's consider another scenario. Imagine that you've written your game solely for the Xbox 360 game console. When you're done writing it, you decide that you would also like to release it as a Windows game. Not every Windows gamer will have an Xbox 360 Controller connected to his PC, so you decide to add keyboard support. Touching every game screen that accepts controller input will be a pain. You don't want to punish the Windows gamers with Xbox 360 Controllers by simply replacing your controller logic with keyboard logic, so you'll need to make sure that your game logic gracefully combines input from both sources. If you're using a wrapper class, you can intercept keyboard input, map it to controller input, and inject phantom button presses into the controller state that your game uses.

If you do decide to write your own custom wrapper to gather user input, you should be aware of the fact that the GamePadState structure contains a handy little member named PacketNumber that will let you know if the controller state has changed since the last time you polled it. Many times, there will be relatively long spans where the player is pressing the same button or combination of buttons. In a racing game, the player may be on a straightaway, pressing the accelerator all the way in. In a first-person shooter, the player may be crouched in a corner, waiting to snipe one of his buddies. On a pause screen, the player has likely put the controller down and isn't pressing anything.

The Example

We will develop a simple "game" that exercises each of the features of the controller. Each digital button will be represented on the screen with a graphic, and when the player presses a button, its graphic will change to reflect the change in state. The screen will also contain "slider" images where the state of each of the analog buttons will be presented. In addition to the button images, there will be a small viewport where a ship flies around on a sheet of graph paper. As the player uses any of the directional controls to move left or right, the ship will rotate. When the player presses the triggers or uses any of the directional controls to move up or down, the ship will move. Figure 1 shows a screenshot of the final game.

Figure 1: Screenshot of the example game that we will develop in this article.

The Source Images

I created two graphics for each digital button, one for each state (pressed and released). To lay out the screen, I played around with the images in a paint program, moving them around until I was happy with the rough design. Figures 2, 3, and 4 show the source graphics. I divided the collection of images into three files, but this separation is fairly arbitrary.

Figure 2: The sprites that represent the pressed and released states for each of the 14 digital buttons.

The first source image, shown in Figure 2, is named "buttons," and it contains the pressed and released graphics for each of the 14 digital buttons. The second image, shown in Figure 3, is named "analog," and it contains the slider bars and arrows that I use to render the states of the analog buttons.

Figure 3: The sprites for the sliders to show the state of the six analog buttons, as well as the sprites that show which controller ports are active.

It also contains the images that I use to indicate which of the four Xbox 360 Controllers are currently connected. Rather than having eight separate images of the controller connection states (four controllers, each with two states), I decided to build a composite image at runtime by layering the images that you see here. The actual source image is transparent, but I've added a simple checkerboard pattern so that you can see how the individual components of the image line up with each other.

The final image, shown in Figure 4, is named "background," and it contains the graph paper graphic that is tiled across the entire game screen, as well as the ship, and the 640x480-pixel background image, which includes the viewport (complete with drop shadow).

Figure 4: The background sprite that will be layered on top of the scrolling grid. The game screen is 640x480, but the image is 1024x512 (dimensions as powers of 2).

The actual source image is transparent, but I've added a simple checkerboard pattern so that you can see how the individual components of the image line up with each other.

The ButtonSprite Class

The individual graphics are lined up in the source image so that I can render each of the button state graphics to the same X and Y coordinates on the screen. When it's time to draw the button on the screen, I select the appropriate graphic based on the current state of the button it represents. I wanted to be able to easily move the buttons around on the screen in case I decided to change my layout, so I created a simple C# class to represent the on-screen button. For each button, I need references to two Texture2D objects -- one for the pressed state and one for the released state. Since my source images contain multiple graphics, I can use the same texture for many of the buttons, but I'll still need some way to remember where the individual graphics are within the larger source image. To keep track of their locations, I'll use a Rectangle for each state.

private Texture2D m_TextureNormal;
public Texture2D TextureNormal
{
  get { return m_TextureNormal; }
  set { m_TextureNormal = value; }
}
private Rectangle m_RectNormal = Rectangle.Empty;
public Rectangle RectNormal
{
  get { return m_RectNormal; }
  set { m_RectNormal = value; }
}
private Texture2D m_TexturePressed;
public Texture2D TexturePressed
{
  get { return m_TexturePressed; }
  set { m_TexturePressed = value; }
}
private Rectangle m_RectPressed = Rectangle.Empty;
public Rectangle RectPressed
{
  get { return m_RectPressed; }
  set { m_RectPressed = value; }
}

I also need to know where to draw the button on the screen. To store the X and Y coordinates, I'll use a Vector2D structure.

public Vector2 Location = Vector2.Zero;

Using this basic class, I can place the ButtonSprite anywhere on the screen, and not worry about the details of how it's rendered. Each ButtonSprite will be rendered the same way -- using code similar to that found in the following snippet.

// bat is an XNA SpriteBatch class, btn is our custom ButtonSprite class
bat.Draw(btn.TexturePressed, btn.Location, btn.RectPressed, Color.White);

(Rather than writing new classes to support the other (non-digital) buttons, I reuse the properties of this class. Where I deviate from the obvious functionality, I've included comments in the source code. For example, the left trigger is an analog control that has no "pressed" state. The TextureNormal and RectNormal properties of the ButtonSprite are used to define the texture and bounds of the vertical slider graphic. Since there is no pressed state, I use the RectPressed structure to denote the bounds of the "usable" area of the slider -- the subset of the graphic that excludes the rounded edges, the transparent areas, and the drop shadow. This inner rectangle is used to place and constrain the vertical bar arrow graphic.)

Draw Methods

The final game screen that you see is made up of many independent components. I've broken the task of rendering all of these components into separate, specialized drawing methods.