The fundamentals - AderitoSilva/XInputium GitHub Wiki

XInputium provides several ways for you to consume its API. It provides its functionality in layers of abstraction, where the more low-level layers offer fewer features, thus being more lightweight, and the higher-level layers offer the most complete set of features. This provides consumers multiple options for consuming the API, depending on their needs and requirements.

XInputium's higher abstraction level layers abstract the lower level ones, so, in this page, we will start by covering the lower-level layers first, and then we will cover the higher-level, more feature rich, layers. Most users would use the more feature rich ways of consuming the API, but you're encouraged to read this page in its full, in order to better understand what's under the hood.

The three main ways of consuming the API are using:

  • XInputDevice static members — Provides only the most basic raw data about XInput devices and leaves most of the work for you.
  • XInputDevice instances — Implements a basic input loop, provides simple state management features and some events.
  • XGamepad instances — Provides the complete XInput feature set of XInputium. This is what most users would use.

In the following sections, we will cover all these in more detail.

XInputDevice static members

The more low-level way of consuming the API starts with the static members of XInputDevice class, which provide you only basic raw data about the current state of an XInput compatible device. This approach is intended for scenarios when you don't need the extra features provided by higher-level approaches.

Get a device state

XInputDevice.GetState(userIndex) static method accepts a constant user index as parameter and returns an XInputDeviceState structure that represents the current state of the XInput device currently connected at the specified user index, if any is connected in the system. The returned structure can be compared for equality with another instance of 'XInputDeviceState' to determine if the state has changed.

Example

// Call this on every game/application frame.
XInputDeviceState state = XInputDevice.GetState(XInputUserIndex.One);

if (state.IsConnected)
{
    Debug.WriteLine($"The device is connected. Left joystick: {state.LeftJoystick}");
}
else
{
    Debug.WriteLine("The device is not connected.");
}

Set a device state

Similarly, XInputDevice.SetState() static method can be used to set the state of a connected XInput device, if any. Currently, the only state you can set to the device is the speed of its motors, to adjust the gamepad's vibration.

Example

// Call this whenever you need to change the vibration of the device.
// The device will keep vibrating until you set the motors speed to 0 or your application exits.
if (XInputDevice.SetState(XInputUserIndex.One, 0f, 0.5f))
{
    Debug.WriteLine("The device is connected and its vibration was successfully set.");
}
else
{
    Debug.WriteLine("The device is not connected.");
}

XInputDevice instances

An XInputDevice class instance represents a specific XInput device that may or may not be connected into the system, and abstracts the low-level approach described above. You create an instance of XInputDevice class, passing it a specific constant XInput user index, and use its instance members to get and set the state of the device. XInputDevice implements System.ComponentModel.INotifyPropertyChanged interface, and notifies you when one of its properties' value is changed, and provides events that notify you about changes to the device.

Get a device state

XInputDevice can provide its events and notification functionality by implementing its own input loop. This input loop works by providing you the Update() method, which you would call on every game or application frame. When you call XInputDevice.Update() method, the XInputDevice instance gets the current state of the associated XInput device — which you can then get using XInputDevice.CurrentState property — and moves the old device state (the state on the previous update) to the XInputDevice.PreviousState property. These states are the same XInputDeviceState structures you would get when calling XInputDevice.GetState() static method. During your call to XInputDevice.Update() method, XInputDevice compares the new state with the old state, and updates its properties, and triggers its events accordingly. For instance, its Connected and Disconnected events will trigger, if the new state indicates the device was just connected or disconnected.

Example

XInputDevice device = new(XInputUserIndex.One);

// Call this on every game/application frame.
if (device.Update())
{
    Debug.WriteLine($"Is button Start pressed?: {device.CurrentState.IsButtonPressed(XButtons.Start)}");
    Debug.WriteLine($"Was button Start pressed?: {device.PreviousState.IsButtonPressed(XButtons.Start)}");
}
else
{
    Debug.WriteLine("The device state hasn't changed since the last update.");
}

Set a device state

XInputDevice enables you to set the state of the device by providing you members like LeftMotorSpeed and RightMotorSpeed properties and SetMotorsSpeed() method.

Example

XInputDevice device = new(XInputUserIndex.One);

// Call this when you need to set the device vibration.
// This performs one single XInput API call.
device.SetMotorsSpeed(0.5f, 0.8f);

// Alternatively, you can use properties to do the same.
// This performs two XInput API calls, one for each motor.
device.LeftMotorSpeed = 0.5f;
device.RightMotorSpeed = 0.5f;

Time measurement

Although XInputDevice instances implement the concept of an input loop and provide you with a more convenient way of managing input states, their input loop doesn't consider time elapsed between states. Time measurement is implemented by the more abstract objects, described next.

