Implement gamepads as entities (#12770)

# 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 #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

```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 <mcanders1@gmail.com>
This commit is contained in:
s-puig 2024-09-27 22:07:20 +02:00 committed by GitHub
parent 39d6a745d2
commit e788e3bc83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1665 additions and 797 deletions

View file

@ -1,42 +1,38 @@
use bevy_input::gamepad::{Gamepad, GamepadAxisType, GamepadButtonType};
use bevy_input::gamepad::{GamepadAxis, GamepadButton};
pub fn convert_gamepad_id(gamepad_id: gilrs::GamepadId) -> Gamepad {
Gamepad::new(gamepad_id.into())
}
pub fn convert_button(button: gilrs::Button) -> Option<GamepadButtonType> {
pub fn convert_button(button: gilrs::Button) -> Option<GamepadButton> {
match button {
gilrs::Button::South => Some(GamepadButtonType::South),
gilrs::Button::East => Some(GamepadButtonType::East),
gilrs::Button::North => Some(GamepadButtonType::North),
gilrs::Button::West => Some(GamepadButtonType::West),
gilrs::Button::C => Some(GamepadButtonType::C),
gilrs::Button::Z => Some(GamepadButtonType::Z),
gilrs::Button::LeftTrigger => Some(GamepadButtonType::LeftTrigger),
gilrs::Button::LeftTrigger2 => Some(GamepadButtonType::LeftTrigger2),
gilrs::Button::RightTrigger => Some(GamepadButtonType::RightTrigger),
gilrs::Button::RightTrigger2 => Some(GamepadButtonType::RightTrigger2),
gilrs::Button::Select => Some(GamepadButtonType::Select),
gilrs::Button::Start => Some(GamepadButtonType::Start),
gilrs::Button::Mode => Some(GamepadButtonType::Mode),
gilrs::Button::LeftThumb => Some(GamepadButtonType::LeftThumb),
gilrs::Button::RightThumb => Some(GamepadButtonType::RightThumb),
gilrs::Button::DPadUp => Some(GamepadButtonType::DPadUp),
gilrs::Button::DPadDown => Some(GamepadButtonType::DPadDown),
gilrs::Button::DPadLeft => Some(GamepadButtonType::DPadLeft),
gilrs::Button::DPadRight => Some(GamepadButtonType::DPadRight),
gilrs::Button::South => Some(GamepadButton::South),
gilrs::Button::East => Some(GamepadButton::East),
gilrs::Button::North => Some(GamepadButton::North),
gilrs::Button::West => Some(GamepadButton::West),
gilrs::Button::C => Some(GamepadButton::C),
gilrs::Button::Z => Some(GamepadButton::Z),
gilrs::Button::LeftTrigger => Some(GamepadButton::LeftTrigger),
gilrs::Button::LeftTrigger2 => Some(GamepadButton::LeftTrigger2),
gilrs::Button::RightTrigger => Some(GamepadButton::RightTrigger),
gilrs::Button::RightTrigger2 => Some(GamepadButton::RightTrigger2),
gilrs::Button::Select => Some(GamepadButton::Select),
gilrs::Button::Start => Some(GamepadButton::Start),
gilrs::Button::Mode => Some(GamepadButton::Mode),
gilrs::Button::LeftThumb => Some(GamepadButton::LeftThumb),
gilrs::Button::RightThumb => Some(GamepadButton::RightThumb),
gilrs::Button::DPadUp => Some(GamepadButton::DPadUp),
gilrs::Button::DPadDown => Some(GamepadButton::DPadDown),
gilrs::Button::DPadLeft => Some(GamepadButton::DPadLeft),
gilrs::Button::DPadRight => Some(GamepadButton::DPadRight),
gilrs::Button::Unknown => None,
}
}
pub fn convert_axis(axis: gilrs::Axis) -> Option<GamepadAxisType> {
pub fn convert_axis(axis: gilrs::Axis) -> Option<GamepadAxis> {
match axis {
gilrs::Axis::LeftStickX => Some(GamepadAxisType::LeftStickX),
gilrs::Axis::LeftStickY => Some(GamepadAxisType::LeftStickY),
gilrs::Axis::LeftZ => Some(GamepadAxisType::LeftZ),
gilrs::Axis::RightStickX => Some(GamepadAxisType::RightStickX),
gilrs::Axis::RightStickY => Some(GamepadAxisType::RightStickY),
gilrs::Axis::RightZ => Some(GamepadAxisType::RightZ),
gilrs::Axis::LeftStickX => Some(GamepadAxis::LeftStickX),
gilrs::Axis::LeftStickY => Some(GamepadAxis::LeftStickY),
gilrs::Axis::LeftZ => Some(GamepadAxis::LeftZ),
gilrs::Axis::RightStickX => Some(GamepadAxis::RightStickX),
gilrs::Axis::RightStickY => Some(GamepadAxis::RightStickY),
gilrs::Axis::RightZ => Some(GamepadAxis::RightZ),
// The `axis_dpad_to_button` gilrs filter should filter out all DPadX and DPadY events. If
// it doesn't then we probably need an entry added to the following repo and an update to
// GilRs to use the updated database: https://github.com/gabomdq/SDL_GameControllerDB

View file

@ -1,103 +1,113 @@
use crate::{
converter::{convert_axis, convert_button, convert_gamepad_id},
Gilrs,
converter::{convert_axis, convert_button},
Gilrs, GilrsGamepads,
};
use bevy_ecs::event::EventWriter;
use bevy_ecs::prelude::Commands;
#[cfg(target_arch = "wasm32")]
use bevy_ecs::system::NonSendMut;
use bevy_ecs::{
event::EventWriter,
system::{Res, ResMut},
};
use bevy_input::{
gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection,
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings,
},
prelude::{GamepadAxis, GamepadButton},
Axis,
use bevy_ecs::system::ResMut;
use bevy_input::gamepad::{
GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};
pub fn gilrs_event_startup_system(
mut commands: Commands,
#[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
mut events: EventWriter<GamepadEvent>,
mut gamepads: ResMut<GilrsGamepads>,
mut events: EventWriter<GamepadConnectionEvent>,
) {
for (id, gamepad) in gilrs.0.get().gamepads() {
// Create entity and add to mapping
let entity = commands.spawn_empty().id();
gamepads.id_to_entity.insert(id, entity);
gamepads.entity_to_id.insert(entity, id);
let info = GamepadInfo {
name: gamepad.name().into(),
};
events.send(
GamepadConnectionEvent {
gamepad: convert_gamepad_id(id),
connection: GamepadConnection::Connected(info),
}
.into(),
);
events.send(GamepadConnectionEvent {
gamepad: entity,
connection: GamepadConnection::Connected(info),
});
}
}
pub fn gilrs_event_system(
mut commands: Commands,
#[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
mut events: EventWriter<GamepadEvent>,
mut gamepad_buttons: ResMut<Axis<GamepadButton>>,
gamepad_axis: Res<Axis<GamepadAxis>>,
gamepad_settings: Res<GamepadSettings>,
mut gamepads: ResMut<GilrsGamepads>,
mut events: EventWriter<RawGamepadEvent>,
mut connection_events: EventWriter<GamepadConnectionEvent>,
mut button_events: EventWriter<RawGamepadButtonChangedEvent>,
mut axis_event: EventWriter<RawGamepadAxisChangedEvent>,
) {
let gilrs = gilrs.0.get();
while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) {
gilrs.update(&gilrs_event);
let gamepad = convert_gamepad_id(gilrs_event.id);
match gilrs_event.event {
EventType::Connected => {
let pad = gilrs.gamepad(gilrs_event.id);
let entity = gamepads.get_entity(gilrs_event.id).unwrap_or_else(|| {
let entity = commands.spawn_empty().id();
gamepads.id_to_entity.insert(gilrs_event.id, entity);
gamepads.entity_to_id.insert(entity, gilrs_event.id);
entity
});
let info = GamepadInfo {
name: pad.name().into(),
};
events.send(
GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)).into(),
GamepadConnectionEvent::new(entity, GamepadConnection::Connected(info.clone()))
.into(),
);
connection_events.send(GamepadConnectionEvent::new(
entity,
GamepadConnection::Connected(info),
));
}
EventType::Disconnected => {
events.send(
GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into(),
);
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
let event = GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected);
events.send(event.clone().into());
connection_events.send(event);
}
EventType::ButtonChanged(gilrs_button, raw_value, _) => {
if let Some(button_type) = convert_button(gilrs_button) {
let button = GamepadButton::new(gamepad, button_type);
let old_value = gamepad_buttons.get(button);
let button_settings = gamepad_settings.get_button_axis_settings(button);
// Only send events that pass the user-defined change threshold
if let Some(filtered_value) = button_settings.filter(raw_value, old_value) {
events.send(
GamepadButtonChangedEvent::new(gamepad, button_type, filtered_value)
.into(),
);
// Update the current value prematurely so that `old_value` is correct in
// future iterations of the loop.
gamepad_buttons.set(button, filtered_value);
}
}
let Some(button) = convert_button(gilrs_button) else {
continue;
};
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
events.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into());
button_events.send(RawGamepadButtonChangedEvent::new(
gamepad, button, raw_value,
));
}
EventType::AxisChanged(gilrs_axis, raw_value, _) => {
if let Some(axis_type) = convert_axis(gilrs_axis) {
let axis = GamepadAxis::new(gamepad, axis_type);
let old_value = gamepad_axis.get(axis);
let axis_settings = gamepad_settings.get_axis_settings(axis);
// Only send events that pass the user-defined change threshold
if let Some(filtered_value) = axis_settings.filter(raw_value, old_value) {
events.send(
GamepadAxisChangedEvent::new(gamepad, axis_type, filtered_value).into(),
);
}
}
let Some(axis) = convert_axis(gilrs_axis) else {
continue;
};
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
events.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into());
axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value));
}
_ => (),
};

View file

@ -15,9 +15,10 @@ mod gilrs_system;
mod rumble;
use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_input::InputSystem;
use bevy_utils::{synccell::SyncCell, tracing::error};
use bevy_utils::{synccell::SyncCell, tracing::error, HashMap};
use gilrs::GilrsBuilder;
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
use rumble::{play_gilrs_rumble, RunningRumbleEffects};
@ -25,6 +26,27 @@ use rumble::{play_gilrs_rumble, RunningRumbleEffects};
#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))]
pub(crate) struct Gilrs(pub SyncCell<gilrs::Gilrs>);
/// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`].
#[derive(Debug, Default, Resource)]
pub(crate) struct GilrsGamepads {
/// Mapping of [`Entity`] to [`gilrs::GamepadId`].
pub(crate) entity_to_id: EntityHashMap<gilrs::GamepadId>,
/// Mapping of [`gilrs::GamepadId`] to [`Entity`].
pub(crate) id_to_entity: HashMap<gilrs::GamepadId, Entity>,
}
impl GilrsGamepads {
/// Returns the [`Entity`] assigned to a connected [`gilrs::GamepadId`].
pub fn get_entity(&self, gamepad_id: gilrs::GamepadId) -> Option<Entity> {
self.id_to_entity.get(&gamepad_id).copied()
}
/// Returns the [`gilrs::GamepadId`] assigned to a gamepad [`Entity`].
pub fn get_gamepad_id(&self, entity: Entity) -> Option<gilrs::GamepadId> {
self.entity_to_id.get(&entity).copied()
}
}
/// Plugin that provides gamepad handling to an [`App`].
#[derive(Default)]
pub struct GilrsPlugin;
@ -45,7 +67,7 @@ impl Plugin for GilrsPlugin {
app.insert_non_send_resource(Gilrs(SyncCell::new(gilrs)));
#[cfg(not(target_arch = "wasm32"))]
app.insert_resource(Gilrs(SyncCell::new(gilrs)));
app.init_resource::<GilrsGamepads>();
app.init_resource::<RunningRumbleEffects>()
.add_systems(PreStartup, gilrs_event_startup_system)
.add_systems(PreUpdate, gilrs_event_system.before(InputSystem))

View file

@ -1,5 +1,5 @@
//! Handle user specified rumble request events.
use crate::Gilrs;
use crate::{Gilrs, GilrsGamepads};
use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource};
#[cfg(target_arch = "wasm32")]
use bevy_ecs::system::NonSendMut;
@ -16,8 +16,6 @@ use gilrs::{
};
use thiserror::Error;
use crate::converter::convert_gamepad_id;
/// A rumble effect that is currently in effect.
struct RunningRumble {
/// Duration from app startup when this effect will be finished
@ -84,6 +82,7 @@ fn get_base_effects(
fn handle_rumble_request(
running_rumbles: &mut RunningRumbleEffects,
gilrs: &mut gilrs::Gilrs,
gamepads: &GilrsGamepads,
rumble: GamepadRumbleRequest,
current_time: Duration,
) -> Result<(), RumbleError> {
@ -91,7 +90,7 @@ fn handle_rumble_request(
let (gamepad_id, _) = gilrs
.gamepads()
.find(|(pad_id, _)| convert_gamepad_id(*pad_id) == gamepad)
.find(|(pad_id, _)| *pad_id == gamepads.get_gamepad_id(gamepad).unwrap())
.ok_or(RumbleError::GamepadNotFound)?;
match rumble {
@ -129,6 +128,7 @@ pub(crate) fn play_gilrs_rumble(
time: Res<Time<Real>>,
#[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
gamepads: Res<GilrsGamepads>,
mut requests: EventReader<GamepadRumbleRequest>,
mut running_rumbles: ResMut<RunningRumbleEffects>,
) {
@ -146,7 +146,7 @@ pub(crate) fn play_gilrs_rumble(
// Add new effects.
for rumble in requests.read().cloned() {
let gamepad = rumble.gamepad();
match handle_rumble_request(&mut running_rumbles, gilrs, rumble, current_time) {
match handle_rumble_request(&mut running_rumbles, gilrs, &gamepads, rumble, current_time) {
Ok(()) => {}
Err(RumbleError::GilrsError(err)) => {
if let ff::Error::FfNotSupported(_) = err {

View file

@ -57,7 +57,7 @@ where
/// Returns the unclamped position data of the provided `input_device`.
///
/// This value may be outside of the [`Axis::MIN`] and [`Axis::MAX`] range.
/// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range.
///
/// Use for things like camera zoom, where you want devices like mouse wheels to be able to
/// exceed the normal range. If being able to move faster on one input device
@ -70,18 +70,11 @@ where
pub fn remove(&mut self, input_device: T) -> Option<f32> {
self.axis_data.remove(&input_device)
}
/// Returns an iterator of all the input devices that have position data
pub fn devices(&self) -> impl ExactSizeIterator<Item = &T> {
self.axis_data.keys()
}
}
#[cfg(test)]
mod tests {
use crate::{
gamepad::{Gamepad, GamepadButton, GamepadButtonType},
Axis,
};
use crate::{gamepad::GamepadButton, Axis};
#[test]
fn test_axis_set() {
@ -100,13 +93,11 @@ mod tests {
];
for (value, expected) in cases {
let gamepad_button =
GamepadButton::new(Gamepad::new(1), GamepadButtonType::RightTrigger);
let mut axis = Axis::<GamepadButton>::default();
axis.set(gamepad_button, value);
axis.set(GamepadButton::RightTrigger, value);
let actual = axis.get(gamepad_button);
let actual = axis.get(GamepadButton::RightTrigger);
assert_eq!(expected, actual);
}
}
@ -116,48 +107,16 @@ mod tests {
let cases = [-1.0, -0.9, -0.1, 0.0, 0.1, 0.9, 1.0];
for value in cases {
let gamepad_button =
GamepadButton::new(Gamepad::new(1), GamepadButtonType::RightTrigger);
let mut axis = Axis::<GamepadButton>::default();
axis.set(gamepad_button, value);
assert!(axis.get(gamepad_button).is_some());
axis.set(GamepadButton::RightTrigger, value);
assert!(axis.get(GamepadButton::RightTrigger).is_some());
axis.remove(gamepad_button);
let actual = axis.get(gamepad_button);
axis.remove(GamepadButton::RightTrigger);
let actual = axis.get(GamepadButton::RightTrigger);
let expected = None;
assert_eq!(expected, actual);
}
}
#[test]
fn test_axis_devices() {
let mut axis = Axis::<GamepadButton>::default();
assert_eq!(axis.devices().count(), 0);
axis.set(
GamepadButton::new(Gamepad::new(1), GamepadButtonType::RightTrigger),
0.1,
);
assert_eq!(axis.devices().count(), 1);
axis.set(
GamepadButton::new(Gamepad::new(1), GamepadButtonType::LeftTrigger),
0.5,
);
assert_eq!(axis.devices().count(), 2);
axis.set(
GamepadButton::new(Gamepad::new(1), GamepadButtonType::RightTrigger),
-0.1,
);
assert_eq!(axis.devices().count(), 2);
axis.remove(GamepadButton::new(
Gamepad::new(1),
GamepadButtonType::RightTrigger,
));
assert_eq!(axis.devices().count(), 1);
}
}

View file

@ -73,17 +73,13 @@ use {
/// ```no_run
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, Update};
/// # use bevy_ecs::{prelude::{IntoSystemConfigs, Res, Resource, resource_changed}, schedule::Condition};
/// # use bevy_input::{ButtonInput, prelude::{GamepadButton, KeyCode, MouseButton}};
/// # use bevy_input::{ButtonInput, prelude::{KeyCode, MouseButton}};
///
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_systems(
/// Update,
/// print_gamepad.run_if(resource_changed::<ButtonInput<GamepadButton>>),
/// )
/// .add_systems(
/// Update,
/// print_mouse.run_if(resource_changed::<ButtonInput<MouseButton>>),
/// )
/// .add_systems(
@ -93,10 +89,6 @@ use {
/// .run();
/// }
///
/// fn print_gamepad(gamepad: Res<ButtonInput<GamepadButton>>) {
/// println!("Gamepad: {:?}", gamepad.get_pressed().collect::<Vec<_>>());
/// }
///
/// fn print_mouse(mouse: Res<ButtonInput<MouseButton>>) {
/// println!("Mouse: {:?}", mouse.get_pressed().collect::<Vec<_>>());
/// }
@ -115,33 +107,6 @@ use {
/// }
/// ```
///
/// Accepting input from multiple devices:
/// ```no_run
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, Update};
/// # use bevy_ecs::{prelude::IntoSystemConfigs, schedule::Condition};
/// # use bevy_input::{ButtonInput, common_conditions::{input_just_pressed}, prelude::{GamepadButton, Gamepad, GamepadButtonType, KeyCode}};
///
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_systems(
/// Update,
/// something_used.run_if(
/// input_just_pressed(KeyCode::KeyE)
/// .or(input_just_pressed(GamepadButton::new(
/// Gamepad::new(0),
/// GamepadButtonType::West,
/// ))),
/// ),
/// )
/// .run();
/// }
///
/// fn something_used() {
/// println!("Generic use-ish button pressed.");
/// }
/// ```
///
/// ## Note
///
/// When adding this resource for a new input type, you should:

File diff suppressed because it is too large Load diff

View file

@ -30,9 +30,7 @@ pub use button_input::*;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
gamepad::{
Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, Gamepads,
},
gamepad::{Gamepad, GamepadAxis, GamepadButton, GamepadSettings},
keyboard::KeyCode,
mouse::MouseButton,
touch::{TouchInput, Touches},
@ -54,10 +52,10 @@ use mouse::{
use touch::{touch_screen_input_system, TouchInput, Touches};
use gamepad::{
gamepad_axis_event_system, gamepad_button_event_system, gamepad_connection_system,
gamepad_event_system, GamepadAxis, GamepadAxisChangedEvent, GamepadButton,
GamepadButtonChangedEvent, GamepadButtonInput, GamepadConnectionEvent, GamepadEvent,
GamepadRumbleRequest, GamepadSettings, Gamepads,
gamepad_connection_system, gamepad_event_processing_system, GamepadAxisChangedEvent,
GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnection,
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadRumbleRequest, GamepadSettings,
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
@ -98,30 +96,22 @@ impl Plugin for InputPlugin {
.add_event::<DoubleTapGesture>()
.add_event::<PanGesture>()
// gamepad
.add_event::<GamepadEvent>()
.add_event::<GamepadConnectionEvent>()
.add_event::<GamepadButtonChangedEvent>()
.add_event::<GamepadButtonInput>()
.add_event::<GamepadButtonStateChangedEvent>()
.add_event::<GamepadAxisChangedEvent>()
.add_event::<GamepadEvent>()
.add_event::<RawGamepadEvent>()
.add_event::<RawGamepadAxisChangedEvent>()
.add_event::<RawGamepadButtonChangedEvent>()
.add_event::<GamepadRumbleRequest>()
.init_resource::<GamepadSettings>()
.init_resource::<Gamepads>()
.init_resource::<ButtonInput<GamepadButton>>()
.init_resource::<Axis<GamepadAxis>>()
.init_resource::<Axis<GamepadButton>>()
.init_resource::<AccumulatedMouseMotion>()
.init_resource::<AccumulatedMouseScroll>()
.add_systems(
PreUpdate,
(
gamepad_event_system,
gamepad_connection_system.after(gamepad_event_system),
gamepad_button_event_system
.after(gamepad_event_system)
.after(gamepad_connection_system),
gamepad_axis_event_system
.after(gamepad_event_system)
.after(gamepad_connection_system),
gamepad_connection_system,
gamepad_event_processing_system.after(gamepad_connection_system),
)
.in_set(InputSystem),
)
@ -141,8 +131,15 @@ impl Plugin for InputPlugin {
.register_type::<DoubleTapGesture>()
.register_type::<PanGesture>()
.register_type::<TouchInput>()
.register_type::<GamepadEvent>()
.register_type::<GamepadButtonInput>()
.register_type::<RawGamepadEvent>()
.register_type::<RawGamepadAxisChangedEvent>()
.register_type::<RawGamepadButtonChangedEvent>()
.register_type::<GamepadConnectionEvent>()
.register_type::<GamepadButtonChangedEvent>()
.register_type::<GamepadAxisChangedEvent>()
.register_type::<GamepadButtonStateChangedEvent>()
.register_type::<GamepadInfo>()
.register_type::<GamepadConnection>()
.register_type::<GamepadSettings>()
.register_type::<AccumulatedMouseMotion>()
.register_type::<AccumulatedMouseScroll>();

View file

@ -9,35 +9,22 @@ fn main() {
.run();
}
fn gamepad_system(
gamepads: Res<Gamepads>,
button_inputs: Res<ButtonInput<GamepadButton>>,
button_axes: Res<Axis<GamepadButton>>,
axes: Res<Axis<GamepadAxis>>,
) {
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);
fn gamepad_system(gamepads: Query<(Entity, &Gamepad)>) {
for (entity, gamepad) in &gamepads {
if gamepad.just_pressed(GamepadButton::South) {
info!("{:?} just pressed South", entity);
} else if gamepad.just_released(GamepadButton::South) {
info!("{:?} just released South", entity);
}
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 {}", gamepad, right_trigger);
info!("{:?} RightTrigger2 value is {}", entity, 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 {}", gamepad, left_stick_x);
info!("{:?} LeftStickX value is {}", entity, left_stick_x);
}
}
}

View file

@ -2,7 +2,7 @@
use bevy::{
input::gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadButtonInput,
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadButtonStateChangedEvent,
GamepadConnectionEvent, GamepadEvent,
},
prelude::*,
@ -17,15 +17,14 @@ fn main() {
fn gamepad_events(
mut connection_events: EventReader<GamepadConnectionEvent>,
// Handles the continuous measure of an axis, equivalent to GamepadAxes::get.
mut axis_changed_events: EventReader<GamepadAxisChangedEvent>,
// Handles the continuous measure of how far a button has been pressed down, as measured
// by `Axis<GamepadButton>`. Whenever that value changes, this event is emitted.
// Handles the continuous measure of how far a button has been pressed down, equivalent to `GamepadButtons::get`.
mut button_changed_events: EventReader<GamepadButtonChangedEvent>,
// Handles the boolean measure of whether a button is considered pressed or unpressed, as
// defined by the thresholds in `GamepadSettings::button_settings` and measured by
// `Input<GamepadButton>`. When the threshold is crossed and the button state changes,
// this event is emitted.
mut button_input_events: EventReader<GamepadButtonInput>,
// defined by the thresholds in `GamepadSettings::button_settings`.
// When the threshold is crossed and the button state changes, this event is emitted.
mut button_input_events: EventReader<GamepadButtonStateChangedEvent>,
) {
for connection_event in connection_events.read() {
info!("{:?}", connection_event);
@ -33,15 +32,13 @@ fn gamepad_events(
for axis_changed_event in axis_changed_events.read() {
info!(
"{:?} of {:?} is changed to {}",
axis_changed_event.axis_type, axis_changed_event.gamepad, axis_changed_event.value
axis_changed_event.axis, axis_changed_event.entity, axis_changed_event.value
);
}
for button_changed_event in button_changed_events.read() {
info!(
"{:?} of {:?} is changed to {}",
button_changed_event.button_type,
button_changed_event.gamepad,
button_changed_event.value
button_changed_event.button, button_changed_event.entity, button_changed_event.value
);
}
for button_input_event in button_input_events.read() {

View file

@ -2,7 +2,7 @@
//! pressed.
use bevy::{
input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest},
input::gamepad::{Gamepad, GamepadRumbleIntensity, GamepadRumbleRequest},
prelude::*,
utils::Duration,
};
@ -15,51 +15,43 @@ fn main() {
}
fn gamepad_system(
gamepads: Res<Gamepads>,
button_inputs: Res<ButtonInput<GamepadButton>>,
gamepads: Query<(Entity, &Gamepad)>,
mut rumble_requests: EventWriter<GamepadRumbleRequest>,
) {
for gamepad in gamepads.iter() {
let button_pressed = |button| {
button_inputs.just_pressed(GamepadButton {
gamepad,
button_type: button,
})
};
if button_pressed(GamepadButtonType::North) {
for (entity, gamepad) in &gamepads {
if gamepad.just_pressed(GamepadButton::North) {
info!(
"North face button: strong (low-frequency) with low intensity for rumble for 5 seconds. Press multiple times to increase intensity."
);
rumble_requests.send(GamepadRumbleRequest::Add {
gamepad,
gamepad: entity,
intensity: GamepadRumbleIntensity::strong_motor(0.1),
duration: Duration::from_secs(5),
});
}
if button_pressed(GamepadButtonType::East) {
if gamepad.just_pressed(GamepadButton::East) {
info!("East face button: maximum rumble on both motors for 5 seconds");
rumble_requests.send(GamepadRumbleRequest::Add {
gamepad,
gamepad: entity,
duration: Duration::from_secs(5),
intensity: GamepadRumbleIntensity::MAX,
});
}
if button_pressed(GamepadButtonType::South) {
if gamepad.just_pressed(GamepadButton::South) {
info!("South face button: low-intensity rumble on the weak motor for 0.5 seconds");
rumble_requests.send(GamepadRumbleRequest::Add {
gamepad,
gamepad: entity,
duration: Duration::from_secs_f32(0.5),
intensity: GamepadRumbleIntensity::weak_motor(0.25),
});
}
if button_pressed(GamepadButtonType::West) {
if gamepad.just_pressed(GamepadButton::West) {
info!("West face button: custom rumble intensity for 5 second");
rumble_requests.send(GamepadRumbleRequest::Add {
gamepad,
gamepad: entity,
intensity: GamepadRumbleIntensity {
// intensity low-frequency motor, usually on the left-hand side
strong_motor: 0.5,
@ -70,9 +62,9 @@ fn gamepad_system(
});
}
if button_pressed(GamepadButtonType::Start) {
if gamepad.just_pressed(GamepadButton::Start) {
info!("Start button: Interrupt the current rumble");
rumble_requests.send(GamepadRumbleRequest::Stop { gamepad });
rumble_requests.send(GamepadRumbleRequest::Stop { gamepad: entity });
}
}
}

View file

@ -3,7 +3,7 @@
use std::f32::consts::PI;
use bevy::{
input::gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadSettings},
input::gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnectionEvent},
prelude::*,
sprite::{Anchor, MaterialMesh2dBundle, Mesh2dHandle},
};
@ -25,20 +25,20 @@ const LIVE_COLOR: Color = Color::srgb(0.4, 0.4, 0.4);
const DEAD_COLOR: Color = Color::srgb(0.13, 0.13, 0.13);
#[derive(Component, Deref)]
struct ReactTo(GamepadButtonType);
struct ReactTo(GamepadButton);
#[derive(Component)]
struct MoveWithAxes {
x_axis: GamepadAxisType,
y_axis: GamepadAxisType,
x_axis: GamepadAxis,
y_axis: GamepadAxis,
scale: f32,
}
#[derive(Component)]
struct TextWithAxes {
x_axis: GamepadAxisType,
y_axis: GamepadAxisType,
x_axis: GamepadAxis,
y_axis: GamepadAxis,
}
#[derive(Component, Deref)]
struct TextWithButtonValue(GamepadButtonType);
struct TextWithButtonValue(GamepadButton);
#[derive(Component)]
struct ConnectedGamepadsText;
@ -84,7 +84,7 @@ struct GamepadButtonBundle {
impl GamepadButtonBundle {
pub fn new(
button_type: GamepadButtonType,
button_type: GamepadButton,
mesh: Mesh2dHandle,
material: Handle<ColorMaterial>,
x: f32,
@ -140,28 +140,28 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
})
.with_children(|parent| {
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::North,
GamepadButton::North,
meshes.circle.clone(),
materials.normal.clone(),
0.,
BUTTON_CLUSTER_RADIUS,
));
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::South,
GamepadButton::South,
meshes.circle.clone(),
materials.normal.clone(),
0.,
-BUTTON_CLUSTER_RADIUS,
));
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::West,
GamepadButton::West,
meshes.circle.clone(),
materials.normal.clone(),
-BUTTON_CLUSTER_RADIUS,
0.,
));
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::East,
GamepadButton::East,
meshes.circle.clone(),
materials.normal.clone(),
BUTTON_CLUSTER_RADIUS,
@ -172,7 +172,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
// Start and Pause
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::Select,
GamepadButton::Select,
meshes.start_pause.clone(),
materials.normal.clone(),
-30.,
@ -180,7 +180,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
));
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::Start,
GamepadButton::Start,
meshes.start_pause.clone(),
materials.normal.clone(),
30.,
@ -196,7 +196,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
})
.with_children(|parent| {
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::DPadUp,
GamepadButton::DPadUp,
meshes.triangle.clone(),
materials.normal.clone(),
0.,
@ -204,7 +204,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
));
parent.spawn(
GamepadButtonBundle::new(
GamepadButtonType::DPadDown,
GamepadButton::DPadDown,
meshes.triangle.clone(),
materials.normal.clone(),
0.,
@ -214,7 +214,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
);
parent.spawn(
GamepadButtonBundle::new(
GamepadButtonType::DPadLeft,
GamepadButton::DPadLeft,
meshes.triangle.clone(),
materials.normal.clone(),
-BUTTON_CLUSTER_RADIUS,
@ -224,7 +224,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
);
parent.spawn(
GamepadButtonBundle::new(
GamepadButtonType::DPadRight,
GamepadButton::DPadRight,
meshes.triangle.clone(),
materials.normal.clone(),
BUTTON_CLUSTER_RADIUS,
@ -237,7 +237,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
// Triggers
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::LeftTrigger,
GamepadButton::LeftTrigger,
meshes.trigger.clone(),
materials.normal.clone(),
-BUTTONS_X,
@ -245,7 +245,7 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
));
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::RightTrigger,
GamepadButton::RightTrigger,
meshes.trigger.clone(),
materials.normal.clone(),
BUTTONS_X,
@ -257,8 +257,10 @@ fn setup_sticks(
mut commands: Commands,
meshes: Res<ButtonMeshes>,
materials: Res<ButtonMaterials>,
gamepad_settings: Res<GamepadSettings>,
) {
// NOTE: This stops making sense because in entities because there isn't a "global" default,
// instead each gamepad has its own default setting
let gamepad_settings = GamepadSettings::default();
let dead_upper =
STICK_BOUNDS_SIZE * gamepad_settings.default_axis_settings.deadzone_upperbound();
let dead_lower =
@ -358,16 +360,16 @@ fn setup_sticks(
spawn_stick(
-STICKS_X,
STICKS_Y,
GamepadAxisType::LeftStickX,
GamepadAxisType::LeftStickY,
GamepadButtonType::LeftThumb,
GamepadAxis::LeftStickX,
GamepadAxis::LeftStickY,
GamepadButton::LeftThumb,
);
spawn_stick(
STICKS_X,
STICKS_Y,
GamepadAxisType::RightStickX,
GamepadAxisType::RightStickY,
GamepadButtonType::RightThumb,
GamepadAxis::RightStickX,
GamepadAxis::RightStickY,
GamepadButton::RightThumb,
);
}
@ -403,16 +405,8 @@ fn setup_triggers(
});
};
spawn_trigger(
-BUTTONS_X,
BUTTONS_Y + 145.,
GamepadButtonType::LeftTrigger2,
);
spawn_trigger(
BUTTONS_X,
BUTTONS_Y + 145.,
GamepadButtonType::RightTrigger2,
);
spawn_trigger(-BUTTONS_X, BUTTONS_Y + 145., GamepadButton::LeftTrigger2);
spawn_trigger(BUTTONS_X, BUTTONS_Y + 145., GamepadButton::RightTrigger2);
}
fn setup_connected(mut commands: Commands) {
@ -443,30 +437,28 @@ fn setup_connected(mut commands: Commands) {
}
fn update_buttons(
gamepads: Res<Gamepads>,
button_inputs: Res<ButtonInput<GamepadButton>>,
gamepads: Query<&Gamepad>,
materials: Res<ButtonMaterials>,
mut query: Query<(&mut Handle<ColorMaterial>, &ReactTo)>,
) {
for gamepad in gamepads.iter() {
for buttons in &gamepads {
for (mut handle, react_to) in query.iter_mut() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, **react_to)) {
if buttons.just_pressed(**react_to) {
*handle = materials.active.clone();
}
if button_inputs.just_released(GamepadButton::new(gamepad, **react_to)) {
if buttons.just_released(**react_to) {
*handle = materials.normal.clone();
}
}
}
}
fn update_button_values(
mut events: EventReader<GamepadButtonChangedEvent>,
mut query: Query<(&mut Text, &TextWithButtonValue)>,
) {
for button_event in events.read() {
for (mut text, text_with_button_value) in query.iter_mut() {
if button_event.button_type == **text_with_button_value {
if button_event.button == **text_with_button_value {
text.sections[0].value = format!("{:.3}", button_event.value);
}
}
@ -479,7 +471,7 @@ fn update_axes(
mut text_query: Query<(&mut Text, &TextWithAxes)>,
) {
for axis_event in axis_events.read() {
let axis_type = axis_event.axis_type;
let axis_type = axis_event.axis;
let value = axis_event.value;
for (mut transform, move_with) in query.iter_mut() {
if axis_type == move_with.x_axis {
@ -501,18 +493,19 @@ fn update_axes(
}
fn update_connected(
gamepads: Res<Gamepads>,
mut connected: EventReader<GamepadConnectionEvent>,
gamepads: Query<(Entity, &Gamepad)>,
mut query: Query<&mut Text, With<ConnectedGamepadsText>>,
) {
if !gamepads.is_changed() {
if connected.is_empty() {
return;
}
connected.clear();
let mut text = query.single_mut();
let formatted = gamepads
.iter()
.map(|g| format!("- {}", gamepads.name(g).unwrap()))
.map(|(entity, gamepad)| format!("{} - {}", entity, gamepad.name()))
.collect::<Vec<_>>()
.join("\n");