Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement gamepads as entities #12770

Merged
merged 71 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
0075a18
Initial implementation
s-puig Mar 27, 2024
8e0c983
Merge Gamepad and GamepadInfo
s-puig Mar 28, 2024
7c547b1
Merge analog and digital buttons components into GamepadButtons
s-puig Mar 28, 2024
18a8cce
Reimplement axis filtering
s-puig Mar 28, 2024
8acf8c0
Reimplement button filtering
s-puig Mar 28, 2024
385c732
Docs
s-puig Mar 28, 2024
43b3c49
Fix connection event
s-puig Mar 28, 2024
3c5aea1
Linting
s-puig Mar 28, 2024
1454ef2
Rename GamepadAxisComponent to GamepadAxes
s-puig Mar 28, 2024
21eb9c8
Fix examples
s-puig Mar 28, 2024
e380b0b
Preserve gamepad settings after connection events
s-puig Mar 30, 2024
663826c
Add analog button changed event
s-puig Mar 30, 2024
e4ab4c6
Refactor events
s-puig Mar 30, 2024
969182f
Remove gamepad event system
s-puig Mar 30, 2024
9757988
Linting
s-puig Mar 30, 2024
7ab28e9
Add Gamepads methods
s-puig Mar 31, 2024
4fac996
CI fixes
s-puig Mar 31, 2024
e297edb
Add connection tests
s-puig Mar 31, 2024
21a95d1
Add system tests
s-puig Mar 31, 2024
a737adf
Merge branch 'main' into gamepad-entities
s-puig Mar 31, 2024
9c9a347
Fix CI
s-puig Mar 31, 2024
c22e3ac
Add derives
s-puig Mar 31, 2024
7fbabdf
Fix doc example
s-puig Mar 31, 2024
fcc44ca
Change GamepadId to tuple
s-puig Mar 31, 2024
c3a8132
Fix docs
s-puig Mar 31, 2024
9e8645a
Add tests
s-puig Apr 1, 2024
4d010ed
Add more tests
s-puig Apr 1, 2024
deb5e4e
Merge branch 'main' into gamepad-entities
s-puig Apr 1, 2024
e5b0403
Fix example gamepad_viewer
s-puig Apr 1, 2024
0e2f315
Implement defaults for axis
s-puig Apr 1, 2024
27f97ad
Linting
s-puig Apr 1, 2024
041af88
Fix typo
s-puig Apr 1, 2024
e2766a5
Fix tests after adding defaults
s-puig Apr 1, 2024
2f40aae
Event naming consistency and add events to gamepad_input_events example
s-puig Apr 1, 2024
dc585ff
Merge 'main'
s-puig May 16, 2024
624f932
Remove outdated run conditions
s-puig May 16, 2024
bbba226
Merge branch 'main' into gamepad-entities
s-puig Sep 19, 2024
9fe78c6
Merge branch 'main' into gamepad-entities
s-puig Sep 19, 2024
94419e6
Fix CI
s-puig Sep 19, 2024
7d5cf91
Temporarily disable gamepad tests
s-puig Sep 19, 2024
562b968
Remove raw events from example
s-puig Sep 19, 2024
7b46818
Merge GamepadButtons and GamepadAxes into Gamepad component
s-puig Sep 20, 2024
8ba65f2
Rename GamepadAxisType to GamepadAxis
s-puig Sep 20, 2024
be31660
Rename GamepadButtonType to GamepadButton
s-puig Sep 20, 2024
921bbda
Merge gamepad axis into a single prop
s-puig Sep 20, 2024
19960ff
Merge gamepad input event systems
s-puig Sep 20, 2024
ae0bb4e
Add dpad function
s-puig Sep 20, 2024
86f2ec4
Formatting
s-puig Sep 20, 2024
105a4d2
Change from GamepadId to entity
s-puig Sep 20, 2024
3166167
Fix gamepad_rumble.rs example
s-puig Sep 21, 2024
42a0991
Input use RawGamepadEvent
s-puig Sep 21, 2024
67a3fb4
Reconnection on bevy_gilrs
s-puig Sep 21, 2024
a4f5dd9
Remove old comment
s-puig Sep 21, 2024
2d0cf41
Refactor some tests
s-puig Sep 21, 2024
bde6718
Formatting
s-puig Sep 21, 2024
e001dc6
Finished migrating tests
s-puig Sep 23, 2024
312e052
Remove warnings
s-puig Sep 23, 2024
d1b6402
Fix bevy_input docs
s-puig Sep 23, 2024
a7e3801
Happy CI
s-puig Sep 23, 2024
56851fd
Re-add raw events
s-puig Sep 23, 2024
bd2f3b7
Fix CI
s-puig Sep 23, 2024
6f12b4d
Re-add GamepadEvent
s-puig Sep 23, 2024
76c93d7
Merge branch 'main' into gamepad-entities
s-puig Sep 23, 2024
d425ce2
Fix tests
s-puig Sep 23, 2024
3457616
Fix CI
s-puig Sep 23, 2024
fe353e4
Example use Gamepad event instead of raw
s-puig Sep 23, 2024
cec4610
Clean up examples
s-puig Sep 27, 2024
b148ddd
Rename and private Gamepads
s-puig Sep 27, 2024
d0e8c1c
Add comment to gamepad removal
s-puig Sep 27, 2024
3d60b38
Merge branch 'main' into gamepad-entities
s-puig Sep 27, 2024
c27b162
Fix CI
s-puig Sep 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 28 additions & 32 deletions crates/bevy_gilrs/src/converter.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
use bevy_input::gamepad::{Gamepad, GamepadAxisType, GamepadButtonType};
use bevy_input::gamepad::{GamepadAxis, GamepadButton};