XGamepad instances

Most of XInputium features for XInput devices are provided through its high-level objects. The XGamepad class encapsulates an XInputDevice instance and extends the functionality it provides. XGamepad is the class you would use to get the most XInput related features XInputium provides.

While an XInputDevice instance represents an XInput device connected at a specific XInput user index, thus representing a unique device, an XGamepad instance represents a logical XInput device. What this means is that you can instantiate a new XGamepad class and use that single instance throughout the whole lifetime of your game or application. You can set the XInputDevice instance an XGamepad instance abstracts at any moment, by setting its XGamepad.Device property.

Example

// Create an XGamepad that encapsulates the XInputDevice.
XInputDevice device = new(XInputUserIndex.One);
XGamepad gamepad1 = new(device);
Debug.WriteLine($"Using device of user {gamepad1.Device!.UserIndex}.");

// Alternatively, you can do the same by using another constructor.
// This creates an underlying XInputDevice instance implicitly.
XGamepad gamepad2 = new(XInputUserIndex.One);
Debug.WriteLine($"Using device of user {gamepad2.Device!.UserIndex}.");

// Another way of doing this is to set the 'Device' property directly.
XGamepad gamepad3 = new();
gamepad3.Device = new XInputDevice(XInputUserIndex.One);
Debug.WriteLine($"Using device of user {gamepad3.Device.UserIndex}.");

Get device state

Getting the state of an XInput device using an XGamepad instance is similar to how it is in an XInputDevice instance. You call XGamepad.Update() method on every game or application frame. Alternatively, you can also update its underlying XInputDevice directly, by calling XGamepad.Device.Update() method. However, XGamepad provides you high level properties that abstract the input state and doesn't provide the concept of current and previous state, in contrast to XInputDevice. Instead, it uses events to notify you about specific state changes — for instance, when a button is pressed or released, or when a joystick is moved.

Example

XGamepad gamepad = new();  // This uses the first connected device, if any.
gamepad.LeftJoystickMove += (s, e)
    => Debug.WriteLine($"Left joystick moved to: {gamepad.LeftJoystick.Delta.Direction}");

// Call this on every game/application frame.
gamepad.Update();

Set device state

