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);
@alice-i-cecile @mockersf I think this is mostly done, i will recheck the docs again once you decide if this should be merged or not.
@cart, just to be sure: you're broadly in favor of representing gamepads as entities, correct?
@cart, just to be sure: you're broadly in favor of representing gamepads as entities, correct?
Provided we have a satisfying answer to my comment right above this, then yeah I'm broadly in favor.
Missing the changelog but i think this is mostly done
I agree with the suggestions from Cart, but feel more strongly about them! Let me know when those are cleaned up and I'll approve and merge this.
I agree with the suggestions from Cart, but feel more strongly about them! Let me know when those are cleaned up and I'll approve and merge this.
Will LWIM have enough time this late into 0.15? If not, we might want to postpone and merge it early into 0.16 so they have plenty of time.
The release candidate period makes me confident that LWIM will be able to port over :)
@alice-i-cecile Gosh you are fast. I didn't even get the chance to update the changelog!
Anyways here are some relevant issues you might want to take a look related to this PR:
- #13195 Solved by sending RawGamepadEvent that will be used by bevy_input.
- #4911 You can send GamepadConnectionEvent to disconnect or connect gamepads.
- #3398 I think this should have been closed long ago?
- #3224 I guess we merged them in a way ¯\(ツ)/¯
- Partially #5163, analogue values now share a Gamepad::get() and Gamepad::get_clamped() method. Also added the difference between a GamepadAxis (range from [-1.0,1.0]) and GamepadButton(range from [0.0,1.0]). I think now we can easily turn GamepadAxis into GamepadButtons if we wanted.
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to https://github.com/bevyengine/bevy-website/issues/1700 if you'd like to help out.
Found a small functionality regression, opened https://github.com/bevyengine/bevy/issues/16221