bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Implement gamepads as entities

Open s-puig opened this issue 10 months ago • 1 comments

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.

  1. There are too many: Gamepads, GamepadSettings, GamepadInfo, ButtonInput<T>, 2 Axis<T>.
  2. ButtonInput/Axis generic methods become really inconvenient to use e.g. any_pressed()
  3. 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);
        }
}
  1. 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.

  1. Reduce boilerplate and allows iteration
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
  1. ButtonInput/Axis generic methods become ergonomic again
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
  1. Reduces the number of public components significantly (Gamepad, GamepadSettings, GamepadButtons, GamepadAxes)
  2. 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);

s-puig avatar Mar 29 '24 00:03 s-puig