Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement gamepads as entities #12770

Merged
merged 71 commits into from
Sep 27, 2024
Merged

Conversation

s-puig
Copy link
Contributor

@s-puig s-puig commented Mar 29, 2024

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

  1. There are too many structs: Gamepads, GamepadSettings, GamepadInfo, ButtonInput, 2 Axis.
  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.

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(GamepadButton::South))
  1. ButtonInput/Axis generic methods become ergonomic again
gamepad.any_just_pressed([GamepadButton::Start, GamepadButton::Select])
  1. Reduces the number of public components significantly (Gamepad, GamepadSettings)
  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: Query<&Gamepad, With<Player<T>>>,
) {
    if let Ok(gamepad) = gamepads.get_single() {
        if gamepad.pressed(GamepadButton::South) {
            // move player
        }
    }
}

Follow-up

  • Run conditions?
  • Rumble component

Changelog

Gamepads are now entities instead of resources.
Filtering is now done in bevy_input instead of bevy_gilrs.
RawGamepad events are made available to users.

Added

  • RawGamepadEvent
  • RawGamepadAxisChangedEvent
  • RawGamepadButtonChangedEvent
  • Gamepad component
  • GamepadSettings component
  • GamepadInput enum
    • Encapsulation over GamepadButton and GamepadAxis to share the Gamepad.get and Gamepad.get_clamped methods and Axis

Changed

  • GamepadAxisType to GamepadAxis
  • GamepadButtonType to GamepadButton
  • GamepadButtonInput event to GamepadButtonStateChangedEvent

Removed

  • Axis resource
  • Axis resource
  • ButtonInput
  • GamepadSettings resource
  • Gamepads resource
  • Old GamepadAxis no longer exists
  • Old GamepadButton no longer exists

Migration Guide

Gamepad input is no longer accessed using resources, instead they are entities and are accessible using the Gamepad component as long as the gamepad is connected.

Gamepads resource has been deleted, instead of using an internal id to identify gamepads you can use its Entity. Disconnected gamepads will NOT be despawned. Gamepad components that don't need to preserve their state will be removed i.e. Gamepad component is removed, but GamepadSettings is kept.
Reconnected gamepads will try to preserve their Entity id and necessary components will be re-inserted.

GamepadSettings is no longer a resource, instead it is a component attached to the Gamepad entity.

Axis, Axis and ButtonInput methods are accessible via Gamepad component.

fn gamepad_system(
-   gamepads: Res<Gamepads>,
-   button_inputs: Res<ButtonInput<GamepadButton>>,
-   button_axes: Res<Axis<GamepadButton>>,
-   axes: Res<Axis<GamepadAxis>>,
+   gamepads: Query<&Gamepad>
) {
    for gamepad in gamepads.iter() {
-      if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
+      if gamepad.just_pressed(GamepadButton::South) {
            println!("just pressed South");
        } 
         
-      let right_trigger = button_axes
-           .get(GamepadButton::new(
-               gamepad,
-               GamepadButtonType::RightTrigger2,
-           ))
-           .unwrap();
+      let right_trigger = gamepad.get(GamepadButton::RightTrigger2).unwrap();
        if right_trigger.abs() > 0.01 {
            info!("RightTrigger2 value is {}", right_trigger);      
        }

-        let left_stick_x = axes
-           .get(GamepadAxis::new(gamepad, GamepadAxisType::LeftStickX))
-           .unwrap();
+       let left_stick_x = gamepad.get(GamepadAxis::LeftStickX).unwrap();
        if left_stick_x.abs() > 0.01 {
            info!("LeftStickX value is {}", left_stick_x);        
        }
    }
}

