Input Manager

At this point, you may be noticing that our input processing could quickly dominate our Game class, and can be very messy. Especially if we want to support multiple forms of input in the same game. Consider if we wanted to do a platformer - we might want the player to be able to use the keyboard or a gamepad.

One technique we can employ is an input manager, a class that handles polling the input and abstracts it to just the commands we care about. I.e. for a simple platformer, we might want the four directions and “jump”.

We can create a class called InputManager that would provide those:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

/// <summary>
/// Manages input for a simple platformer
/// </summary>
public class InputManager 
{
    /// <summary>
    /// The player's direction
    /// </summary>
    public Vector2 Direction { get; private set; }

    /// <summary>
    /// If the player pressed jump this frame 
    /// </summary>
    public bool Jump { get; private set; }
}

Note that we use public auto-properties, but override the set to be private. This way outside code can access these boolean properties, but only the code in this class can set them.

We’ll also need to declare our state variables:

    /// Input state variables
    private KeyboardState currentKeyboardState;
    private KeyboardState priorKeyboardState;
    private GamePadState currentGamePadState;
    private GamePadState priorGamePadState;

And, we’ll process these in an update method:

    /// <summary>
    /// Update the input object
    /// </summary>
    /// <param name="gameTime">The game time</param>
    public void Update(GameTime gameTime)
    {
        // Update input state
        priorKeyboardState = currentKeyboardState;
        currentKeyboardState = Keyboard.GetState();
        priorGamePadState = currentGamePadState;
        currentGamePadState = GamePad.GetState(0);

        // TODO: Assign actions based on input
    }

This looks just like how we updated state before. The next step is to abstract the input into the properties we defined. We’ll start with the Direction, which we are using a Vector2 to represent. This conveniently matches with our gamepad’s thumbstick representation, so we can assign it directly:

    // Right thumbstick
    Direction = currentGamePadState.Thumbsticks.Right;

If there is no gamepad available, this will be the vector $(0,0)$. Then we can check the WASD keys, and assign a corresponding value

    // WASD keys:
    if (currentKeyboardState.IsKeyDown(Keys.W)) Direction += new Vector2(0,-1);
    if (currentKeyboardState.IsKeyDown(Keys.A)) Direction += new Vector2(-1, 0);
    if (currentKeyboardState.IsKeyDown(Keys.S)) Direction += new Vector2(0,1);
    if (currentKeyboardState.IsKeyDown(Keys.D)) Direction += new Vector2(1, 0);

Note that we are adding a unit vector to the (supposedly zero) existing vector. This does mean that a player using both keyboard and mouse could double the direction vector length, so if this is important in your game you’ll need additional logic to prevent it.

For the jump, we want that to be a discrete push, i.e. it is only true the frame the button is pushed. So we’ll first need to reset it to false (in case it was true in a prior frame):

    // reset jump
    Jump = false;

Now we can check if the “A” button is pressed:

    if(currentGamePadState.IsButtonDown(Buttons.A) &&  priorGamePadState.IsButtonUp(Buttons.A))
        Jump = true;

Similarly, we can check for the spacebar:

    if(currentKeyboardState.IsKeyDown(Keys.Space) && priorKeyboardState.IsKeyUp(Keys.Space))
        Jump = true;

Now, we just need to construct an instance of InputManager in our game, invoke its Update() at the start of our game class' Update() method, and then we can use the Direction and Jump properties to determine what should happen in our game.

This idea can be adapted to any game you want to make - but it will always be specific to the game, as what the controls need to do will vary from game to game. It also makes it easier to allow for multiple forms of input, and to also do user-controlled input mapping, where users can reassign keys/buttons to corresponding actions.