In this tutorial we are going to be building an input system for our engine. This system will support the Xbox 360 game pad, keyboard, and mouse, but could be extended to support most if not all other types of devices (assuming you know how to interface with the device, as XNA doesn’t provide support for any other devices directly).

The structure will be as follows:

InputDevice: The InputDevice class is the base class for input devices. An input device is the interface between the engine/game and the device itself. It keeps track of a state object and provides functions for getting data from the device.

The input devices we will be building are MouseDevice, KeyboardDevice, and GamePadDevice.

First, however, I want to discuss what are called “Generics”. A generic is a way for us to tell C# that our class or function can handle multiple types. A Type is a class that contains info about a class. You can get an object’s Type using MyObject.GetType(). You can get the Type of a class using typeof(ClassName). An example of a function that uses generics is XNA’s “Content.Load()” function. Since it can load multiple data types, it uses a generic to tell what kind of Type it returns. This is why we load textures using , models using , etc. (ie: “Texture2D texture = Content.Load(“Content/TextureName”) ). We tell C# what kind of generics definition we expect using in the class name line. Of course, you could use any or multiple types here, ie: . The generic can then be used like a regular type in the class. Here is an example of doing this:

public class ExampleClass <T, R>
{
	public T ReturnVariableT()
	{
		T object = new T();

		return T;
	}

	public R ReturnVariableR()
	{
		R object = new R();

		return T;
	}
}

Here is the code for the InputDevice class. It’s not very complex, since all the base class does is maintain a state object.

// The base input device class. Other input device types will
// inherit from it. The <T> generic allows us to specify what
// type of input device state it manages. (MouseState, etc.)
public abstract class InputDevice <T> : Component
{
    // The State object of type T specified above
    public abstract T State { get; }
}

We will also need to create two classes: an event argument class and event handler class, which will be used for events relating to input (like button pressed, button up, etc).

Before we get to that, however, I want to quicly discuss events. An event is exactly what it sounds like, we can define events that other classes can “hook”, by specifying a method for C# to call when the event is triggered by the owner of the event.

We create an event like this:

public event EventHandler ExampleEvent;

The “EventHandler” part of the declaration tells C# what kind of method is allowed to hook an event. EventHandler is just a delegate that accepts a few specific arguments. A delegate, in this case, is just a way of defining the type of method we want. For example, an event that specifies the EventHandler event handler will only be hooked by methods of the following format:

void FunctionToCallWhenEventIsFired(object sender, EventArgs e)
{
}

We would then hook it like this:

EventOwner.ExampleEvent += new EventHandler(FunctionToCallWhenEventIsFired);

Now, if EventOwner fires ExampleEvent, FunctionToCallWhenEventIsFired would be called and passed the object that called it as “object sender” and the data about the event in “EventArgs e”.

We can create our own class that inherits from “EventArgs” to pass our own data to the event handler. We would then need to create our own EventHandler delegate that accepts the custom event arguments class. For example, our event arguments class and event handler class:

// An input device event argument class that can handle events
// for several types of input device. "O" specified what type
// of object the event was triggered by (Key, Button,
// MouseButton, etc). "S" specifies the type of state the
// event provides (MouseState, KeyboardState, etc.)
public class InputDeviceEventArgs<O, S> : EventArgs
{
    // The object of type O that triggered the event
    public O Object;

    // The input device that manages the state of type S that
    // owns the triggered object
    public InputDevice<S> Device;

    // The state of the input device of type S that was triggered
    public S State;

    // Constructor takes the triggered object and input device
    public InputDeviceEventArgs(O Object, InputDevice<S> Device)
    {
        this.Object = Object;
        this.Device = Device;
        this.State = ((InputDevice<S>)Device).State;
    }
}

// An input device event handler delegate. This defines what type
// of method may handle an event. In this case, it is a void that
// accepts the specified input device arguments
public delegate void InputEventHandler<O, S>(object sender,
    InputDeviceEventArgs<O, S> e);

So, lets start with KeyboardDevice. Here’s the class definition, as you can see we specify that KeyboardDevice keeps track of a KeyboardState object.

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

namespace Innovation
{
    // An input device that manages the Keyboard and KeyboardState
    public class KeyboardDevice : InputDevice<KeyboardState>
    {
    }
}

Now we are going to add several members, properties, and events. They should be self explanatory:

// The last and current KeyboardStates
KeyboardState last;
KeyboardState current;

// The keys that were pressed in the current state
Keys[] currentKeys;

// Public properties for the above members
public override KeyboardState State { get { return current; } }
public Keys[] PressedKeys { get { return currentKeys; } }

// Events for when a key is pressed, released, and held. This
// event can be handled by our InputEventHandler class.
public event InputEventHandler<Keys, KeyboardState> KeyPressed;
public event InputEventHandler<Keys, KeyboardState> KeyReleased;
public event InputEventHandler<Keys, KeyboardState> KeyHeld;

Now the constructor:

