bevy
bevy copied to clipboard
Implement gamepads as entities
Objective
- Significantly improve the ergonomics of gamepads and allow new features
Gamepads are a bit unergonomic to work with, they use resources but unlike other inputs, they are not limited to a single gamepad, to get around this it uses an identifier (Gamepad) to interact with anything causing all sorts of issues.
- There are too many: Gamepads, GamepadSettings, GamepadInfo, ButtonInput<T>, 2 Axis<T>.
- ButtonInput/Axis generic methods become really inconvenient to use e.g. any_pressed()
- GamepadButton/Axis structs are unnecessary boilerplate:
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
- Projects often need to create resources to store the selected gamepad and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new possibilities.
- Reduce boilerplate and allows iteration
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
- ButtonInput/Axis generic methods become ergonomic again
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
- Reduces the number of public components significantly (Gamepad, GamepadSettings, GamepadButtons, GamepadAxes)
- Components are highly convenient. Gamepad optional features could now be expressed naturally (
Option<Rumble> or Option<Gyro>
), allows devs to attach their own components and filter them, so code like this becomes possible:
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
Future work
- [x] Testing
- [x] Gamepad button input event with analog data.
- [x] Consider stop using RawGamepadEvent on bevy_input.
- [x] EntityGamepadMap safe public methods.
- [ ] Run conditions
Follow-up
- [ ] Merge ButtonSettings into GamepadButtons and GamepadAxes
- [ ] Recreate the previous GamepadEvent
- [ ] Tag active gamepads each frame
- [ ] Change GamepadButtons property ButtonInput to 2 fixedbitsets.
- [ ] Rumble component
Changelog
Added
Gamepad (GamepadId + Gamepadinfo), GamepadButtons(Input+Axis), GamepadAxes components GamepadAxisChanged, GamepadButtonChanged event
Changed
GamepadSettings is now a component ~~Gamepad~~ GamepadId ~~GamepadAxisChanged~~ RawGamepadAxisChanged ~~GamepadButtonChanged~~ RawGamepadButtonChanged ~~GamepadEvent~~ RawGamepadEvent ~~GamepadButtonInput~~ GamepadButtonStateChanged
Filtering is now done on bevy_input RawGamepadEvent is no longer used by bevy_input
Removed
GamepadButton and GamepadAxis ~~GamepadEvent~~ The filtered event queue is lost with this PR.
Migration Guide
You can no longer access gamepads as resources, instead you have to query them:
fn gamepad_system(
- gamepads: Res<Gamepads>,
- button_inputs: Res<ButtonInput<GamepadButton>>,
- button_axes: Res<Axis<GamepadButton>>,
- axes: Res<Axis<GamepadAxis>>,
+ gamepads: Query<(&Gamepad, &GamepadButtons, &GamepadAxes)>
)
GamepadButton and GamepadAxis have been removed:
- buttons.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South))
+ buttons.just_pressed(GamepadButtonType::South)
- axes.get(GamepadAxis::new(gamepad, GamepadAxisType::LeftStickX)).unwrap();
+ axis.get(GamepadAxisType::LeftStickX).unwrap()
GamepadButtonInput has been renamed to GamepadButtonStateChanged:
- mut button_input_events: EventReader<GamepadButtonInput>,
+ mut button_input_events: EventReader<GamepadButtonStateChanged>,
Gamepad has been renamed to GamepadId.
- Gamepad::new(1);
+ GampadId(1);