From 48e20278272ca8c725c86e1ed02d1f912ed89ff7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 8 Oct 2024 05:19:38 -0700 Subject: [PATCH] Add some missing features from the gamepads-as-entities change that were needed to update `leafwing-input-manager`. (#15685) The gamepads-as-entities change caused several regressions. This patch fixes each of them: 1. This PR introduces two new fields on `GamepadInfo`: `vendor_id`, and `product_id`, as well as associated methods. These fields are simply mirrored from the `gilrs` library. 2. That PR removed the methods that allowed iterating over all pressed and released buttons, as well as the method that allowed iterating over the axis values. (It was still technically possible to do so by using reflection to access the private fields of `Gamepad`.) 3. The `Gamepad` component wasn't marked reflectable. This PR fixes that problem. These changes allowed me to forward port `leafwing-input-manager`. --- crates/bevy_gilrs/src/gilrs_system.rs | 4 ++ crates/bevy_input/src/axis.rs | 14 +++++++ crates/bevy_input/src/gamepad.rs | 57 ++++++++++++++++++++++++++- crates/bevy_input/src/lib.rs | 15 +++++-- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 4ea9d2bf78..a0ed0e4e76 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -28,6 +28,8 @@ pub fn gilrs_event_startup_system( let info = GamepadInfo { name: gamepad.name().into(), + vendor_id: gamepad.vendor_id(), + product_id: gamepad.product_id(), }; events.send(GamepadConnectionEvent { @@ -62,6 +64,8 @@ pub fn gilrs_event_system( let info = GamepadInfo { name: pad.name().into(), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), }; events.send( diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs index df16c0babf..f2e97777cb 100644 --- a/crates/bevy_input/src/axis.rs +++ b/crates/bevy_input/src/axis.rs @@ -4,12 +4,16 @@ use bevy_ecs::system::Resource; use bevy_utils::HashMap; use core::hash::Hash; +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; + /// Stores the position data of the input devices of type `T`. /// /// The values are stored as `f32`s, using [`Axis::set`]. /// Use [`Axis::get`] to retrieve the value clamped between [`Axis::MIN`] and [`Axis::MAX`] /// inclusive, or unclamped using [`Axis::get_unclamped`]. #[derive(Debug, Resource)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] pub struct Axis { /// The position data of the input devices. axis_data: HashMap, @@ -70,6 +74,16 @@ where pub fn remove(&mut self, input_device: T) -> Option { self.axis_data.remove(&input_device) } + + /// Returns an iterator over all axes. + pub fn all_axes(&self) -> impl Iterator { + self.axis_data.keys() + } + + /// Returns an iterator over all axes and their values. + pub fn all_axes_and_values(&self) -> impl Iterator { + self.axis_data.iter().map(|(axis, value)| (axis, *value)) + } } #[cfg(test)] diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 152efe74c0..1e508a1e8f 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -364,6 +364,7 @@ pub enum ButtonSettingsError { /// } /// ``` #[derive(Component, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] #[require(GamepadSettings)] pub struct Gamepad { info: GamepadInfo, @@ -374,7 +375,7 @@ pub struct Gamepad { } impl Gamepad { - /// Creates a gamepad with the given metadata + /// Creates a gamepad with the given metadata. fn new(info: GamepadInfo) -> Self { let mut analog = Axis::default(); for button in GamepadButton::all().iter().copied() { @@ -399,6 +400,18 @@ impl Gamepad { 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`]]. @@ -505,8 +518,39 @@ impl Gamepad { .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)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] @@ -522,6 +566,14 @@ pub struct GamepadInfo { /// /// For example on Windows the name may be "HID-compliant game controller". pub name: String, + + /// The USB vendor ID as assigned by the USB-IF, if available. + pub vendor_id: Option, + + /// The USB product ID as assigned by the [vendor], if available. + /// + /// [vendor]: Self::vendor_id + pub product_id: Option, } /// Represents gamepad input types that are mapped in the range [0.0, 1.0]. @@ -665,6 +717,7 @@ impl GamepadAxis { /// Encapsulation over [`GamepadAxis`] and [`GamepadButton`] // This is done so Gamepad can share a single Axis and simplifies the API by having only one get/get_unclamped method #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] pub enum GamepadInput { /// A [`GamepadAxis`] Axis(GamepadAxis), @@ -1988,6 +2041,8 @@ mod tests { gamepad, Connected(GamepadInfo { name: String::from("Gamepad test"), + vendor_id: None, + product_id: None, }), )); gamepad diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index ddcfa5bf17..f78b264977 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -51,11 +51,14 @@ use mouse::{ }; use touch::{touch_screen_input_system, TouchInput, Touches}; +#[cfg(feature = "bevy_reflect")] +use gamepad::Gamepad; use gamepad::{ - gamepad_connection_system, gamepad_event_processing_system, GamepadAxisChangedEvent, - GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnection, - GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadRumbleRequest, GamepadSettings, - RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, + gamepad_connection_system, gamepad_event_processing_system, GamepadAxis, + GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, + GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent, + GamepadInfo, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent, + RawGamepadButtonChangedEvent, RawGamepadEvent, }; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] @@ -134,6 +137,7 @@ impl Plugin for InputPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() @@ -141,6 +145,9 @@ impl Plugin for InputPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::() + .register_type::() .register_type::() .register_type::(); }