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::ResMut;
use bevy_input::gamepad::{
GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadAxisChangedEvent,
GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
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.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 {
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
});
let info = GamepadInfo {
name: pad.name().into(),
let event = GamepadConnectionEvent::new(
entity,
GamepadConnection::Connected {
name: pad.name().to_string(),
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,
GamepadConnection::Connected(info),
));
events.send(event.clone().into());
connection_events.send(event);
}
EventType::Disconnected => {
let gamepad = gamepads

View file

@ -21,6 +21,7 @@ serialize = ["serde", "smol_str/serde"]
[dependencies]
# bevy
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 = [
"serialize",
] }

View file

@ -1,6 +1,7 @@
//! The gamepad input functionality.
use crate::{Axis, ButtonInput, ButtonState};
use bevy_core::Name;
use bevy_ecs::{
change_detection::DetectChangesMut,
component::Component,
@ -148,7 +149,7 @@ impl GamepadConnectionEvent {
/// Is the gamepad connected?
pub fn connected(&self) -> bool {
matches!(self.connection, GamepadConnection::Connected(_))
matches!(self.connection, GamepadConnection::Connected { .. })
}
/// Is the gamepad disconnected?
@ -318,10 +319,10 @@ pub enum ButtonSettingsError {
/// ```
/// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton};
/// # use bevy_ecs::system::Query;
/// # use bevy_core::Name;
/// #
/// fn gamepad_usage_system(gamepads: Query<&Gamepad>) {
/// for gamepad in &gamepads {
/// let name = &gamepad.info.name;
/// fn gamepad_usage_system(gamepads: Query<(&Name, &Gamepad)>) {
/// for (name, gamepad) in &gamepads {
/// println!("{name}");
///
/// if gamepad.digital.just_pressed(GamepadButton::North) {
@ -338,31 +339,22 @@ pub enum ButtonSettingsError {
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
#[require(GamepadSettings)]
pub struct Gamepad {
/// Metadata.
pub info: GamepadInfo,
/// 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>,
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
pub digital: ButtonInput<GamepadButton>,
/// [`Axis`] of [`GamepadButton`] representing their analog state.
pub analog: Axis<GamepadInput>,
}
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`]
pub fn left_stick(&self) -> Vec2 {
Vec2 {
@ -390,32 +382,23 @@ impl Gamepad {
}
}
// 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, 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(
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,
impl Default for Gamepad {
fn default() -> 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);
}
/// 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>,
Self {
vendor_id: None,
product_id: None,
digital: Default::default(),
analog,
}
}
}
/// 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() {
let id = connection_event.gamepad;
match &connection_event.connection {
GamepadConnection::Connected(info) => {
GamepadConnection::Connected {
name,
vendor_id,
product_id,
} => {
let Some(mut gamepad) = commands.get_entity(id) else {
warn!("Gamepad {:} removed before handling connection event.", id);
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);
}
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.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -1260,7 +1257,20 @@ pub fn gamepad_connection_system(
)]
pub enum GamepadConnection {
/// 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.
Disconnected,
}
@ -1497,8 +1507,8 @@ mod tests {
GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent,
GamepadConnection::{Connected, Disconnected},
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings,
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
GamepadConnectionEvent, GamepadEvent, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use crate::ButtonState;
use bevy_app::{App, PreUpdate};
@ -1871,7 +1881,11 @@ mod tests {
.resource_mut::<Events<GamepadConnectionEvent>>()
.send(GamepadConnectionEvent::new(
gamepad,
Connected(GamepadInfo::default()),
Connected {
name: "Test gamepad".to_string(),
vendor_id: None,
product_id: None,
},
));
gamepad
}

View file

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

View file

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