Use Name component for gamepad (#16233)

# Objective

Addressing a suggestion I made in Discord: store gamepad name as a
`Name` component.
Advantages: 
- Will be nicely displayed in inspector / editor.
- Easier to spawn in tests, just `world.spawn(Gamepad::default())`.

## Solution

`Gamepad` component now stores only vendor and product IDs and `Name`
stores the gamepad name.
Since `GamepadInfo` is no longer necessary, I removed it and merged its
fields into the connection event.

## Testing

- Run unit tests.

---

## Migration Guide

- `GamepadInfo` no longer exists:
  -  Name now accesible via `Name` component.
  -  Other information available on `Gamepad` component directly.
  - `GamepadConnection::Connected` now stores all info fields directly.
This commit is contained in:
Hennadii Chernyshchyk 2024-11-05 03:30:48 +03:00 committed by GitHub
parent 718688e791
commit 282ca735ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 87 additions and 78 deletions

View file

@ -8,7 +8,7 @@ use bevy_ecs::prelude::Commands;
use bevy_ecs::system::NonSendMut; use bevy_ecs::system::NonSendMut;
use bevy_ecs::system::ResMut; use bevy_ecs::system::ResMut;
use bevy_input::gamepad::{ use bevy_input::gamepad::{
GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadAxisChangedEvent, GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
}; };
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};
@ -26,15 +26,13 @@ pub fn gilrs_event_startup_system(
gamepads.id_to_entity.insert(id, entity); gamepads.id_to_entity.insert(id, entity);
gamepads.entity_to_id.insert(entity, id); gamepads.entity_to_id.insert(entity, id);
let info = GamepadInfo {
name: gamepad.name().into(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
};
events.send(GamepadConnectionEvent { events.send(GamepadConnectionEvent {
gamepad: entity, gamepad: entity,
connection: GamepadConnection::Connected(info), connection: GamepadConnection::Connected {
name: gamepad.name().to_string(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
},
}); });
} }
} }
@ -62,20 +60,17 @@ pub fn gilrs_event_system(
entity entity
}); });
let info = GamepadInfo { let event = GamepadConnectionEvent::new(
name: pad.name().into(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
};
events.send(
GamepadConnectionEvent::new(entity, GamepadConnection::Connected(info.clone()))
.into(),
);
connection_events.send(GamepadConnectionEvent::new(
entity, entity,
GamepadConnection::Connected(info), GamepadConnection::Connected {
)); name: pad.name().to_string(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
},
);
events.send(event.clone().into());
connection_events.send(event);
} }
EventType::Disconnected => { EventType::Disconnected => {
let gamepad = gamepads let gamepad = gamepads

View file

@ -21,6 +21,7 @@ serialize = ["serde", "smol_str/serde"]
[dependencies] [dependencies]
# bevy # bevy
bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false } bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false }
bevy_core = { path = "../bevy_core", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false, features = [ bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false, features = [
"serialize", "serialize",
] } ] }

View file