pub fn convert_gamepad_id(gamepad_id: gilrs::GamepadId) -> Gamepad {
Gamepad::new(gamepad_id.into())
}

pub fn convert_button(button: gilrs::Button) -> Option<GamepadButtonType> {
pub fn convert_button(button: gilrs::Button) -> Option<GamepadButton> {
match button {
gilrs::Button::South => Some(GamepadButtonType::South),
gilrs::Button::East => Some(GamepadButtonType::East),
gilrs::Button::North => Some(GamepadButtonType::North),
gilrs::Button::West => Some(GamepadButtonType::West),
gilrs::Button::C => Some(GamepadButtonType::C),
gilrs::Button::Z => Some(GamepadButtonType::Z),
gilrs::Button::LeftTrigger => Some(GamepadButtonType::LeftTrigger),
gilrs::Button::LeftTrigger2 => Some(GamepadButtonType::LeftTrigger2),
gilrs::Button::RightTrigger => Some(GamepadButtonType::RightTrigger),
gilrs::Button::RightTrigger2 => Some(GamepadButtonType::RightTrigger2),
gilrs::Button::Select => Some(GamepadButtonType::Select),
gilrs::Button::Start => Some(GamepadButtonType::Start),
gilrs::Button::Mode => Some(GamepadButtonType::Mode),
gilrs::Button::LeftThumb => Some(GamepadButtonType::LeftThumb),
gilrs::Button::RightThumb => Some(GamepadButtonType::RightThumb),
gilrs::Button::DPadUp => Some(GamepadButtonType::DPadUp),
gilrs::Button::DPadDown => Some(GamepadButtonType::DPadDown),
gilrs::Button::DPadLeft => Some(GamepadButtonType::DPadLeft),
gilrs::Button::DPadRight => Some(GamepadButtonType::DPadRight),
gilrs::Button::South => Some(GamepadButton::South),
gilrs::Button::East => Some(GamepadButton::East),
gilrs::Button::North => Some(GamepadButton::North),
gilrs::Button::West => Some(GamepadButton::West),
gilrs::Button::C => Some(GamepadButton::C),
gilrs::Button::Z => Some(GamepadButton::Z),
gilrs::Button::LeftTrigger => Some(GamepadButton::LeftTrigger),
gilrs::Button::LeftTrigger2 => Some(GamepadButton::LeftTrigger2),
gilrs::Button::RightTrigger => Some(GamepadButton::RightTrigger),
gilrs::Button::RightTrigger2 => Some(GamepadButton::RightTrigger2),
gilrs::Button::Select => Some(GamepadButton::Select),
gilrs::Button::Start => Some(GamepadButton::Start),
gilrs::Button::Mode => Some(GamepadButton::Mode),
gilrs::Button::LeftThumb => Some(GamepadButton::LeftThumb),
gilrs::Button::RightThumb => Some(GamepadButton::RightThumb),
gilrs::Button::DPadUp => Some(GamepadButton::DPadUp),
gilrs::Button::DPadDown => Some(GamepadButton::DPadDown),
gilrs::Button::DPadLeft => Some(GamepadButton::DPadLeft),
gilrs::Button::DPadRight => Some(GamepadButton::DPadRight),
gilrs::Button::Unknown => None,
}
}

pub fn convert_axis(axis: gilrs::Axis) -> Option<GamepadAxisType> {
pub fn convert_axis(axis: gilrs::Axis) -> Option<GamepadAxis> {
match axis {
gilrs::Axis::LeftStickX => Some(GamepadAxisType::LeftStickX),
gilrs::Axis::LeftStickY => Some(GamepadAxisType::LeftStickY),
gilrs::Axis::LeftZ => Some(GamepadAxisType::LeftZ),
gilrs::Axis::RightStickX => Some(GamepadAxisType::RightStickX),
gilrs::Axis::RightStickY => Some(GamepadAxisType::RightStickY),
gilrs::Axis::RightZ => Some(GamepadAxisType::RightZ),
gilrs::Axis::LeftStickX => Some(GamepadAxis::LeftStickX),
gilrs::Axis::LeftStickY => Some(GamepadAxis::LeftStickY),
gilrs::Axis::LeftZ => Some(GamepadAxis::LeftZ),
gilrs::Axis::RightStickX => Some(GamepadAxis::RightStickX),
gilrs::Axis::RightStickY => Some(GamepadAxis::RightStickY),
gilrs::Axis::RightZ => Some(GamepadAxis::RightZ),
// The `axis_dpad_to_button` gilrs filter should filter out all DPadX and DPadY events. If
// it doesn't then we probably need an entry added to the following repo and an update to
// GilRs to use the updated database: https://github.com/gabomdq/SDL_GameControllerDB
Expand Down
128 changes: 69 additions & 59 deletions crates/bevy_gilrs/src/gilrs_system.rs
Original file line number Diff line number Diff line change
@@ -1,103 +1,113 @@
use crate::{
converter::{convert_axis, convert_button, convert_gamepad_id},
Gilrs,
converter::{convert_axis, convert_button},
Gilrs, GilrsGamepads,
};
use bevy_ecs::event::EventWriter;
use bevy_ecs::prelude::Commands;
#[cfg(target_arch = "wasm32")]
use bevy_ecs::system::NonSendMut;
use bevy_ecs::{
event::EventWriter,
system::{Res, ResMut},
};
use bevy_input::{
gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection,
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings,
},
prelude::{GamepadAxis, GamepadButton},
Axis,
use bevy_ecs::system::ResMut;
use bevy_input::gamepad::{
GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};

pub fn gilrs_event_startup_system(
mut commands: Commands,
#[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
mut events: EventWriter<GamepadEvent>,
mut gamepads: ResMut<GilrsGamepads>,
mut events: EventWriter<GamepadConnectionEvent>,
) {
for (id, gamepad) in gilrs.0.get().gamepads() {
// Create entity and add to mapping
let entity = commands.spawn_empty().id();
gamepads.id_to_entity.insert(id, entity);
gamepads.entity_to_id.insert(entity, id);

let info = GamepadInfo {
name: gamepad.name().into(),
};

events.send(
GamepadConnectionEvent {
gamepad: convert_gamepad_id(id),
connection: GamepadConnection::Connected(info),
}
.into(),
);
events.send(GamepadConnectionEvent {
gamepad: entity,
connection: GamepadConnection::Connected(info),
});
}
}

pub fn gilrs_event_system(
mut commands: Commands,
#[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
mut events: EventWriter<GamepadEvent>,
mut gamepad_buttons: ResMut<Axis<GamepadButton>>,
gamepad_axis: Res<Axis<GamepadAxis>>,
gamepad_settings: Res<GamepadSettings>,
mut gamepads: ResMut<GilrsGamepads>,
mut events: EventWriter<RawGamepadEvent>,
mut connection_events: EventWriter<GamepadConnectionEvent>,
mut button_events: EventWriter<RawGamepadButtonChangedEvent>,
mut axis_event: EventWriter<RawGamepadAxisChangedEvent>,
) {
let gilrs = gilrs.0.get();
while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) {
gilrs.update(&gilrs_event);

let gamepad = convert_gamepad_id(gilrs_event.id);
match gilrs_event.event {
EventType::Connected => {
let pad = gilrs.gamepad(gilrs_event.id);
let entity = gamepads.get_entity(gilrs_event.id).unwrap_or_else(|| {
let entity = commands.spawn_empty().id();
gamepads.id_to_entity.insert(gilrs_event.id, entity);
gamepads.entity_to_id.insert(entity, gilrs_event.id);
entity
});

let info = GamepadInfo {
name: pad.name().into(),
};

events.send(
GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)).into(),
GamepadConnectionEvent::new(entity, GamepadConnection::Connected(info.clone()))
.into(),
);
connection_events.send(GamepadConnectionEvent::new(
entity,
GamepadConnection::Connected(info),
));
}
EventType::Disconnected => {
events.send(
GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into(),
);
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
let event = GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected);
events.send(event.clone().into());
connection_events.send(event);
}
EventType::ButtonChanged(gilrs_button, raw_value, _) => {
if let Some(button_type) = convert_button(gilrs_button) {
let button = GamepadButton::new(gamepad, button_type);
let old_value = gamepad_buttons.get(button);
let button_settings = gamepad_settings.get_button_axis_settings(button);

// Only send events that pass the user-defined change threshold
if let Some(filtered_value) = button_settings.filter(raw_value, old_value) {
events.send(
GamepadButtonChangedEvent::new(gamepad, button_type, filtered_value)
.into(),
);
// Update the current value prematurely so that `old_value` is correct in
// future iterations of the loop.
gamepad_buttons.set(button, filtered_value);
}
}
let Some(button) = convert_button(gilrs_button) else {
continue;
};
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
events.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into());
button_events.send(RawGamepadButtonChangedEvent::new(
gamepad, button, raw_value,
));
}
EventType::AxisChanged(gilrs_axis, raw_value, _) => {
if let Some(axis_type) = convert_axis(gilrs_axis) {
let axis = GamepadAxis::new(gamepad, axis_type);
let old_value = gamepad_axis.get(axis);
let axis_settings = gamepad_settings.get_axis_settings(axis);

// Only send events that pass the user-defined change threshold
if let Some(filtered_value) = axis_settings.filter(raw_value, old_value) {
events.send(
GamepadAxisChangedEvent::new(gamepad, axis_type, filtered_value).into(),
);
}
}
let Some(axis) = convert_axis(gilrs_axis) else {
continue;
};
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
events.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into());
axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value));
}
_ => (),
};
Expand Down
26 changes: 24 additions & 2 deletions crates/bevy_gilrs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,38 @@ mod gilrs_system;
mod rumble;