To set the device state (the motors' speed) with XGamepad, you use LeftMotorSpeed and RightMotorSpeed properties. However, unlike XInputDevice, the motor speed will only be effectively changed during the next call to XGamepad.Update() method. This way, only one single XInput API call is performed. If you need to set the motors' speed immediately, you can still use the underlying XInputDevice.

Example

XGamepad gamepad = new();  // This uses the first connected device, if any.
gamepad.LeftMotorSpeed = 0.5f;

// Call this on every game/application frame.
// The vibration will only be applied after you call Update() method.
gamepad.Update();

Time measurement and input loop

XGamepad measures elapsed time between calls to its Update() method. It needs to be aware of elapsed time to implement some of its features. It uses the concept of an input loop, as implemented by the underlying XInputDevice, and extends it with time measurement functionality. However, it abstracts most of the input loop from consumers and offers its state in the form of events.

Because XGamepad doesn't adhere to the concept of get/set state, its Update() method works more as a way to advance the input loop by one more iteration. All input related events of XGamepad are fired only during a call to its Update() method.

Event dispatching

Many XInputium objects that derive from EventDispatcherObject use event dispatching to stack events and trigger them all at once when requested. EventDispatcherObject implements System.ComponentModel.INotifyPropertyChanged interface, to notify consumers about property changes. XGamepad inherits indirectly from EventDispatcherObject and stacks events until the Update() method is called. This means you can change several of its properties with the guarantee that all properties will have their new value when each PropertyChanged event is fired, assuming you call Update() method after all properties were set.

Dynamic events

XGamepad notifies you about specific input state changes through regular .NET events. To consume them, you just need to subscribe to these events. However, it also implements the concept of dynamic events. In this context, dynamic events are events that are triggered only when specific conditions you provide are met. This enables you to get notified, for example, when a specific button is held by a specific amount of time, which is something that would not be viable with regular .NET events.

You can subscribe to dynamic events of XGamepad by calling their respective registration methods, which by convention are named Register<EventName>Event(). These methods return an object that can be used later to unregister the event, if necessary.

Example

XGamepad gamepad = new();

// Subscribe a dynamic event that fires when button A is held for 250ms.
var buttonAHoldEvent = gamepad.RegisterButtonHoldEvent(
    XButtons.A, TimeSpan.FromMilliseconds(250), ButtonHeld);

// Call this on every game/application frame.
gamepad.Update();

// Optionally, unregister the event, later on, when you don't need it anymore.
gamepad.UnregisterInputEvent(buttonAHoldEvent);

// This method will handle the event, just like it would with a regular .NET event.
void ButtonHeld(object? sender, DigitalButtonInputEventArgs<XInputButton> e)
{
    Debug.WriteLine($"Button {e.Button} was held.");
}

XInputDeviceManager

XInputDeviceManager class represents a set of XInputDevice objects and provides easier access to all XInput devices in the system (connected or not). You would use an instance of XInputDeviceManager to determine what XInput devices are connected and to update their state all at once. XInput API supports up to four connected devices on a single system, and you can access each one using XInputDeviceManager.UserOne, XInputDeviceManager.UserTwo, XInputDeviceManager.UserThree and XInputDeviceManager.UserFour properties. You can also get a collection that contains only the currently connected devices using XInputDeviceManager.ConnectedDevices property. XInputDeviceManager class also provides you with events that notify you when a device is connected or disconnected.

To provide its functionality, XInputDeviceManager instances rely on an input loop. You call XInputDeviceManager.Update() method to update the state of all devices, and to advance the input loop by one iteration. Any of XInputDeviceManager's events will be triggered during a call to this method. When you call its Update() method, all of its XInputDevice instances are updated, so you won't need to update them manually. For instance, if you call XInputDeviceManager.Update() method on every game or application frame, you won't need to call XGamepad.Update() method, if that XGamepad instance is encapsulating an XInputDevice instance obtained from the XInputDeviceManager.

Example 1

XGamepad gamepad = new();  // Our application-wide gamepad instance.

XInputDeviceManager deviceManager = new();  // Our application-wide device manager instance.
deviceManager.DeviceDisconnected += DeviceDisconnected;

// Call this every time you want to get the connected/disconnected
// state of the devices. It could be on every game/application frame.
// Note that this updates the state of the devices, so an XGamepad 
// instance encapsulating one of these device instances will get
// updated automatically. If you call this on every frame, you won't
// need to call gamepad.Update() method at all.
deviceManager.Update();  // Events will be triggered now.

// This method handles the DeviceDisconnected event.
void DeviceDisconnected(object? sender, XInputDeviceEventArgs e)
{
    Debug.WriteLine($"Device {e.Device.UserIndex} has just disconnected.");
    // If our XGamepad has no device or if it has an unconnected device, 
    // use the first connected device available, if there is any.
    if (!gamepad.IsConnected)
    {
        gamepad.Device = deviceManager.ConnectedDevices.FirstOrDefault();
    }
}

The example above, uses an XGamepad instance to obtain input data, and uses an XInputDeviceManager instance to determine what devices are connected. This specific approach gets the state of all four devices on every game/application frame, but you could also only update the XInputDeviceManager when the XGamepad has no connected devices. The following example demonstrates that:

Example 2

XGamepad gamepad = new();  // Our application-wide gamepad instance.
XInputDeviceManager deviceManager = new();  // Our application-wide device manager instance.
deviceManager.DeviceConnected += DeviceConnected;

// Call this on every game/application frame.
if (gamepad.IsConnected)
    gamepad.Update();
else
    deviceManager.Update();

// This method handles the DeviceConnected event.
void DeviceConnected(object? sender, XInputDeviceEventArgs e)
{
    if (!gamepad.IsConnected)
    {
        gamepad.Device = e.Device;
    }
}

You are not required to use XInputDeviceManager to determine what XInput devices are currently connected, it is just a helper class that aims to make it easier for you in some scenarios. A common and more lightweight alternative would be to use one of the static methods of XInputDevice class, like XInputDevice.GetFirstConnectedDevice() method. The following example, shows how you could continuously look for a new connected XInput device when your XGamepad instance has no connected device:

Example 3

XGamepad gamepad = new();

// Run this on every game/application frame.
gamepad.Update();
if (!gamepad.IsConnected)
{
    gamepad.Device = XInputDevice.GetFirstConnectedDevice();
}

The approach in this last example is much simpler and more lightweight than using an XInputDeviceManager instance. Nonetheless, using XInputDeviceManager can be a better option in situations where you need to determine the connected/disconnected state of several XInput devices at once. The demo application that ships with XInputium — XInputium Preview — uses XInputDeviceManager to make it simpler to bind its UI elements to the state of each XInput device, as this class implements property change notification functionality.

Tutorials

Now, that you have learned about the fundamentals of XInputium, you have the necessary foundation to start bringing XInput into your game or applications. The Tutorials page contains simple examples on how you could code the most common gamepad related tasks on your game or application, using XInputium.

⚠️ **GitHub.com Fallback** ⚠️