@ -1,6 +1,7 @@
//! The gamepad input functionality. //! The gamepad input functionality.
use crate::{Axis, ButtonInput, ButtonState}; use crate::{Axis, ButtonInput, ButtonState};
use bevy_core::Name;
use bevy_ecs::{ use bevy_ecs::{
change_detection::DetectChangesMut, change_detection::DetectChangesMut,
component::Component, component::Component,
@ -148,7 +149,7 @@ impl GamepadConnectionEvent {
/// Is the gamepad connected? /// Is the gamepad connected?
pub fn connected(&self) -> bool { pub fn connected(&self) -> bool {
matches!(self.connection, GamepadConnection::Connected(_)) matches!(self.connection, GamepadConnection::Connected { .. })
} }
/// Is the gamepad disconnected? /// Is the gamepad disconnected?
@ -318,10 +319,10 @@ pub enum ButtonSettingsError {
/// ``` /// ```
/// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton}; /// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton};
/// # use bevy_ecs::system::Query; /// # use bevy_ecs::system::Query;
/// # use bevy_core::Name;
/// # /// #
/// fn gamepad_usage_system(gamepads: Query<&Gamepad>) { /// fn gamepad_usage_system(gamepads: Query<(&Name, &Gamepad)>) {
/// for gamepad in &gamepads { /// for (name, gamepad) in &gamepads {
/// let name = &gamepad.info.name;
/// println!("{name}"); /// println!("{name}");
/// ///
/// if gamepad.digital.just_pressed(GamepadButton::North) { /// if gamepad.digital.just_pressed(GamepadButton::North) {
@ -338,31 +339,22 @@ pub enum ButtonSettingsError {
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
#[require(GamepadSettings)] #[require(GamepadSettings)]
pub struct Gamepad { pub struct Gamepad {
/// Metadata. /// The USB vendor ID as assigned by the USB-IF, if available.
pub info: GamepadInfo, 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>,
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state /// [`ButtonInput`] of [`GamepadButton`] representing their digital state
pub digital: ButtonInput<GamepadButton>, pub digital: ButtonInput<GamepadButton>,
/// [`Axis`] of [`GamepadButton`] representing their analog state. /// [`Axis`] of [`GamepadButton`] representing their analog state.
pub analog: Axis<GamepadInput>, pub analog: Axis<GamepadInput>,
} }
impl Gamepad { impl Gamepad {
/// Creates a gamepad with the given metadata.
pub fn new(info: GamepadInfo) -> Self {
let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() {
analog.set(button, 0.0);
}
for axis_type in GamepadAxis::all().iter().copied() {
analog.set(axis_type, 0.0);
}
Self {
info,
analog,
digital: ButtonInput::default(),
}
}
/// Returns the left stick as a [`Vec2`] /// Returns the left stick as a [`Vec2`]
pub fn left_stick(&self) -> Vec2 { pub fn left_stick(&self) -> Vec2 {
Vec2 { Vec2 {
@ -390,32 +382,23 @@ impl Gamepad {
} }
} }
// Note that we don't expose `gilrs::Gamepad::uuid` due to impl Default for Gamepad {
// https://gitlab.com/gilrs-project/gilrs/-/issues/153. fn default() -> Self {
// let mut analog = Axis::default();
/// Metadata associated with a [`Gamepad`]. for button in GamepadButton::all().iter().copied() {
#[derive(Debug, Default, Clone, PartialEq, Eq)] analog.set(button, 0.0);
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] }
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] for axis_type in GamepadAxis::all().iter().copied() {
#[cfg_attr( analog.set(axis_type, 0.0);
all(feature = "serialize", feature = "bevy_reflect"), }
reflect(Serialize, Deserialize)
)]
pub struct GamepadInfo {
/// 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 name: String,
/// The USB vendor ID as assigned by the USB-IF, if available. Self {
pub vendor_id: Option<u16>, vendor_id: None,
product_id: None,
/// The USB product ID as assigned by the [vendor], if available. digital: Default::default(),
/// analog,
/// [vendor]: Self::vendor_id }
pub product_id: Option<u16>, }
} }
/// Represents gamepad input types that are mapped in the range [0.0, 1.0]. /// Represents gamepad input types that are mapped in the range [0.0, 1.0].
@ -1227,12 +1210,23 @@ pub fn gamepad_connection_system(
for connection_event in connection_events.read() { for connection_event in connection_events.read() {
let id = connection_event.gamepad; let id = connection_event.gamepad;
match &connection_event.connection { match &connection_event.connection {
GamepadConnection::Connected(info) => { GamepadConnection::Connected {
name,
vendor_id,
product_id,
} => {
let Some(mut gamepad) = commands.get_entity(id) else { let Some(mut gamepad) = commands.get_entity(id) else {
warn!("Gamepad {:} removed before handling connection event.", id); warn!("Gamepad {:} removed before handling connection event.", id);
continue; continue;
}; };
gamepad.insert(Gamepad::new(info.clone())); gamepad.insert((
Name::new(name.clone()),
Gamepad {
vendor_id: *vendor_id,
product_id: *product_id,
..Default::default()
},
));
info!("Gamepad {:?} connected.", id); info!("Gamepad {:?} connected.", id);
} }
GamepadConnection::Disconnected => { GamepadConnection::Disconnected => {
@ -1250,6 +1244,9 @@ pub fn gamepad_connection_system(
} }
} }
// Note that we don't expose `gilrs::Gamepad::uuid` due to
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
//
/// The connection status of a gamepad. /// The connection status of a gamepad.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -1260,7 +1257,20 @@ pub fn gamepad_connection_system(
)] )]
pub enum GamepadConnection { pub enum GamepadConnection {
/// The gamepad is connected. /// The gamepad is connected.
Connected(GamepadInfo), Connected {
/// 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".
name: String,
/// The USB vendor ID as assigned by the USB-IF, if available.
vendor_id: Option<u16>,
/// The USB product ID as assigned by the vendor, if available.
product_id: Option<u16>,
},
/// The gamepad is disconnected. /// The gamepad is disconnected.
Disconnected, Disconnected,
} }
@ -1497,8 +1507,8 @@ mod tests {
GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent, GamepadButtonStateChangedEvent,
GamepadConnection::{Connected, Disconnected}, GamepadConnection::{Connected, Disconnected},
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings, GamepadConnectionEvent, GamepadEvent, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
}; };
use crate::ButtonState; use crate::ButtonState;
use bevy_app::{App, PreUpdate}; use bevy_app::{App, PreUpdate};
@ -1871,7 +1881,11 @@ mod tests {
.resource_mut::<Events<GamepadConnectionEvent>>() .resource_mut::<Events<GamepadConnectionEvent>>()
.send(GamepadConnectionEvent::new( .send(GamepadConnectionEvent::new(
gamepad, gamepad,
Connected(GamepadInfo::default()), Connected {
name: "Test gamepad".to_string(),
vendor_id: None,
product_id: None,
},
)); ));
gamepad gamepad
} }

