Logo of the Nuclex Framework, the text "Nuclex" with three green dots on a blue ring

New Component: Nuclex.Input

The word 'Nuclex' in a stylish font framed by an elliptical ring with three dots

Developers following my twitter feed may already know that in the past few days, I’ve been working on a new component for the Nuclex Framework: Nuclex.Input. This component aims to solve all problems I ever had with input devices in XNA :)

It’s a very simple library that provides input device classes similar to the ones in XNA (Keyboard, Mouse, GamePad), but instead of 4 game pads, there are 8 (with indexes 5-8 for DirectInput-based controllers). All input devices provide events (like KeyPressed and CharacterEntered on the keyboard or MouseWheelRotated on the mouse, for example). Here’s a quick run down of the features:

  • Well-behaving keyboard text input

    • Honors keyboard layout and system locale
    • Supports XBox 360 chat pads
    • Very easy to use: just subscribe to an event
  • Support for standard PC game controllers

    • Works with any DirectInput-compatible controller
  • Mouse movement with sub-pixel accuracy (postponed)

    • Finally put those expensive high-dpi mice to use ;-)
  • Allows event-based input handling

    • Fully type-safe: events instead of message objects
    • Only compares states if events have subscribers
    • Mouse and keyboard don’t have to compare states at all
  • Zero garbage: doesn’t feed the garbage collector

    • During usage, the library produces zero garbage

Curious? Click on “Read More” to view some code samples!

Download

This component will be in the next release of the Nuclex Framework!
If you want it now: Nightly builds, Source code (svn)

Design Overview

If you understand UML, here’s an overview of the library’s design:

UML diagram showing the input manager and input devices classes in Nuclex.Input

Specific usage examples follow below!

Initialization

To use Nuclex.Input, you simply create an instance of its InputManager and add it to the Game.Components collection.

Advanced users can also specify a custom window handle and make use of the component without having it register itself in the Game.Services container. But let’s keep it simple for now:

// Highlight 3,12,16,20
using Microsoft.Xna.Framework;

using Nuclex.Input;

namespace TestGame {

  /// <summary>This is the main type for your game</summary>
  public class Game1 : Microsoft.Xna.Framework.Game {

    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    InputManager input;

    public Game1() {
      graphics = new GraphicsDeviceManager(this);
      input = new InputManager(Services);

      Content.RootDirectory = "Content";
      
      Components.Add(input);
    }

    // ...

  }

} // namespace TestGame

Getting the Game Pad State

This is as simple as it was before. Instead of using the GetState() on XNA’s Keyboard, Mouse or GamePad classes, you ask the input manager for the device and then for its state:

// Highlight 17,18,19
/// <summary>
///   Allows the game to run logic such as updating the world,
///   checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime) {

  /* old code:

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

  */

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

  // TODO: Add your update logic here

  base.Update(gameTime);
}

As you can see, instead of calling GamePad.GetState(PlayerIndex.One) the above snippet calls input.GetGamePad(PlayerIndex.One).GetState(). That basically all there is to it.

In addition to the 4 XBox 360 controllers that XNA’s GamePad class allows you to access, the InputManager provides 4 additional game pads that represent any DirectInput-compatible joysticks or game pads attached to the system. So game pads 1-4 are XNA’s XBox 360 game pads and game pads 5-7 are standard DirectInput-compatible game pads. You can query them like so:

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

Note the ExtendedPlayerIndex enumeration. Also important is that there are always 8 game pads you can query. This is identical in behavior to XNA’s GamePad class which allows you to query non-existent game pads. If there are less than 4 DirectInput-compatible controllers in a system, the remaining slots will be filled with dummies that simply do nothing and return a state with all buttons released and all sticks in neutral positions.

In other words, you can safely call GetGamePad(ExtendedPlayerIndex.Five) and it will always work. If a DirectInput-compatible game pad is attached, pressing the back button on it will exit the game. If no DirectInput-compatible game pad is attached, the game pad will simply report that the button is not pressed all the time. This is very fast.

Event-based Input

Sometimes, you’ll want to perform an action only when a button is pressed down or when a certain key is pressed. This requires you to keep the previous state of an input device and check whether the state of a button has changed from the last time the controller’s state was queried:

previous = current;
current = GamePad.GetState(PlayerIndex.One);

bool pressedInThisFrame =
  (previous.Buttons.A == ButtonState.Released) &&
  (current.Buttons.A == ButtonState.Pressed);

if(pressedInThisFrame) {
  doSomething();
}

With event-based input, this has become a lot simpler:

IGamePad gamePad = input.GetGamePad(PlayerIndex.One);
gamePad.ButtonPressed += delegate(Buttons buttons) {
  if((buttons & Buttons.A) != 0) {
    doSomething();
  }
};

There are various ways in which event-based input can be designed, the most popular one probably being message objects that are passed around. I decided against this design because it tends to create unsightly code (long methods with dozens of switch cases), loses type safety and requires additional user interaction to allow for an important performance optimization: only comparing the states of those devices in whose events the user is actually interested. This comes free with events because each device can simply look if its ButtonPressed etc. event has any subscribers before looking for state changes.

Event-based input is ideal for GUI libraries because GUIs need to route input notifications down the control tree. For example, if you press space with a text box in focus, a GUI would add a space character to the text box, but if you press space with a button in focus, key press is routed to the button instead of the text box and might cause it to be pushed down.

Text Entry

This is another often-needed feature.

You can certainly write a text input system that uses XNA’s Keyboard class, but that would

  • force you to check all 256 keys for state changes during each update cycle
  • require hand-coding the handling of shift, caps lock, num lock and other keys
  • still not respect the keyboard layout the user has selected. The top row of french keyboards, for example, reads “azerty”, not “qwerty”. Then there is an accent key, a special shift key called “alt gr”, …

The way normal windows applications receive their text input is via a special window message called WM_CHAR. Nuclex.Input subclasses XNA’s main window and intercepts this message so that its keyboard device can provide you with plain chars that you can simply append to a string, no matter whether you game is being played on a French, German, Chinese or Russian keyboard.

All you have to do is subscribe to the Keyboards CharacterEntered event:

/// <summary>
///   Allows the game to perform any initialization it needs to before starting to run.
///   This is where it can query for any required services and load any non-graphic
///   related content.  Calling base.Initialize will enumerate through any components
///   and initialize them as well.
/// </summary>
protected override void Initialize() {
  base.Initialize();

  // TODO: Add your initialization logic here

  IKeyboard keyboard = input.GetKeyboard();
  keyboard.CharacterEntered += characterEntered;
}

void characterEntered(char character) {
  Trace.WriteLine("User has typed the character '" + character + "'");
}

How easy is that!

Of course this still leaves you to implement any luxury features such as backspace, movable caret or text selection :)