Gamepad (Controller, Joystick)

Relevant official examples: gamepad_input, gamepad_input_events.


Bevy has support for gamepad input hardware: console controllers, joysticks, etc. Many different kinds of hardware should work, but if your device is not supported, you should file an issue with the gilrs project.

Gamepad IDs

Bevy assigns a unique ID (Gamepad) to each connected gamepad. This lets you associate the device with a specific player and distinguish which one your inputs are coming from.

You can use the Gamepads resource to list the IDs of all the currently connected gamepad devices, or to check the status of a specific one.

To detect when gamepads are connected or disconnected, you can use GamepadEvent events.

Example showing how to remember the first connected gamepad ID:

/// Simple resource to store the ID of the connected gamepad.
/// We need to know which gamepad to use for player input.
struct MyGamepad(Gamepad);

fn gamepad_connections(
    mut commands: Commands,
    my_gamepad: Option<Res<MyGamepad>>,
    mut gamepad_evr: EventReader<GamepadEvent>,
) {
    for GamepadEvent(id, kind) in gamepad_evr.iter() {
        match kind {
            GamepadEventType::Connected => {
                println!("New gamepad connected with ID: {:?}", id);

                // if we don't have any gamepad yet, use this one
                if my_gamepad.is_none() {
                    commands.insert_resource(MyGamepad(*id));
                }
            }
            GamepadEventType::Disconnected => {
                println!("Lost gamepad connection with ID: {:?}", id);

                // if it's the one we previously associated with the player,
                // disassociate it:
                if let Some(MyGamepad(old_id)) = my_gamepad.as_deref() {
                    if old_id == id {
                        commands.remove_resource::<MyGamepad>();
                    }
                }
            }
            // other events are irrelevant
            _ => {}
        }
    }
}

Handling Gamepad Inputs

You can handle the analog sticks and triggers with Axis<GamepadAxis> (Axis, GamepadAxis). Buttons can be handled with Input<GamepadButton> (Input, GamepadButton), similar to mouse buttons or keyboard keys.

Notice that the names of buttons in the GamepadButton are vendor-neutral (like South and East instead of X/O or A/B).

fn gamepad_input(
    axes: Res<Axis<GamepadAxis>>,
    buttons: Res<Input<GamepadButton>>,
    my_gamepad: Option<Res<MyGamepad>>,
) {
    let gamepad = if let Some(gp) = my_gamepad {
        // a gamepad is connected, we have the id
        gp.0
    } else {
        // no gamepad is connected
        return;
    };

    // The joysticks are represented using a separate axis for X and Y

    let axis_lx = GamepadAxis(gamepad, GamepadAxisType::LeftStickX);
    let axis_ly = GamepadAxis(gamepad, GamepadAxisType::LeftStickY);

    if let (Some(x), Some(y)) = (axes.get(axis_lx), axes.get(axis_ly)) {
        // combine X and Y into one vector
        let left_stick_pos = Vec2::new(x, y);

        // Example: check if the stick is pushed up
        if left_stick_pos.length() > 0.9 && left_stick_pos.y > 0.5 {
            // do something
        }
    }

    // In a real game, the buttons would be configurable, but here we hardcode them
    let jump_button = GamepadButton(gamepad, GamepadButtonType::South);
    let heal_button = GamepadButton(gamepad, GamepadButtonType::East);

    if buttons.just_pressed(jump_button) {
        // button just pressed: make the player jump
    }

    if buttons.pressed(heal_button) {
        // button being held down: heal the player
    }
}

You can also handle gamepad inputs using GamepadEvent events:

fn gamepad_input_events(
    my_gamepad: Option<Res<MyGamepad>>,
    mut gamepad_evr: EventReader<GamepadEvent>,
) {
    let gamepad = if let Some(gp) = my_gamepad {
        // a gamepad is connected, we have the id
        gp.0
    } else {
        // no gamepad is connected
        return;
    };

    for GamepadEvent(id, kind) in gamepad_evr.iter() {
        if id.0 != gamepad.0 {
            // event not from our gamepad
            continue;
        }

        use GamepadEventType::{AxisChanged, ButtonChanged};

        match kind {
            AxisChanged(GamepadAxisType::RightStickX, x) => {
                // Right Stick moved (X)
            }
            AxisChanged(GamepadAxisType::RightStickY, y) => {
                // Right Stick moved (Y)
            }
            ButtonChanged(GamepadButtonType::DPadDown, val) => {
                // buttons are also reported as analog, so use a threshold
                if *val > 0.5 {
                    // button pressed
                }
            }
            _ => {} // don't care about other inputs
        }
    }
}

Gamepad Settings

You can use the GamepadSettings resource to configure dead-zones and other parameters of the various axes and buttons. You can set the global defaults, as well as individually per-axis/button.

Here is an example showing how to configure gamepads with custom settings (not necessarily good settings, please don't copy these blindly):

// this should be run once, when the game is starting
// (transition entering your in-game state might be a good place to put it)
fn configure_gamepads(
    my_gamepad: Option<Res<MyGamepad>>,
    mut settings: ResMut<GamepadSettings>,
) {
    let gamepad = if let Some(gp) = my_gamepad {
        // a gamepad is connected, we have the id
        gp.0
    } else {
        // no gamepad is connected
        return;
    };

    // add a larger default dead-zone to all axes (ignore small inputs, round to zero)
    settings.default_axis_settings.negative_low = -0.1;
    settings.default_axis_settings.positive_low = 0.1;

    // make the right stick "binary", squash higher values to 1.0 and lower values to 0.0
    let right_stick_settings = AxisSettings {
        positive_high:  0.5, // values  0.5 to  1.0, become  1.0
        positive_low:   0.5, // values  0.0 to  0.5, become  0.0
        negative_low:  -0.5, // values -0.5 to  0.0, become  0.0
        negative_high: -0.5, // values -1.0 to -0.5, become -1.0
        // the raw value should change by at least this much,
        // for Bevy to register an input event:
        threshold: 0.01,
    };

    // make the triggers work in big/coarse steps, to get fewer events
    // reduces noise and precision
    let trigger_settings = AxisSettings {
        threshold: 0.2,
        // also set some conservative deadzones
        positive_high: 0.8,
        positive_low: 0.2,
        negative_high: -0.8,
        negative_low: -0.2,
    };

    // set these settings for the gamepad we use for our player
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::RightStickX),
        right_stick_settings.clone()
    );
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::RightStickY),
        right_stick_settings.clone()
    );
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::LeftZ),
        trigger_settings.clone()
    );
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::RightZ),
        trigger_settings.clone()
    );

    // for buttons (or axes treated as buttons), make them less sensitive
    let button_settings = ButtonSettings {
        // require them to be pressed almost all the way, to count
        press: 0.9,
        // require them to be released almost all the way, to count
        release: 0.1,
    };

    settings.default_button_settings = button_settings;
}