Nuclex.Input

Nuclex.Input is a very lightweight component that extends your capabilities in interacting with input devices in XNA. It closely follows XNA's design and uses the same state structures returned by XNA's input device classes, allowing you to easily integrate it into existing games.

Features

Here's a short summary of the features, explained in further detail below:
  • State change notifications for all input devices
  • Well-behaved text input respecting the keyboard layout
  • Buffered input for keyboard and mouse
  • Consistent extension of the XINPUT design for mice and keyboards
  • Support for any DirectInput-compatible game pad or joystick
  • Mocks of all input devices for smooth input testing
  • Zero garbage produced during usage

Design Overview

At the core of Nuclex.Input are three interfaces - IGamePad, IMouse and IKeyboard:

nuclex-input-device-classes.png

By using individual objects for the input devices, Nuclex.Input can provide events that the game can subscribe to in order to be notified of state changes to the input devices. The implementations behind those interfaces are extremely efficient and will only compare between the states of input devices if subscribers exist for a particular event, so no time will be wasted unless the game is actually interested in an event. The IKeyboard and IMouse devices go even further and are based on window messages, so they are notified of any state changes and do not need to compare states at all.

Taking the route of window messages also enables the IKeyboard to provide well-behaved text input, respecting the user's keyboard layout and any other locale-specific settings. And, just as important, keyboard input is buffered - your game will be notified that the player pressed the X key, even if it was pressed and released between two calls to the GetState() method. This completely gets rid of the ''lost characters'' problem when a fast typist uses the keyboard.

To access the input devices, the IInputService is used:

nuclex-input-service-and-manager.png

XNA's GamePad class supports 4 game pads that you can queried for their state even if they are not connected (they will return a neutral state with all buttons release and all sticks in center position in that case). This design decision goes back to XINPUT and makes it much easier to cope with suddenly disconnected game pads.

The InputManager extends this and always provides 8 game pads. The first 4 game pads match XNA's game pads, the second 4 game pads represent up to 4 !DirectInput-compatible game pads on the PC. On the Xbox 360 and on Windows Phone 7, the second 4 game pads are dummies. Similarly, there will always be a mouse you can query, but a dummy will take its place on the Xbox 360 and on Winows Phone 7. Finally, there are 5 keyboards. Keyboards 1-4 are the chat pads attached to the XNA game pads with the same index and keyboard 5 is the PC keyboard (again, replaced with a dummy everywhere else but on the PC).

As a little bonus, Nuclex.Input also provides a complete mock implementation of the InputManager, named MockInputManager that you can use simulate input devices easy and in a very readable manner when you write unit tests for your input handling code.

Simple Usage

To use Nuclex.Input in your game, simply create the InputManager in your Game class:

class Game1 : Microsoft.Xna.Framework.Game {

  GraphicsDeviceManager graphics;
  InputManager input;

  public Game1() {
    graphics = new GraphicsDeviceManager(this);
    input = new InputManager(Services, Window.Handle);

    Components.Add(input);
  }

  // ...other code...

}

Getting the state of an input device is not any harder than it was before:

protected override void Update(GameTime gameTime) {

  // Allows the game to exit
  if (GamePad.GetState(PlayerIndex.One).Buttons.Start == ButtonState.Pressed)
    this.Exit();

}
becomes
protected override void Update(GameTime gameTime) {

  // Allows the game to exit
  if (input.GetGamePad(PlayerIndex.One).GetState().Buttons.Start == ButtonState.Pressed)
    this.Exit();

}

DirectInput Game Pads

If you want to check the state of the first DirectInput game pad in your system instead, just write:

protected override void Update(GameTime gameTime) {

  // Allows the game to exit
  if (input.GetGamePad(ExtendedPlayerIndex.Five).GetState().Buttons.Start == ButtonState.Pressed)
    this.Exit();

}

Note the use of the ExtendedPlayerIndex enumeration. Its first 4 entries are equivalent to the PlayerIndex enumeration in XNA (so you can simply cast between both enumerations), then follow 4 additional entires (Five, Six, Seven and Eight) for the additional DirectInput game pads.

State Change Notifications

Instead of checking whether the Start button is pressed ourselves, we could also let the input device notify the game when a button on the game pad is pressed:

protected override void Initialize() {
  base.Initialize();

  input.GetGamePad(PlayerIndex.One).ButtonPressed += gamePadButtonPressed;
}

private void gamePadButtonPressed(Buttons buttons) {
  if(buttons == Buttons.Start)
    Exit();
}

Keyboard Text Input

To be notified when the user has entered a character through the keyboard, simply subscribe to the keyboard's CharacterEntered event:

private string enteredText;

protected override void Initialize() {
  base.Initialize();

  input.GetKeyboard().CharacterEntered += keyboardCharacterEntered;
}

private void keyboardCharacterEntered(char character) {
  enteredText += character;
}

There it is. Be aware that full support for Windows' text input facilities means your game might receive Russian Cyrillic characters, Japanese and Chinese Kanjis, German Umlauts, French Accents, Arabic letters and any other conceivable character you probably won't have in the SpriteFont used to display the text.

Last edited Oct 5, 2010 at 12:16 PM by Cygon, version 6

Comments

Taylank May 14, 2011 at 6:14 AM 
Here's a little backspace support for CharacterEntered event:

private void keyboardCharacterEntered(char character)
{
if (character == 8)
{
if (enteredText.Length != 0)
enteredText = enteredText.Substring(0, (enteredText.Length - 1));
}
else
{
enteredText += character;
}
}

:)