use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate};
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_input::InputSystem;
use bevy_utils::{synccell::SyncCell, tracing::error};
use bevy_utils::{synccell::SyncCell, tracing::error, HashMap};
use gilrs::GilrsBuilder;
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
use rumble::{play_gilrs_rumble, RunningRumbleEffects};

#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))]
pub(crate) struct Gilrs(pub SyncCell<gilrs::Gilrs>);

/// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`].
#[derive(Debug, Default, Resource)]
pub(crate) struct GilrsGamepads {
/// Mapping of [`Entity`] to [`gilrs::GamepadId`].
pub(crate) entity_to_id: EntityHashMap<gilrs::GamepadId>,
/// Mapping of [`gilrs::GamepadId`] to [`Entity`].
pub(crate) id_to_entity: HashMap<gilrs::GamepadId, Entity>,
}

impl GilrsGamepads {
/// Returns the [`Entity`] assigned to a connected [`gilrs::GamepadId`].
pub fn get_entity(&self, gamepad_id: gilrs::GamepadId) -> Option<Entity> {
self.id_to_entity.get(&gamepad_id).copied()
}

/// Returns the [`gilrs::GamepadId`] assigned to a gamepad [`Entity`].
pub fn get_gamepad_id(&self, entity: Entity) -> Option<gilrs::GamepadId> {
self.entity_to_id.get(&entity).copied()
}
}

