Beginnersdl3
Beginner

Use Gamepads and Joysticks with SDL3

Learn how to use gamepads and joysticks with SDL3 to control your games using external controllers in this beginner-friendly tutorial.

Prerequisites / Tools :
  • SDL3
Nicolas Perdu
Written by Nicolas PerduSoftware engineer

In this tutorial the goal is to be able to use a gamepad or a joystick with SDL3, for that I will use a Xbox controller.

Initialize and open the gamepad

The first thing to check is whether a gamepad is connected. For that, you can use the function: SDL_HasGamepad

if (!SDL_HasGamepad()) {
    std::cout << "No gamepads connected.\n";
    SDL_Quit();
    return 0;
}

After we know at least one gamepad is connected, we can list the gamepads and pick the first one. The function to list the gamepads is: SDL_GetGamepads

int count = 0;
SDL_JoystickID *ids = SDL_GetGamepads(&count);
SDL_Gamepad* gamepad = NULL;

// Iterate over the list of gamepads
for (int i = 0; i < count; i++) {
    SDL_Gamepad* gamepd = SDL_OpenGamepad(ids[i]);
    if (gamepad == NULL) {
        gamepad = gamepd;
    }
    std::cout << "Gamepad connected: " << SDL_GetGamepadName(gamepd) << "\n";
    
    // Close the other gamepads
    if (i > 0) {
        SDL_CloseGamepad(gamepd);
    }
}

if (!gamepad) {
    std::cerr << "Failed to open gamepad: " << SDL_GetError() << "\n";
    SDL_Quit();
    return 1;
}

Record all the events

After the gamepad is opened, we can record the event when a button is pressed (SDL_EVENT_GAMEPAD_BUTTON_DOWN) and released (SDL_EVENT_GAMEPAD_BUTTON_UP), or when a joystick is moved (SDL_EVENT_GAMEPAD_AXIS_MOTION):

bool running = true;
SDL_Event event;

while (running) {
    while (SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_EVENT_QUIT:
                running = false;
                break;
            case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
                std::cout << "Button pressed: "
                          << SDL_GetGamepadStringForButton((SDL_GamepadButton)event.gbutton.button)
                          << "\n";
                break;
            case SDL_EVENT_GAMEPAD_BUTTON_UP:
                std::cout << "Button released: "
                          << SDL_GetGamepadStringForButton((SDL_GamepadButton)event.gbutton.button)
                          << "\n";
                break;
            case SDL_EVENT_GAMEPAD_AXIS_MOTION:
                std::cout << "Axis moved: "
                          << SDL_GetGamepadStringForAxis((SDL_GamepadAxis)event.gaxis.axis)
                          << " Value: " << event.gaxis.value << "\n";
                break;
        }
    }
    SDL_Delay(16); // Sleep 16ms (~60FPS)
}

Define a deadzone to have more precise triggers

If you test this code, you can see the axes of the joystick are triggered very easily. To avoid that, you need to define a threshold for a deadzone; this threshold is a constant:

Thumbstick axis values range from SDL_JOYSTICK_AXIS_MIN (-32768) to SDL_JOYSTICK_AXIS_MAX (32768), and are centered within ~8000 of zero, though advanced UI will allow users to set or autodetect the dead zone, which varies between gamepads.

bool running = true;
SDL_Event event;
const int DEADZONE = 9000;

while (running) {
    while (SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_EVENT_QUIT:
                running = false;
                break;
            case SDL_EVENT_GAMEPAD_AXIS_MOTION:
                if (event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFTX && event.gaxis.value > DEADZONE) {
                    std::cout << "left axis right\n";
                } else if (event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFTX && event.gaxis.value < -DEADZONE) {
                    std::cout << "left axis left\n";
                }
                
                if (event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFTY && event.gaxis.value > DEADZONE) {
                    std::cout << "left axis down\n";
                } else if (event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFTY && event.gaxis.value < -DEADZONE) {
                    std::cout << "left axis up\n";
                }
                break;
        }
    }
    SDL_Delay(16); // Sleep ~16ms (~60FPS)
}

Values of SDL_GamepadButtonLabel and SDL_GamepadAxis

The buttons (event.gbutton.button) for the gamepad can have these values:

The values for the axis (event.gaxis.axis) are the following:

Need another OS ? => Windows, Mac, Linux