// Constructor sets up the current state and updates for the
// first time
public KeyboardDevice()
{
    current = Keyboard.GetState();
    Update();
}

Now the Update() method. We start by updating the keyboard states:

public override void Update()
{
    // Set the last state to the current one and update the
    // current state
    last = current;
    current = Keyboard.GetState();

    // Set the currently pressed keys to the keys defined in
    // the current state
    currentKeys = current.GetPressedKeys();
}

Now we will check if our events need to be fired, then fire them if neccessary. We will write the functions that check for the various cases (button up, down, etc.).

First we need to create a class that will allow us to enumerate over the values of an enum, in a foreach loop. We can do this on Windows using Enum.GetValues(), but this isn’t provided in the .Net Compact Framework on Xbox and Zune, so we need to create our own. Add a new static class called Util, and add the following code:

using System;
using System.Collections.Generic;
using System.Reflection;

namespace Innovation
{
    public static class Util
    {
        public static List<T> GetEnumValues<T>()
        {
            Type currentEnum = typeof(T);
            List<T> resultSet = new List<T>();

            if (currentEnum.IsEnum)
            {
                FieldInfo[] fields = currentEnum.GetFields(BindingFlags.Static | BindingFlags.Public);
                foreach (FieldInfo field in fields)
                    resultSet.Add((T)field.GetValue(null));
            }
            else
                throw new ArgumentException("The argument must of type Enum or of a type derived from Enum", "T");

            return resultSet;
        }
    }
}

Now we can go through the values of an enum like this:

Enum ExampleEnum { One, Two, Three };

foreach(ExampleEnum value in Util.GetEnumValues<ExampleEnum>()
{
}
// For every key on the keyboard...
foreach (Keys key in Util.GetEnumValues<Keys>())
{
    // If it is a new key press (this is the first frame
    // it is down), trigger the corresponding event
    if (WasKeyPressed(key))
        if (KeyPressed != null)
            KeyPressed(this, new InputDeviceEventArgs
                <Keys, KeyboardState>(key, this));

    // If it was just released (this is the first frame
    // it is up), trigger the corresponding event
    if (WasKeyReleased(key))
        if (KeyReleased != null)
            KeyReleased(this, new InputDeviceEventArgs
                <Keys, KeyboardState>(key, this));

    // If it has been held (it has been down for more
    // than one frame), trigger the corresponding event
    if (WasKeyHeld(key))
        if (KeyHeld != null)
            KeyHeld(this, new InputDeviceEventArgs
                <Keys, KeyboardState>(key, this));
}

Now we will write the functions that check for various states (button up, down, held, released, and pressed):

// Whether the specified key is currently down
public bool IsKeyDown(Keys Key)
{
    return current.IsKeyDown(Key);
}

// Whether the specified key is currently up
public bool IsKeyUp(Keys Key)
{
    return current.IsKeyUp(Key);
}

// Whether the specified key is down for the first time
// this frame
public bool WasKeyPressed(Keys Key)
{
    if (last.IsKeyUp(Key) && current.IsKeyDown(Key))
        return true;

    return false;
}

// Whether the specified key is up for the first time
// this frame
public bool WasKeyReleased(Keys Key)
{
    if (last.IsKeyDown(Key) && current.IsKeyUp(Key))
        return true;

    return false;
}

// Whether the specified key has been down for more than
// one frame
public bool WasKeyHeld(Keys Key)
{
    if (last.IsKeyDown(Key) && current.IsKeyDown(Key))
        return true;

    return false;
}

The MouseDevice and GamepadDevice classes work similarly, so I will link to the files instead of posting the whole thing:

MouseDevice.cs
GamepadDevice.cs

And thats all there is to it! So, for example, if you wanted to monitor the keyboard and mouse, you would create instances of KeyboardDevice and MouseDevice in your game:

MouseDevice mouse = new MouseDevice();
KeyboardDevice keyboard = new KeyboardDevice();

Then to monitor them, add them add services so you can access them elsewhere:

Engine.Services.AddService(typeof(MouseDevice), mouse);
Engine.Services.AddService(typeof(KeyboardDevice), keyboard);

For example, to spawn the box to tip the others over when the mouse button is pressed, change the box spawn code in the update method to the following:

if (Engine.Services.GetService<MouseDevice>().WasButtonPressed(MouseButtons.Left))
{
    PhysicsActor act = new PhysicsActor(
        Engine.Content.Load<Model>("Content/ig_box"),
        new BoxObject(new Vector3(1), new Vector3(0, .5f, 1), Vector3.Zero));

    act.Scale = new Vector3(1);
    act.PhysicsObject.Mass = 1000;
    act.PhysicsObject.Velocity = new Vector3(0, 2, -6);
}

We now have a full input system. Other devices could be built to monitor other device types, such as the guitar, or zune input panel, or an input type that XNA doesn’t support at all.

Download the Code for this Chapter

Back to Game Engine Tutorial Series

« »