/// Plugin that provides gamepad handling to an [`App`].
#[derive(Default)]
pub struct GilrsPlugin;
Expand All @@ -45,7 +67,7 @@ impl Plugin for GilrsPlugin {
app.insert_non_send_resource(Gilrs(SyncCell::new(gilrs)));
#[cfg(not(target_arch = "wasm32"))]
app.insert_resource(Gilrs(SyncCell::new(gilrs)));

app.init_resource::<GilrsGamepads>();
app.init_resource::<RunningRumbleEffects>()
.add_systems(PreStartup, gilrs_event_startup_system)
.add_systems(PreUpdate, gilrs_event_system.before(InputSystem))
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_gilrs/src/rumble.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Handle user specified rumble request events.
use crate::Gilrs;
use crate::{Gilrs, GilrsGamepads};
use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource};
#[cfg(target_arch = "wasm32")]
use bevy_ecs::system::NonSendMut;
Expand All @@ -16,8 +16,6 @@ use gilrs::{
};
use thiserror::Error;

use crate::converter::convert_gamepad_id;

/// A rumble effect that is currently in effect.
struct RunningRumble {
/// Duration from app startup when this effect will be finished
Expand Down Expand Up @@ -84,14 +82,15 @@ fn get_base_effects(
fn handle_rumble_request(
running_rumbles: &mut RunningRumbleEffects,
gilrs: &mut gilrs::Gilrs,
gamepads: &GilrsGamepads,
rumble: GamepadRumbleRequest,
current_time: Duration,
) -> Result<(), RumbleError> {
let gamepad = rumble.gamepad();

let (gamepad_id, _) = gilrs
.gamepads()
.find(|(pad_id, _)| convert_gamepad_id(*pad_id) == gamepad)
.find(|(pad_id, _)| *pad_id == gamepads.get_gamepad_id(gamepad).unwrap())
.ok_or(RumbleError::GamepadNotFound)?;

match rumble {
Expand Down Expand Up @@ -129,6 +128,7 @@ pub(crate) fn play_gilrs_rumble(
time: Res<Time<Real>>,
#[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
gamepads: Res<GilrsGamepads>,
mut requests: EventReader<GamepadRumbleRequest>,
mut running_rumbles: ResMut<RunningRumbleEffects>,
) {
Expand All @@ -146,7 +146,7 @@ pub(crate) fn play_gilrs_rumble(
// Add new effects.
for rumble in requests.read().cloned() {
let gamepad = rumble.gamepad();
match handle_rumble_request(&mut running_rumbles, gilrs, rumble, current_time) {
match handle_rumble_request(&mut running_rumbles, gilrs, &gamepads, rumble, current_time) {
Ok(()) => {}
Err(RumbleError::GilrsError(err)) => {
if let ff::Error::FfNotSupported(_) = err {
Expand Down
Loading