This blog is no longer updated. Feel free to copy any useful information to other blogs or wikis as this site may not exist for much longer. Thanks.


Extending GamePadState

November 28th, 2009 | 6 comments

One thing that is quite common in games is to utilize multiple buttons for the same action. Most usually you’ll see something like this:

GamePadState gps = GamePad.GetState(PlayerIndex.One);
if (gps.IsButtonDown(Buttons.Start) ||
	 gps.IsButtonDown(Buttons.Back) ||
	 gps.IsButtonDown(Buttons.A) ||
	 gps.IsButtonDown(Buttons.B) ||
	 gps.IsButtonDown(Buttons.X) ||
	 gps.IsButtonDown(Buttons.Y))
{
	// do stuff
}

That’s quite a pain just to see if one of those buttons are pressed. There must be a better way.

Some people have realized that the Buttons enumeration has the FlagsAttribute meaning you can use them as a bit flag. Most people then try something like this:

Buttons b = Buttons.Start | Buttons.Back | Buttons.A | Buttons.B | Buttons.X | Buttons.Y;
GamePadState gps = GamePad.GetState(PlayerIndex.One);
if (gps.IsButtonDown(b))
{
	// do stuff
}

Unfortunately the IsButtonDown method requires all of those buttons to be pressed for it to return true. Not exactly what we want (though possibly useful in other cases).

So what I did was create an extension method to solve this for me. Here I’ll just paste my entire class for you to see and then I’ll explain the trickery of some parts:

public static class GamePadStateExtensions
{
	private static readonly IEnumerable<Buttons> individualButtons;

	static GamePadStateExtensions()
	{
		// populate our list with all possible buttons. we use LINQ and reflection instead of
		// Enum.GetValues since Enum.GetValues isn't available on Xbox.
		individualButtons =
			from x in typeof(Buttons).GetFields(BindingFlags.Static | BindingFlags.Public)
			select (Buttons)x.GetValue(null);
	}

	/// <summary>
	/// Initializes the extensions to prepare any cached data. Calling this method isn't required
	/// but does allow the game to determine exactly when the cache is generated.
	/// </summary>
	public static void Initialize()
	{
		// calling this method invokes the static constructor above which will do the work.
		// we just needed a method to call on the class so that the CLR will initialize the class.
	}

	/// <summary>
	/// Determines if any of the buttons specified are currently pressed.
	/// </summary>
	/// <param name="state">The GamePadState to check.</param>
	/// <param name="buttons">One or more buttons to test for.</param>
	/// <returns>True if any of the buttons are pressed, false otherwise.</returns>
	public static bool AreAnyButtonsPressed(this GamePadState state, Buttons buttons)
	{
		// iterate through all the possible buttons
		foreach (var button in individualButtons)
		{
			// if the parameter contains the button and the state has the button as pressed,
			// return true
			if ((buttons & button) != 0 && state.IsButtonDown(button))
				return true;
		}

		// if we're here, none of the requested buttons were pressed
		return false;
	}
}

First let’s look at the constructor. Basically what we’re doing is building a list of all values in the Buttons enumeration. Normally we’d just use Enum.GetValues but that method isn’t available on the Xbox so instead we use a little LINQ query to extract all the Buttons values using reflection. We put this into the static constructor for two reasons:

  1. The constructor only runs once so we can cache these values instead of getting them each time we want to test (which would be quite slow given that we’re using reflection).
  2. The static constructor is always run when the class is first used in code meaning that we can’t accidentally forget to run that code and get those values.

Next we have an Initialize method. After point 2 above you might be wondering why. Well, as the comments say, it gives us something to call if we want to manually choose when this reflection cost occurs. We could call that method during the game’s initialization that way we didn’t get the hit the first time we call the extension method.

Last we have our actual extension method AreAnyButtonsPressed. This is the simplest part of the class, really. We go through our list of buttons seeing if any are specified in our parameter. If a button is in the parameter and is pressed, we return true. If none of the buttons cause us to return true, we just return false. Easy peasy.

Returning to our example above, we can now use this code to cause the game to do something if the Start, Back, A, B, X, or Y buttons are pressed:

Buttons b = Buttons.Start | Buttons.Back | Buttons.A | Buttons.B | Buttons.X | Buttons.Y;
GamePadState gps = GamePad.GetState(PlayerIndex.One);
if (gps.AreAnyButtonsPressed(b))
{
	// do stuff
}

Pretty nice, right? Of course, usually you aren’t responding to six buttons, but this method is still useful even if it’s just for something like a “Press Start” screen where you want to respond to either Start or A. You can still reduce this:

GamePadState gps = GamePad.GetState(PlayerIndex.One);
if (gps.IsButtonDown(Buttons.Start) || gps.IsButtonDown(Buttons.A))
{
	// do stuff
}

to this:

GamePadState gps = GamePad.GetState(PlayerIndex.One);
if (gps.AreAnyButtonsPressed(Buttons.Start | Buttons.A))
{
	// do stuff
}

Cutting down on redundancy, FTW. :)

Anyone else use any tricks like this? Any ideas how to optimize the extension method? As is we’re looking at a worst case of iterating over all the possible buttons each time the method is called. Is there any way we could implement the method to lower our worst case scenario? Not that iterating over all the buttons is that expensive but it’s still wasteful.


Possibly Related Posts

(Automatically Generated)
Basic Handling of Multiple Controllers
.NET Misconceptions Part 1
Catching Exceptions on Xbox 360
Using interpolators and timers
Spice up your PC input with extension methods

  1. November 29th, 2009 at 08:10
    Quote | #1

    That’s a useful little class, thanks for this.

  2. jamesjlang
    December 1st, 2009 at 08:10
    Quote | #2

    I wonder if the extra class is worth just cutting down that tiny little bit. Seems that it is a bit overkill for what you are trying to accomplish. However, the concept of a game pad extension in general is something that I am going to play around with.

  3. December 1st, 2009 at 08:30
    Quote | #3

    It’s a tiny bit but something that occurs frequently:

    - Menus should accept A & Start to activate an item
    - Menus should accept B & Back to go back
    - Sometimes you want multiple buttons mapped to the same action in a game

    I don’t personally see it as overkill because it lets me write the code I want to write when handling input. Plus this is actually just a part of my larger set of GamePadState extension methods, so it’s not the only thing I have in there.

  4. jamesjlang
    December 1st, 2009 at 08:38
    Quote | #4

    Yeah I suppose so. I would agree that it adds to readability (if I ever have plans of anyone other than me reading my code).

  5. Kainsin
    December 15th, 2009 at 06:16
    Quote | #5

    How about using a variable argument list instead? Then you would just need the function.

    public static bool AreAnyButtonsPressed(this GamePadState state, params object[] buttons)
    {
    foreach (Buttons button in buttons)
    {
    if (state.IsButtonDown(button))
    return true;
    }
    return false;
    }

    You can redefine this as a for loop with an “is” check for object -> Buttons if you’re concerned about the whole foreach garbage thing.

    (I don’t know how to format code blocks on this site, just registered.)

  6. January 14th, 2010 at 10:55
    Quote | #6

    I just now saw that comment. My bad. :)

    The main problem with that is that you’re going to generate garbage (it will allocate a new array each time you use it) and you’ll see a lot of boxing/unboxing costs from casting an enumeration to object and back (you can read more on that here: http://msdn.microsoft.com/en-us/library/yz2be5wk.aspx).

    True that your method doesn’t require the static initialization stuff and is a bit simpler, but I personally feel the performance implications aren’t worth it.

Comments are closed.