From b0058dc54b9d4966868376d3e67305929e34bbbb Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 4 Nov 2024 20:05:24 +0300 Subject: [PATCH] Gamepad improvements (#16222) # Objective Closes #16221. ## Solution - Make `Gamepad` fields public and remove delegates / getters. - Move `impl Into` to `Axis` methods (delegates for `Axis` used `impl Into` to allow passing both `GamepadAxis` and `GamepadButton`). - Improve docs. ## Testing - I run tests. Not sure if the migration guide is needed, since it's a feature from RC, but I wrote it just in case. --- ## Migration Guide - `Gamepad` fields are now public. - Instead of using `Gamepad` delegates like `Gamepad::just_pressed`, call these methods directly on the fields. --------- Co-authored-by: Alice Cecile --- crates/bevy_input/src/axis.rs | 16 +- crates/bevy_input/src/gamepad.rs | 264 ++++++------------------------- examples/input/gamepad_input.rs | 8 +- examples/input/gamepad_rumble.rs | 10 +- examples/tools/gamepad_viewer.rs | 8 +- 5 files changed, 69 insertions(+), 237 deletions(-) diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs index f2e97777cb..ffaadf0120 100644 --- a/crates/bevy_input/src/axis.rs +++ b/crates/bevy_input/src/axis.rs @@ -45,16 +45,16 @@ where /// If the `input_device`: /// - was present before, the position data is updated, and the old value is returned. /// - wasn't present before, `None` is returned. - pub fn set(&mut self, input_device: T, position_data: f32) -> Option { - self.axis_data.insert(input_device, position_data) + pub fn set(&mut self, input_device: impl Into, position_data: f32) -> Option { + self.axis_data.insert(input_device.into(), position_data) } /// Returns the position data of the provided `input_device`. /// /// This will be clamped between [`Axis::MIN`] and [`Axis::MAX`] inclusive. - pub fn get(&self, input_device: T) -> Option { + pub fn get(&self, input_device: impl Into) -> Option { self.axis_data - .get(&input_device) + .get(&input_device.into()) .copied() .map(|value| value.clamp(Self::MIN, Self::MAX)) } @@ -66,13 +66,13 @@ where /// 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 /// than another would give an unfair advantage, you should likely use [`Axis::get`] instead. - pub fn get_unclamped(&self, input_device: T) -> Option { - self.axis_data.get(&input_device).copied() + pub fn get_unclamped(&self, input_device: impl Into) -> Option { + self.axis_data.get(&input_device.into()).copied() } /// Removes the position data of the `input_device`, returning the position data if the input device was previously set. - pub fn remove(&mut self, input_device: T) -> Option { - self.axis_data.remove(&input_device) + pub fn remove(&mut self, input_device: impl Into) -> Option { + self.axis_data.remove(&input_device.into()) } /// Returns an iterator over all axes. diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 8c9d369063..05171949ec 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -306,13 +306,12 @@ pub enum ButtonSettingsError { }, } -/// The [`Gamepad`] [`component`](Component) stores a connected gamepad's metadata such as the `name` and its [`GamepadButton`] and [`GamepadAxis`]. +/// Stores a connected gamepad's state and any metadata such as the device name. /// -/// The [`entity`](Entity) representing a gamepad and its [`minimal components`](GamepadSettings) are automatically managed. +/// An entity with this component is spawned automatically after [`GamepadConnectionEvent`] +/// and updated by [`gamepad_event_processing_system`]. /// -/// # Usage -/// -/// The only way to obtain a [`Gamepad`] is by [`query`](Query). +/// See also [`GamepadSettings`] for configuration. /// /// # Examples /// @@ -321,14 +320,15 @@ pub enum ButtonSettingsError { /// # use bevy_ecs::system::Query; /// # /// fn gamepad_usage_system(gamepads: Query<&Gamepad>) { -/// for gamepad in gamepads.iter() { -/// println!("{}", gamepad.name()); +/// for gamepad in &gamepads { +/// let name = &gamepad.info.name; +/// println!("{name}"); /// -/// if gamepad.just_pressed(GamepadButton::North) { -/// println!("{} just pressed North", gamepad.name()) +/// if gamepad.digital.just_pressed(GamepadButton::North) { +/// println!("{name} just pressed North") /// } /// -/// if let Some(left_stick_x) = gamepad.get(GamepadAxis::LeftStickX) { +/// if let Some(left_stick_x) = gamepad.analog.get(GamepadAxis::LeftStickX) { /// println!("left stick X: {}", left_stick_x) /// } /// } @@ -338,22 +338,23 @@ pub enum ButtonSettingsError { #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] #[require(GamepadSettings)] pub struct Gamepad { - info: GamepadInfo, + /// Metadata. + pub info: GamepadInfo, /// [`ButtonInput`] of [`GamepadButton`] representing their digital state - pub(crate) digital: ButtonInput, + pub digital: ButtonInput, /// [`Axis`] of [`GamepadButton`] representing their analog state. - pub(crate) analog: Axis, + pub analog: Axis, } impl Gamepad { /// Creates a gamepad with the given metadata. - fn new(info: GamepadInfo) -> Self { + pub fn new(info: GamepadInfo) -> Self { let mut analog = Axis::default(); for button in GamepadButton::all().iter().copied() { - analog.set(button.into(), 0.0); + analog.set(button, 0.0); } for axis_type in GamepadAxis::all().iter().copied() { - analog.set(axis_type.into(), 0.0); + analog.set(axis_type, 0.0); } Self { info, @@ -362,168 +363,38 @@ impl Gamepad { } } - /// The name of the gamepad. - /// - /// This name is generally defined by the OS. - /// - /// For example on Windows the name may be "HID-compliant game controller". - pub fn name(&self) -> &str { - self.info.name.as_str() - } - - /// Returns the USB vendor ID as assigned by the USB-IF, if available. - pub fn vendor_id(&self) -> Option { - self.info.vendor_id - } - - /// Returns the USB product ID as assigned by the [vendor], if available. - /// - /// [vendor]: Self::vendor_id - pub fn product_id(&self) -> Option { - self.info.product_id - } - - /// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`]. - /// - /// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]]. - pub fn get(&self, input: impl Into) -> Option { - self.analog.get(input.into()) - } - - /// Returns the unclamped analog data of the provided [`GamepadAxis`] or [`GamepadButton`]. - /// - /// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range. - pub fn get_unclamped(&self, input: impl Into) -> Option { - self.analog.get_unclamped(input.into()) - } - /// Returns the left stick as a [`Vec2`] pub fn left_stick(&self) -> Vec2 { Vec2 { - x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0), - y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0), + x: self.analog.get(GamepadAxis::LeftStickX).unwrap_or(0.0), + y: self.analog.get(GamepadAxis::LeftStickY).unwrap_or(0.0), } } /// Returns the right stick as a [`Vec2`] pub fn right_stick(&self) -> Vec2 { Vec2 { - x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0), - y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0), + x: self.analog.get(GamepadAxis::RightStickX).unwrap_or(0.0), + y: self.analog.get(GamepadAxis::RightStickY).unwrap_or(0.0), } } /// Returns the directional pad as a [`Vec2`] pub fn dpad(&self) -> Vec2 { Vec2 { - x: self.get(GamepadButton::DPadRight).unwrap_or(0.0) - - self.get(GamepadButton::DPadLeft).unwrap_or(0.0), - y: self.get(GamepadButton::DPadUp).unwrap_or(0.0) - - self.get(GamepadButton::DPadDown).unwrap_or(0.0), + x: self.analog.get(GamepadButton::DPadRight).unwrap_or(0.0) + - self.analog.get(GamepadButton::DPadLeft).unwrap_or(0.0), + y: self.analog.get(GamepadButton::DPadUp).unwrap_or(0.0) + - self.analog.get(GamepadButton::DPadDown).unwrap_or(0.0), } } - - /// Returns `true` if the [`GamepadButton`] has been pressed. - pub fn pressed(&self, button_type: GamepadButton) -> bool { - self.digital.pressed(button_type) - } - - /// Returns `true` if any item in [`GamepadButton`] has been pressed. - pub fn any_pressed(&self, button_inputs: impl IntoIterator) -> bool { - button_inputs - .into_iter() - .any(|button_type| self.pressed(button_type)) - } - - /// Returns `true` if all items in [`GamepadButton`] have been pressed. - pub fn all_pressed(&self, button_inputs: impl IntoIterator) -> bool { - button_inputs - .into_iter() - .all(|button_type| self.pressed(button_type)) - } - - /// Returns `true` if the [`GamepadButton`] has been pressed during the current frame. - /// - /// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_released`]. - pub fn just_pressed(&self, button_type: GamepadButton) -> bool { - self.digital.just_pressed(button_type) - } - - /// Returns `true` if any item in [`GamepadButton`] has been pressed during the current frame. - pub fn any_just_pressed(&self, button_inputs: impl IntoIterator) -> bool { - button_inputs - .into_iter() - .any(|button_type| self.just_pressed(button_type)) - } - - /// Returns `true` if all items in [`GamepadButton`] have been just pressed. - pub fn all_just_pressed(&self, button_inputs: impl IntoIterator) -> bool { - button_inputs - .into_iter() - .all(|button_type| self.just_pressed(button_type)) - } - - /// Returns `true` if the [`GamepadButton`] has been released during the current frame. - /// - /// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_pressed`]. - pub fn just_released(&self, button_type: GamepadButton) -> bool { - self.digital.just_released(button_type) - } - - /// Returns `true` if any item in [`GamepadButton`] has just been released. - pub fn any_just_released( - &self, - button_inputs: impl IntoIterator, - ) -> bool { - button_inputs - .into_iter() - .any(|button_type| self.just_released(button_type)) - } - - /// Returns `true` if all items in [`GamepadButton`] have just been released. - pub fn all_just_released( - &self, - button_inputs: impl IntoIterator, - ) -> bool { - button_inputs - .into_iter() - .all(|button_type| self.just_released(button_type)) - } - - /// Returns an iterator over all digital [button]s that are pressed. - /// - /// [button]: GamepadButton - pub fn get_pressed(&self) -> impl Iterator { - self.digital.get_pressed() - } - - /// Returns an iterator over all digital [button]s that were just pressed. - /// - /// [button]: GamepadButton - pub fn get_just_pressed(&self) -> impl Iterator { - self.digital.get_just_pressed() - } - - /// Returns an iterator over all digital [button]s that were just released. - /// - /// [button]: GamepadButton - pub fn get_just_released(&self) -> impl Iterator { - self.digital.get_just_released() - } - - /// Returns an iterator over all analog [axes]. - /// - /// [axes]: GamepadInput - pub fn get_analog_axes(&self) -> impl Iterator { - self.analog.all_axes() - } } // Note that we don't expose `gilrs::Gamepad::uuid` due to // https://gitlab.com/gilrs-project/gilrs/-/issues/153. // /// Metadata associated with a [`Gamepad`]. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( @@ -1426,12 +1297,12 @@ pub fn gamepad_event_processing_system( }; let Some(filtered_value) = gamepad_settings .get_axis_settings(axis) - .filter(value, gamepad_axis.get(axis)) + .filter(value, gamepad_axis.analog.get(axis)) else { continue; }; - gamepad_axis.analog.set(axis.into(), filtered_value); + gamepad_axis.analog.set(axis, filtered_value); let send_event = GamepadAxisChangedEvent::new(gamepad, axis, filtered_value); processed_axis_events.send(send_event); processed_events.send(GamepadEvent::from(send_event)); @@ -1447,16 +1318,16 @@ pub fn gamepad_event_processing_system( }; let Some(filtered_value) = settings .get_button_axis_settings(button) - .filter(value, gamepad_buttons.get(button)) + .filter(value, gamepad_buttons.analog.get(button)) else { continue; }; let button_settings = settings.get_button_settings(button); - gamepad_buttons.analog.set(button.into(), filtered_value); + gamepad_buttons.analog.set(button, filtered_value); if button_settings.is_released(filtered_value) { // Check if button was previously pressed - if gamepad_buttons.pressed(button) { + if gamepad_buttons.digital.pressed(button) { processed_digital_events.send(GamepadButtonStateChangedEvent::new( gamepad, button, @@ -1468,7 +1339,7 @@ pub fn gamepad_event_processing_system( gamepad_buttons.digital.release(button); } else if button_settings.is_pressed(filtered_value) { // Check if button was previously not pressed - if !gamepad_buttons.pressed(button) { + if !gamepad_buttons.digital.pressed(button) { processed_digital_events.send(GamepadButtonStateChangedEvent::new( gamepad, button, @@ -2000,11 +1871,7 @@ mod tests { .resource_mut::>() .send(GamepadConnectionEvent::new( gamepad, - Connected(GamepadInfo { - name: String::from("Gamepad test"), - vendor_id: None, - product_id: None, - }), + Connected(GamepadInfo::default()), )); gamepad } @@ -2522,13 +2389,8 @@ mod tests { assert_eq!(event.button, GamepadButton::DPadDown); assert_eq!(event.state, ButtonState::Pressed); } - assert!(ctx - .app - .world_mut() - .query::<&Gamepad>() - .get(ctx.app.world(), entity) - .unwrap() - .pressed(GamepadButton::DPadDown)); + let gamepad = ctx.app.world_mut().get::(entity).unwrap(); + assert!(gamepad.digital.pressed(GamepadButton::DPadDown)); ctx.app .world_mut() @@ -2543,13 +2405,8 @@ mod tests { .len(), 0 ); - assert!(ctx - .app - .world_mut() - .query::<&Gamepad>() - .get(ctx.app.world(), entity) - .unwrap() - .pressed(GamepadButton::DPadDown)); + let gamepad = ctx.app.world_mut().get::(entity).unwrap(); + assert!(gamepad.digital.pressed(GamepadButton::DPadDown)); } #[test] @@ -2568,23 +2425,13 @@ mod tests { ctx.update(); // Check it is flagged for this frame - assert!(ctx - .app - .world_mut() - .query::<&Gamepad>() - .get(ctx.app.world(), entity) - .unwrap() - .just_pressed(GamepadButton::DPadDown)); + let gamepad = ctx.app.world_mut().get::(entity).unwrap(); + assert!(gamepad.digital.just_pressed(GamepadButton::DPadDown)); ctx.update(); //Check it clears next frame - assert!(!ctx - .app - .world_mut() - .query::<&Gamepad>() - .get(ctx.app.world(), entity) - .unwrap() - .just_pressed(GamepadButton::DPadDown)); + let gamepad = ctx.app.world_mut().get::(entity).unwrap(); + assert!(!gamepad.digital.just_pressed(GamepadButton::DPadDown)); } #[test] fn gamepad_buttons_released() { @@ -2627,13 +2474,8 @@ mod tests { assert_eq!(event.button, GamepadButton::DPadDown); assert_eq!(event.state, ButtonState::Released); } - assert!(!ctx - .app - .world_mut() - .query::<&Gamepad>() - .get(ctx.app.world(), entity) - .unwrap() - .pressed(GamepadButton::DPadDown)); + let gamepad = ctx.app.world_mut().get::(entity).unwrap(); + assert!(!gamepad.digital.pressed(GamepadButton::DPadDown)); ctx.app .world_mut() .resource_mut::>() @@ -2672,23 +2514,13 @@ mod tests { ctx.update(); // Check it is flagged for this frame - assert!(ctx - .app - .world_mut() - .query::<&Gamepad>() - .get(ctx.app.world(), entity) - .unwrap() - .just_released(GamepadButton::DPadDown)); + let gamepad = ctx.app.world_mut().get::(entity).unwrap(); + assert!(gamepad.digital.just_released(GamepadButton::DPadDown)); ctx.update(); - //Check it clears next frame - assert!(!ctx - .app - .world_mut() - .query::<&Gamepad>() - .get(ctx.app.world(), entity) - .unwrap() - .just_released(GamepadButton::DPadDown)); + // Check it clears next frame + let gamepad = ctx.app.world_mut().get::(entity).unwrap(); + assert!(!gamepad.digital.just_released(GamepadButton::DPadDown)); } #[test] diff --git a/examples/input/gamepad_input.rs b/examples/input/gamepad_input.rs index 4f3dfba4ae..d81e6fd836 100644 --- a/examples/input/gamepad_input.rs +++ b/examples/input/gamepad_input.rs @@ -11,18 +11,18 @@ fn main() { fn gamepad_system(gamepads: Query<(Entity, &Gamepad)>) { for (entity, gamepad) in &gamepads { - if gamepad.just_pressed(GamepadButton::South) { + if gamepad.digital.just_pressed(GamepadButton::South) { info!("{:?} just pressed South", entity); - } else if gamepad.just_released(GamepadButton::South) { + } else if gamepad.digital.just_released(GamepadButton::South) { info!("{:?} just released South", entity); } - let right_trigger = gamepad.get(GamepadButton::RightTrigger2).unwrap(); + let right_trigger = gamepad.analog.get(GamepadButton::RightTrigger2).unwrap(); if right_trigger.abs() > 0.01 { info!("{:?} RightTrigger2 value is {}", entity, right_trigger); } - let left_stick_x = gamepad.get(GamepadAxis::LeftStickX).unwrap(); + let left_stick_x = gamepad.analog.get(GamepadAxis::LeftStickX).unwrap(); if left_stick_x.abs() > 0.01 { info!("{:?} LeftStickX value is {}", entity, left_stick_x); } diff --git a/examples/input/gamepad_rumble.rs b/examples/input/gamepad_rumble.rs index cdc5a9c85e..c3262daf52 100644 --- a/examples/input/gamepad_rumble.rs +++ b/examples/input/gamepad_rumble.rs @@ -19,7 +19,7 @@ fn gamepad_system( mut rumble_requests: EventWriter, ) { for (entity, gamepad) in &gamepads { - if gamepad.just_pressed(GamepadButton::North) { + if gamepad.digital.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." ); @@ -30,7 +30,7 @@ fn gamepad_system( }); } - if gamepad.just_pressed(GamepadButton::East) { + if gamepad.digital.just_pressed(GamepadButton::East) { info!("East face button: maximum rumble on both motors for 5 seconds"); rumble_requests.send(GamepadRumbleRequest::Add { gamepad: entity, @@ -39,7 +39,7 @@ fn gamepad_system( }); } - if gamepad.just_pressed(GamepadButton::South) { + if gamepad.digital.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: entity, @@ -48,7 +48,7 @@ fn gamepad_system( }); } - if gamepad.just_pressed(GamepadButton::West) { + if gamepad.digital.just_pressed(GamepadButton::West) { info!("West face button: custom rumble intensity for 5 second"); rumble_requests.send(GamepadRumbleRequest::Add { gamepad: entity, @@ -62,7 +62,7 @@ fn gamepad_system( }); } - if gamepad.just_pressed(GamepadButton::Start) { + if gamepad.digital.just_pressed(GamepadButton::Start) { info!("Start button: Interrupt the current rumble"); rumble_requests.send(GamepadRumbleRequest::Stop { gamepad: entity }); } diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index 1112a227dc..ec8b64615c 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -393,12 +393,12 @@ fn update_buttons( materials: Res, mut query: Query<(&mut MeshMaterial2d, &ReactTo)>, ) { - for buttons in &gamepads { + for gamepad in &gamepads { for (mut handle, react_to) in query.iter_mut() { - if buttons.just_pressed(**react_to) { + if gamepad.digital.just_pressed(**react_to) { *handle = materials.active.clone(); } - if buttons.just_released(**react_to) { + if gamepad.digital.just_released(**react_to) { *handle = materials.normal.clone(); } } @@ -458,7 +458,7 @@ fn update_connected( let formatted = gamepads .iter() - .map(|(entity, gamepad)| format!("{} - {}", entity, gamepad.name())) + .map(|(entity, gamepad)| format!("{} - {}", entity, gamepad.info.name)) .collect::>() .join("\n");