@alice-i-cecile alice-i-cecile added A-Input Player input via keyboard, mouse, gamepad, and more C-Usability A targeted quality-of-life change that makes Bevy easier to use X-Controversial There is active debate or serious implications around merging this PR labels Mar 29, 2024
@s-puig s-puig marked this pull request as ready for review March 31, 2024 19:37
@alice-i-cecile alice-i-cecile added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Sep 23, 2024
crates/bevy_gilrs/src/lib.rs Outdated Show resolved Hide resolved
crates/bevy_input/src/gamepad.rs Show resolved Hide resolved
crates/bevy_input/src/gamepad.rs Show resolved Hide resolved
examples/input/gamepad_input.rs Outdated Show resolved Hide resolved
examples/input/gamepad_rumble.rs Outdated Show resolved Hide resolved
examples/tools/gamepad_viewer.rs Outdated Show resolved Hide resolved
@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 27, 2024
@alice-i-cecile
Copy link
Member

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 alice-i-cecile added the M-Needs-Release-Note Work that should be called out in the blog due to impact label Sep 27, 2024
@s-puig
Copy link
Contributor Author

s-puig commented Sep 27, 2024

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.

@alice-i-cecile
Copy link
Member

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

s-puig and others added 5 commits September 27, 2024 21:20
Remove iter() call from gamepads query in examples

Co-authored-by: Carter Anderson <[email protected]>
# Conflicts:
#	crates/bevy_gilrs/src/gilrs_system.rs
#	crates/bevy_input/src/gamepad.rs
@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Sep 27, 2024
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Sep 27, 2024
Merged via the queue into bevyengine:main with commit e788e3b Sep 27, 2024
27 checks passed
rudderbucky pushed a commit to rudderbucky/bevy that referenced this pull request Sep 27, 2024
# 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:

```rust
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);
        }
}
```
4. 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 bevyengine#3419 and bevyengine#12674


## Solution

- Implement gamepads as entities.

Using entities solves all the problems above and opens new
possibilities.

1. Reduce boilerplate and allows iteration

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

## Follow-up

- [ ] Run conditions?
- [ ] Rumble component

# Changelog

## Added

TODO

## Changed

TODO

## Removed

TODO


## Migration Guide

TODO

---------

Co-authored-by: Carter Anderson <[email protected]>
@s-puig
Copy link
Contributor Author

s-puig commented Sep 27, 2024

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

robtfm pushed a commit to robtfm/bevy that referenced this pull request Oct 4, 2024
# 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:

```rust
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);
        }
}
```
4. 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 bevyengine#3419 and bevyengine#12674


## Solution

- Implement gamepads as entities.

Using entities solves all the problems above and opens new
possibilities.

1. Reduce boilerplate and allows iteration

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

## Follow-up

- [ ] Run conditions?
- [ ] Rumble component

# Changelog

## Added

TODO

## Changed

TODO

## Removed

TODO


## Migration Guide

TODO

---------

Co-authored-by: Carter Anderson <[email protected]>
pcwalton added a commit to pcwalton/leafwing-input-manager that referenced this pull request Oct 8, 2024
Most of the work here was fallout from the gamepads-as-entities change
(bevyengine/bevy#12770), as well as the
`PartialReflect` changes.

This relies on bevyengine/bevy#15685. That PR
must be applied to Bevy for this to build.

The most recent upstream commit is
4bf647ff3b0ca7c8ca47496db9cfe03702328473.
pcwalton added a commit to pcwalton/leafwing-input-manager that referenced this pull request Oct 8, 2024
Most of the work here was fallout from the gamepads-as-entities change
(bevyengine/bevy#12770), as well as the
`PartialReflect` changes.

This relies on bevyengine/bevy#15685. That PR
must be applied to Bevy for this to build.

The most recent upstream commit is
4bf647ff3b0ca7c8ca47496db9cfe03702328473.
@alice-i-cecile
Copy link
Member

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 bevyengine/bevy-website#1700 if you'd like to help out.

@Shatur
Copy link
Contributor

Shatur commented Nov 3, 2024

Found a small functionality regression, opened #16221

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Input Player input via keyboard, mouse, gamepad, and more C-Usability A targeted quality-of-life change that makes Bevy easier to use M-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Blessed Has a large architectural impact or tradeoffs, but the design has been endorsed by decision makers
Projects
Status: Responded
Development

Successfully merging this pull request may close these issues.

4 participants