Skip to content

Commit

Permalink
Add some missing features from the gamepads-as-entities change that were
Browse files Browse the repository at this point in the history
needed to update `leafwing-input-manager`.

The gamepads-as-entities change caused several regressions. This patch
fixes each of them:

1. After that PR, there was no stable unique identifier available for
   gamepads. To fix this, this PR introduces several new fields on
   `GamepadInfo`: `uuid`, `vendor_id`, and `product_id`, as well as
   associated methods. These fields are simply mirrored from the `gilrs`
   library.  The UUID is the preferred way to identify gamepads across
   app invocations.

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`.
  • Loading branch information
pcwalton committed Oct 6, 2024
1 parent b4ffb7a commit b44a95f
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 2 deletions.
1 change: 1 addition & 0 deletions crates/bevy_gilrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bevy_time = { path = "../bevy_time", version = "0.15.0-dev" }
# other
gilrs = "0.11.0"
thiserror = "1.0"
uuid = "1"

[lints]
workspace = true
Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_gilrs/src/gilrs_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use bevy_input::gamepad::{
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};
use uuid::Uuid;

pub fn gilrs_event_startup_system(
mut commands: Commands,
Expand All @@ -28,6 +29,9 @@ pub fn gilrs_event_startup_system(

let info = GamepadInfo {
name: gamepad.name().into(),
uuid: Uuid::from_bytes(gamepad.uuid()),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
};

events.send(GamepadConnectionEvent {
Expand Down Expand Up @@ -62,6 +66,9 @@ pub fn gilrs_event_system(

let info = GamepadInfo {
name: pad.name().into(),
uuid: Uuid::from_bytes(pad.uuid()),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
};

events.send(
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_input/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
serde = { version = "1", features = ["derive"], optional = true }
thiserror = "1.0"
smol_str = "0.2"
uuid = "1"

[lints]
workspace = true
Expand Down
9 changes: 9 additions & 0 deletions crates/bevy_input/src/axis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
/// The position data of the input devices.
axis_data: HashMap<T, f32>,
Expand Down Expand Up @@ -70,6 +74,11 @@ where
pub fn remove(&mut self, input_device: T) -> Option<f32> {
self.axis_data.remove(&input_device)
}

/// Returns an iterator over all axes.
pub fn all_axes(&self) -> impl Iterator<Item = &T> {
self.axis_data.keys()
}
}

#[cfg(test)]
Expand Down
78 changes: 76 additions & 2 deletions crates/bevy_input/src/gamepad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use bevy_utils::{
Duration, HashMap,
};
use thiserror::Error;
use uuid::Uuid;

/// A gamepad event.
///
Expand Down Expand Up @@ -364,6 +365,7 @@ pub enum ButtonSettingsError {
/// }
/// ```
#[derive(Component, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
#[require(GamepadSettings)]
pub struct Gamepad {
info: GamepadInfo,
Expand All @@ -374,8 +376,11 @@ pub struct Gamepad {
}

impl Gamepad {
/// Creates a gamepad with the given metadata
fn new(info: GamepadInfo) -> Self {
/// Creates a gamepad with the given metadata.
///
/// Ordinarily, you shouldn't call this, but it can be useful for test
/// mocking.
pub fn new(info: GamepadInfo) -> Self {
let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() {
analog.set(button.into(), 0.0);
Expand All @@ -399,6 +404,27 @@ impl Gamepad {
self.info.name.as_str()
}

/// The UUID of the game pad.
///
/// Bevy will try to reuse IDs for game controllers across invocations of
/// the app. Therefore, this ID can be used to track the gamepad across
/// runs.
pub fn uuid(&self) -> &Uuid {
&self.info.uuid
}

/// Returns the USB vendor ID as assigned by the USB-IF, if available.
pub fn vendor_id(&self) -> Option<u16> {
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<u16> {
self.info.product_id
}

/// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
///
/// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]].
Expand Down Expand Up @@ -505,6 +531,34 @@ 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<Item = &GamepadButton> {
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<Item = &GamepadButton> {
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<Item = &GamepadButton> {
self.digital.get_just_released()
}

/// Returns an iterator over all analog [axes].
///
/// [axes]: GamepadInput
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
self.analog.all_axes()
}
}

/// Metadata associated with a [`Gamepad`].
Expand All @@ -522,6 +576,21 @@ pub struct GamepadInfo {
///
/// For example on Windows the name may be "HID-compliant game controller".
pub name: String,

/// The UUID of the game pad.
///
/// Bevy will try to reuse IDs for game controllers across invocations of
/// the app. Therefore, this ID can be used to track the gamepad across
/// runs.
pub uuid: Uuid,

/// The USB vendor ID as assigned by the USB-IF, if available.
pub vendor_id: Option<u16>,

/// The USB product ID as assigned by the [vendor], if available.
///
/// [vendor]: Self::vendor_id
pub product_id: Option<u16>,
}

/// Represents gamepad input types that are mapped in the range [0.0, 1.0].
Expand Down Expand Up @@ -665,6 +734,7 @@ impl GamepadAxis {
/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`]
// This is done so Gamepad can share a single Axis<T> 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),
Expand Down Expand Up @@ -1620,6 +1690,7 @@ mod tests {
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Events;
use bevy_ecs::schedule::IntoSystemConfigs;
use uuid::Uuid;

fn test_button_axis_settings_filter(
settings: ButtonAxisSettings,
Expand Down Expand Up @@ -1988,6 +2059,9 @@ mod tests {
gamepad,
Connected(GamepadInfo {
name: String::from("Gamepad test"),
uuid: Uuid::parse_str("6d860618-c538-4d51-b0a5-0959b9f8c670").unwrap(),
vendor_id: None,
product_id: None,
}),
));
gamepad
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_input/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ 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,
Expand Down Expand Up @@ -134,6 +136,7 @@ impl Plugin for InputPlugin {
.register_type::<RawGamepadEvent>()
.register_type::<RawGamepadAxisChangedEvent>()
.register_type::<RawGamepadButtonChangedEvent>()
.register_type::<Gamepad>()
.register_type::<GamepadConnectionEvent>()
.register_type::<GamepadButtonChangedEvent>()
.register_type::<GamepadAxisChangedEvent>()
Expand Down

0 comments on commit b44a95f

Please sign in to comment.