Bevy Version: | 0.14 | (current) |
---|
Gamepad (Controller, Joystick)
Relevant official examples:
gamepad_input
,
gamepad_input_events
.
Bevy has support for gamepad input hardware, using gilrs: 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. For local
multiplayer, this lets you associate each 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.
fn list_gamepads(
gamepads: Res<Gamepads>,
) {
println!("Currently connected gamepads:");
for gamepad in gamepads.iter() {
println!(
"ID: {:?}; Name: {}",
gamepad, gamepads.name(gamepad).unwrap_or("unknown")
);
}
}
Handling Connections / Disconnections
To detect when gamepads are connected or disconnected, you can use
GamepadEvent
events.
Example showing how to remember the first connected gamepad ID:
use bevy::input::gamepad::{GamepadConnection, GamepadEvent};
/// Simple resource to store the ID of the first connected gamepad.
/// We can use it to know which gamepad to use for player input.
#[derive(Resource)]
struct MyGamepad(Gamepad);
fn gamepad_connections(
mut commands: Commands,
my_gamepad: Option<Res<MyGamepad>>,
mut evr_gamepad: EventReader<GamepadEvent>,
) {
for ev in evr_gamepad.read() {
// we only care about connection events
let GamepadEvent::Connection(ev_conn) = ev else {
continue;
};
match &ev_conn.connection {
GamepadConnection::Connected(info) => {
debug!(
"New gamepad connected: {:?}, name: {}",
ev_conn.gamepad, info.name,
);
// if we don't have any gamepad yet, use this one
if my_gamepad.is_none() {
commands.insert_resource(MyGamepad(ev_conn.gamepad));
}
}
GamepadConnection::Disconnected => {
debug!("Lost connection with gamepad: {:?}", ev_conn.gamepad);
// if it's the one we previously used for the player, remove it:
if let Some(MyGamepad(old_id)) = my_gamepad.as_deref() {
if *old_id == ev_conn.gamepad {
commands.remove_resource::<MyGamepad>();
}
}
}
}
}
}
Handling Gamepad Inputs
The Axis<GamepadAxis>
(Axis
, GamepadAxis
) resource
keeps track of the current value of the different axes: X/Y for each thumb
stick, and the Z axes (the analog triggers).
Buttons can be handled with the ButtonInput<GamepadButton>
(ButtonInput
, GamepadButton
) resource, similar to mouse
buttons or keyboard keys.
fn gamepad_input(
axes: Res<Axis<GamepadAxis>>,
buttons: Res<ButtonInput<GamepadButton>>,
my_gamepad: Option<Res<MyGamepad>>,
) {
let Some(&MyGamepad(gamepad)) = my_gamepad.as_deref() else {
// no gamepad is connected
return;
};
// The joysticks are represented using a separate axis for X and Y
let axis_lx = GamepadAxis {
gamepad, axis_type: GamepadAxisType::LeftStickX
};
let axis_ly = GamepadAxis {
gamepad, axis_type: 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 = Vec2::new(x, y);
// Example: check if the stick is pushed up
if left_stick.length() > 0.9 && left_stick.y > 0.5 {
// do something
}
}
// In a real game, the buttons would be configurable, but here we hardcode them
let jump_button = GamepadButton {
gamepad, button_type: GamepadButtonType::South
};
let heal_button = GamepadButton {
gamepad, button_type: 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
}
}
Notice that the names of buttons in the GamepadButton
enum
are
vendor-neutral (like South
and East
instead of X/O or A/B).
Some game controllers have additional buttons and axes beyond what is available on a standard controller, for example:
- HOTAS (stick for flight sim)
- steering wheel + pedals (for car driving games)
These are represented by the Other(u8)
variant in GamepadButton
/GamepadAxis
.
The u8
value is hardware-specific, so if you want to support such devices,
your game needs to have a way for your users to configure their input bindings.
Events
Alternatively, if you want to detect all activity as it comes in, you
can also handle gamepad inputs using GamepadEvent
events:
fn gamepad_input_events(
mut evr_gamepad: EventReader<GamepadEvent>,
) {
for ev in evr_gamepad.read() {
match ev {
GamepadEvent::Axis(ev_axis) => {
println!(
"Axis {:?} on gamepad {:?} is now at {:?}",
ev_axis.axis_type, ev_axis.gamepad, ev_axis.value
);
}
GamepadEvent::Button(ev_button) => {
// The "value" of a button is typically `0.0` or `1.0`, but it
// is a `f32` because some gamepads may have buttons that are
// pressure-sensitive or otherwise analog somehow.
println!(
"Button {:?} on gamepad {:?} is now at {:?}",
ev_button.button_type, ev_button.gamepad, ev_button.value
);
}
_ => {
// we don't care about other events here (connect/disconnect)
}
}
}
}
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):
use bevy::input::gamepad::{AxisSettings, ButtonSettings, GamepadSettings};
fn configure_gamepads(
my_gamepad: Option<Res<MyGamepad>>,
mut settings: ResMut<GamepadSettings>,
) {
let Some(&MyGamepad(gamepad)) = my_gamepad.as_deref() 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.set_deadzone_lowerbound(-0.1);
settings.default_axis_settings.set_deadzone_upperbound(0.1);
// make the right stick "binary", squash higher values to 1.0 and lower values to 0.0
let mut right_stick_settings = AxisSettings::default();
right_stick_settings.set_deadzone_lowerbound(-0.5);
right_stick_settings.set_deadzone_upperbound(0.5);
right_stick_settings.set_livezone_lowerbound(-0.5);
right_stick_settings.set_livezone_upperbound(0.5);
// the raw value should change by at least this much,
// for Bevy to register an input event:
right_stick_settings.set_threshold(0.01);
// make the triggers work in big/coarse steps, to get fewer events
// reduces noise and precision
let mut trigger_settings = AxisSettings::default();
trigger_settings.set_threshold(0.25);
// set these settings for the gamepad we use for our player
settings.axis_settings.insert(
GamepadAxis { gamepad, axis_type: GamepadAxisType::RightStickX },
right_stick_settings.clone()
);
settings.axis_settings.insert(
GamepadAxis { gamepad, axis_type: GamepadAxisType::RightStickY },
right_stick_settings.clone()
);
settings.axis_settings.insert(
GamepadAxis { gamepad, axis_type: GamepadAxisType::LeftZ },
trigger_settings.clone()
);
settings.axis_settings.insert(
GamepadAxis { gamepad, axis_type: GamepadAxisType::RightZ },
trigger_settings.clone()
);
// for buttons (or axes treated as buttons):
let mut button_settings = ButtonSettings::default();
// require them to be pressed almost all the way, to count
button_settings.set_press_threshold(0.9);
// require them to be released almost all the way, to count
button_settings.set_release_threshold(0.1);
settings.default_button_settings = button_settings;
}
To tie the examples together: if you have the system from the
connect/disconnect example earlier
above on this page, to update our MyGamepad
resource, we can configure
the system from the above example with a run condition, so that
the gamepad settings are updated whenever a new gamepad is connected and
selected to be used:
app.add_systems(Update,
configure_gamepads
.run_if(resource_exists_and_changed::<MyGamepad>)
);
Gamepad Rumble
To cause rumble/vibration, use the GamepadRumbleRequest
event. Every
event you send will add a "rumble" with a given intensity that lasts for
a given duration of time. As you send multiple events, each requested rumble
will be tracked independently, and the actual hardware vibration intensity
will be the sum of all the rumbles currently in progress.
You can also send a Stop
event to immediately cancel any ongoing rumbling.
The intensity of each rumble is represented as two values: the "strong" motor and the "weak" motor. These might produce different-feeling vibrations on different hardware.
use bevy::input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest};
fn gamepad_rumble(
mut evw_rumble: EventWriter<GamepadRumbleRequest>,
my_gamepad: Option<Res<MyGamepad>>,
) {
let Some(&MyGamepad(gamepad)) = my_gamepad.as_deref() else {
// no gamepad is connected
return;
};
// add a short 100ms rumble at max intensity
evw_rumble.send(GamepadRumbleRequest::Add {
gamepad,
duration: Duration::from_millis(100),
intensity: GamepadRumbleIntensity::MAX,
});
// also rumble for a little longer (500 ms)
// with the weak motor at half intensity
// and the strong motor at quarter intensity
evw_rumble.send(GamepadRumbleRequest::Add {
gamepad,
duration: Duration::from_millis(500),
intensity: GamepadRumbleIntensity {
strong_motor: 0.25,
weak_motor: 0.5,
},
});
}