Include the headers
vector>
<span class="hljs-selector-id">#include <<span class="hljs-selector-tag">SDL_gamecontroller<span class="hljs-selector-class">.h>
The vector is needed to hold the attached game controllers so our games can work with multiple players.
Create a structure for the controller data
Each controller gets a struct to hold the button states and values of each axis.
struct GamePad {
bool buttons[SDL_CONTROLLER_BUTTON_MAX];
int axis[SDL_CONTROLLER_AXIS_MAX];
}
Set up variables
We’re keeping a list of connected controllers, and then two lists of controller input states. Having a previous as well as the current state means checking for a button being pressed and then released is easy.
SDL_GameController*> connectedControllers;
std::vector<span class="hljs-tag"><<span class="hljs-name">GamePad> controllerInputs;
std::vector<span class="hljs-tag"><<span class="hljs-name">GamePad> lastControllerInputs;
int numGamepads;
Give things meaningful names with enums
Nobody wants to be asking for controllerInputs[0].buttons[0]
in their code, they want to be writing controllerInputs[PLAYER1].buttons[SDL_CONTROLLER_BUTTON_A]
because self documenting code is good. Note that this isn’t how the code actually works, there’s functions to get the button states.
enum Controllers {PLAYER1, PLAYER2, PLAYER3, PLAYER4};
Buttons are referenced by SDL’s own enums which are found in SDL_gamecontroller.h
/**
* The list of buttons available from a controller
*/
typedef enum
{
SDL_CONTROLLER_BUTTON_INVALID = -1,
SDL_CONTROLLER_BUTTON_A,
SDL_CONTROLLER_BUTTON_B,
SDL_CONTROLLER_BUTTON_X,
SDL_CONTROLLER_BUTTON_Y,
SDL_CONTROLLER_BUTTON_BACK,
SDL_CONTROLLER_BUTTON_GUIDE,
SDL_CONTROLLER_BUTTON_START,
SDL_CONTROLLER_BUTTON_LEFTSTICK,
SDL_CONTROLLER_BUTTON_RIGHTSTICK,
SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
SDL_CONTROLLER_BUTTON_DPAD_UP,
SDL_CONTROLLER_BUTTON_DPAD_DOWN,
SDL_CONTROLLER_BUTTON_DPAD_LEFT,
SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
SDL_CONTROLLER_BUTTON_MAX
} SDL_GameControllerButton;
Initialise SDL’s game controller subsystem
There’s quite a bit of code here, but all it does is
- Initialise SDL’s game controller system
- Count how many controllers are connected and add them to a list
- Clear the vectors of button/stick states
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) != 1)
SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
int nJoysticks = SDL_NumJoysticks();
numGamepads = 0;
// Count how many controllers there are
for (int i = 0; i < nJoysticks; i++)
if (SDL_IsGameController(i))
numGamepads++;
// If we have some controllers attached
if (numGamepads > 0)
{
for (int i = 0; i < numGamepads; i++)
{
// Open the controller and add it to our list
SDL_GameController* pad = SDL_GameControllerOpen(i);
if (SDL_GameControllerGetAttached(pad) == 1)
connectedControllers.push_back(pad);
else
std::cout << "SDL_GetError() = " << SDL_GetError() << std::endl;
}
SDL_GameControllerEventState(SDL_ENABLE);
}
// Vectors are empty to begin with, this sets their size
controllerInputs.resize(numGamepads);
lastControllerInputs.resize(numGamepads);
// Set the status of the controllers to "nothing is happening"
for (int i = 0; i < numGamepads; i++) {
for (int a = 0; a < SDL_CONTROLLER_AXIS_MAX; a++) {
controllerInputs[i].axis[a] = 0;
lastControllerInputs[i].axis[a] = 0;
}
for (int b = 0; b < SDL_CONTROLLER_BUTTON_MAX; b++) {
controllerInputs[i].buttons[b] = false;
lastControllerInputs[i].buttons[b] = false;
}
}
Game loop code
This small piece of code copies the button states from the previous frame into our “previous” list.
for (int i = 0; i < numGamepads; i++) {
for (int a = 0; a < SDL_CONTROLLER_AXIS_MAX; a++) {
lastControllerInputs[i].axis[a] = controllerInputs[i].axis[a];
}
for (int b = 0; b < SDL_CONTROLLER_BUTTON_MAX; b++) {
lastControllerInputs[i].buttons[b] = controllerInputs[i].buttons[b];
}
}
SDL’s event system is used to actually update the controller states
SDL_Event event;
if (SDL_PollEvent(&event))
{
switch (event.type) {
case SDL_QUIT:
return false;
break;
// This happens when a device is added
// A future improvement is to cope with new controllers being plugged in
// when the game is running
case SDL_CONTROLLERDEVICEADDED:
std::cout << "DEVICEADDED cdevice.which = " << event.cdevice.which << std::endl;
break;
// If a controller button is pressed
case SDL_CONTROLLERBUTTONDOWN:
// Cycle through the controllers
for (int i = 0; i < numGamepads; i++) {
// Looking for the button that was pressed
if (event.cbutton.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(connectedControllers[i]))) {
// So the relevant state can be updated
controllerInputs[i].buttons[event.cbutton.button] = true;
}
}
break;
// Do the same for releasing a button
case SDL_CONTROLLERBUTTONUP:
for (int i = 0; i < numGamepads; i++) {
if (event.cbutton.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(connectedControllers[i]))) {
controllerInputs[i].buttons[event.cbutton.button] = false;
}
}
break;
// And something similar for axis motion
case SDL_CONTROLLERAXISMOTION:
for (int i = 0; i < numGamepads; i++) {
if (event.cbutton.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(connectedControllers[i]))) {
controllerInputs[i].axis[event.caxis.axis] = event.caxis.value;
}
}
break;
}
}
Use the controller data easily
Checking nested structures within vectors would quickly become tedious, so I wrote some routines to do it for me. They’re part of a larger “input” class, but the logic should make sense.
Has a button been pressed?
bool Input::isControllerButtonPressed(Inputs::Controllers controllerID, SDL_GameControllerButton button)
{
if (controllerID < 0 || controllerID > numGamepads) return false;
return controllerInputs[controllerID].buttons[button] && !lastControllerInputs[controllerID].buttons[button];
}
Is a button being pressed?
bool Input::isControllerButtonHeld(Inputs::Controllers controllerID, SDL_GameControllerButton button)
{
if (controllerID < 0 || controllerID > numGamepads) return false;
return controllerInputs[controllerID].buttons[button] && lastControllerInputs[controllerID].buttons[button];
}
Get an axis value
float Input::getControllerAxis(Inputs::Controllers controllerID, SDL_GameControllerAxis axis)
{
if (controllerID < 0 || controllerID > numGamepads) return 0.0;
return controllerInputs[controllerID].axis[axis] / 32768.0f;
}
Example
This code is inside the update loop of my player character.
if (inputs->isControllerButtonHeld(PLAYER1, SDL_CONTROLLER_BUTTON_DPAD_LEFT)) {
rightAcc = -acceleration/3.0;
} else
if (inputs->isControllerButtonHeld(PLAYER1, SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) {
leftAcc = -acceleration/3.0;
} else
if (inputs->isControllerButtonHeld(PLAYER1, SDL_CONTROLLER_BUTTON_DPAD_UP)) {
leftAcc = rightAcc = acceleration;
} else
if (inputs->isControllerButtonHeld(PLAYER1, SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
leftAcc = rightAcc = -acceleration;
}
if (inputs->isControllerButtonPressed(PLAYER1, SDL_CONTROLLER_BUTTON_A)) {
Vec2D temp = (muzzle->transform->globalPosition - transform->globalPosition).getNormal() * 10;
bullets->Emit(1, muzzle->transform->globalPosition, temp);
leftAcc = rightAcc = -acceleration * 5;
assets->GetSample("shoot")->Play();
}
References
I used these sites: