How to Process Gamepad Input
Table of Contents
Gamepad Input
The Windows works well with Microsoft Xbox controllers. Microsoft created a library called XInput.
There are two main functions to call: XInputGetState
and
XInputSetState
.
The first one is used to retrieve the user input buttons and sticks, while the latter is used to set the controller's vibration.
The definitions are included in xinput.h
.
#include <xinput.h>
To get started listening to inputs, we'll first verify for maximum number of controllers, which is currently set to 4.
For each controller, we'll check if it's connected, otherwise, we'll log an error.
u32 maxController = XUSER_MAX_COUNT; for(DWORD i = 0; i < maxController; ++i) { // u32 index = i + 1; // keyboard is zero XINPUT_STATE state; DWORD stateResult = XInputGetState(i, &state); if (stateResult == ERROR_SUCCESS) { XINPUT_GAMEPAD gamepad = state.Gamepad; bool dpadLeft = gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT; bool dpadRight = gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT; bool start = gamepad.wButtons & XINPUT_GAMEPAD_START; bool back = gamepad.wButtons & XINPUT_GAMEPAD_BACK; bool leftThumb = gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB; bool rightThumb = gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB; bool leftShoulder = gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER; bool rightShoulder = gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER; bool buttonA = gamepad.wButtons & XINPUT_GAMEPAD_A; bool buttonB = gamepad.wButtons & XINPUT_GAMEPAD_B; bool buttonX = gamepad.wButtons & XINPUT_GAMEPAD_X; bool buttonY = gamepad.wButtons & XINPUT_GAMEPAD_Y; short leftTrigger = gamepad.bLeftTrigger; short rightTrigger = gamepad.bRightTrigger; int thumbRX = gamepad.sThumbRX; int thumbRY = gamepad.sThumbRY; // TODO: Testar vibration XINPUT_VIBRATION vibration = {}; vibration.wLeftMotorSpeed = 5000; vibration.wRightMotorSpeed = 5000; XInputSetState(0, &vibration); } else if (stateResult == ERROR_DEVICE_NOT_CONNECTED) { // TODO: Dispatch errors to Event } else { // TODO: Log Error } }
Notice that we've called the definition but haven't provided the implementation, either through a static library (.lib) or a dynamic library (.dll).
If you run the code as is, you'll receive an Access Violation (null pointer) error.
To address this, let's load a dynamic library to obtain pointers to these functions, if possible. Otherwise, we'll load a stub function to avoid a crash.
Write the following code to load a dynamic library using the Windows API:
_xinputGetState = (xinput_get_state_t *) GetProcAddress(dll, "XInputGetState"); _xinputSetState = (xinput_set_state_t *) GetProcAddress(dll, "XInputSetState");
But, we need a function pointer to save the result of GetProcAddress
.
There are many ways to achieve this. However, following Casey Muratori,
I've learned to create a stub function together with #define
and
typedef
.
In fact, the C language allows you to define a type for a function pointer, which means that we can store some address dynamically.
Read the following code for a better understanding.
#define XINPUT_SET_STATE(name) DWORD name(DWORD, XINPUT_VIBRATION*) #define XINPUT_GET_STATE(name) DWORD name(DWORD, XINPUT_STATE*) typedef XINPUT_GET_STATE(xinput_get_state_t); typedef XINPUT_SET_STATE(xinput_set_state_t); static xinput_get_state_t *_xinputGetState; static xinput_set_state_t *_xinputSetState; #define XInputGetState _xinputGetState #define XInputSetState _xinputSetState XINPUT_GET_STATE(xinputGetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; } XINPUT_SET_STATE(xinputSetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; }
First, we've defined a macro with the same function signature as xinput.h
#define XINPUT_SET_STATE(name) DWORD name(DWORD, XINPUT_VIBRATION*) #define XINPUT_GET_STATE(name) DWORD name(DWORD, XINPUT_STATE*)
The macro is useful for creating a new function pointer type and the stub functions.
typedef XINPUT_GET_STATE(xinput_get_state_t); typedef XINPUT_SET_STATE(xinput_set_state_t);
The code above demostrates how to define a new "type" called
xinput_get_state_t
and xinput_set_state_t
. It's equivalent to:
typedef DWORD xinput_get_state_t(DWORD, XINPUT_STATE*);
Now, we can declare a pointer to this type. At the and of the day, it's a pointer to some function.
static xinput_get_state_t *_xinputGetState; static xinput_set_state_t *_xinputSetState; // ^ ^ // |___ type |___variable
The next step is to create an alias for this pointer. This alias will be
called by our code in the main loop. If there is a function loaded by
GetProcAddress
inside the pointer, the real gamepad input will be
processed. Otherwise, our next stub function will be called.
#define XInputGetState _xinputGetState #define XInputSetState _xinputSetState // stub functions - using macros to create it XINPUT_GET_STATE(xinputGetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; } XINPUT_SET_STATE(xinputSetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; }
Now, let's finish writing the load library function and set the stub if needed.
static void loadXInput() { _xinputSetState = xinputSetStateStub; _xinputGetState = xinputGetStateStub; HMODULE dll = LoadLibraryA("xinput1_4.dll"); if (!dll) { dll = LoadLibraryA("xinput9_1_0.dll"); } if (!dll) { dll = LoadLibraryA("xinput1_3.dll"); } if (dll) { printf("DLL xinput loaded\n"); _xinputGetState = (xinput_get_state_t *) GetProcAddress(dll, "XInputGetState"); _xinputSetState = (xinput_set_state_t *) GetProcAddress(dll, "XInputSetState"); } }
Another way to have function pointer
typedef int (t_sum)(int,int); // t_sum is the function name // function body int sum(int x, int y) { return x + y; } // allocate a pointer to the type t_sum and set the concrete function t_sum *my_sum = ∑ // running the function int res = (*my_sum)(2, 2);