View file

@ -57,7 +57,7 @@ use gamepad::{
gamepad_connection_system, gamepad_event_processing_system, GamepadAxis, gamepad_connection_system, gamepad_event_processing_system, GamepadAxis,
GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent,
GamepadInfo, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
}; };
@ -142,7 +142,6 @@ impl Plugin for InputPlugin {
.register_type::<GamepadButtonChangedEvent>() .register_type::<GamepadButtonChangedEvent>()
.register_type::<GamepadAxisChangedEvent>() .register_type::<GamepadAxisChangedEvent>()
.register_type::<GamepadButtonStateChangedEvent>() .register_type::<GamepadButtonStateChangedEvent>()
.register_type::<GamepadInfo>()
.register_type::<GamepadConnection>() .register_type::<GamepadConnection>()
.register_type::<GamepadSettings>() .register_type::<GamepadSettings>()
.register_type::<GamepadAxis>() .register_type::<GamepadAxis>()

View file

@ -447,7 +447,7 @@ fn update_axes(
fn update_connected( fn update_connected(
mut connected: EventReader<GamepadConnectionEvent>, mut connected: EventReader<GamepadConnectionEvent>,
gamepads: Query<(Entity, &Gamepad)>, gamepads: Query<(Entity, &Name), With<Gamepad>>,
text: Single<Entity, With<ConnectedGamepadsText>>, text: Single<Entity, With<ConnectedGamepadsText>>,
mut writer: TextUiWriter, mut writer: TextUiWriter,
) { ) {
@ -458,7 +458,7 @@ fn update_connected(
let formatted = gamepads let formatted = gamepads
.iter() .iter()
.map(|(entity, gamepad)| format!("{} - {}", entity, gamepad.info.name)) .map(|(entity, name)| format!("{} - {}", entity, name))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");