bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Implement gamepads as entities

Open s-puig opened this issue 1 year 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

@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.

s-puig avatar Apr 01 '24 22:04 s-puig

@cart, just to be sure: you're broadly in favor of representing gamepads as entities, correct?

alice-i-cecile avatar Sep 17 '24 00:09 alice-i-cecile

@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.

cart avatar Sep 17 '24 00:09 cart

Missing the changelog but i think this is mostly done

s-puig avatar Sep 23 '24 22:09 s-puig

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.

alice-i-cecile avatar Sep 27 '24 00:09 alice-i-cecile

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.

s-puig avatar Sep 27 '24 10:09 s-puig

The release candidate period makes me confident that LWIM will be able to port over :)

alice-i-cecile avatar Sep 27 '24 14:09 alice-i-cecile

@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.

s-puig avatar Sep 27 '24 21:09 s-puig

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.

alice-i-cecile avatar Oct 20 '24 14:10 alice-i-cecile

Found a small functionality regression, opened https://github.com/bevyengine/bevy/issues/16221

Shatur avatar Nov 03 '24 20:11 Shatur