diff --git a/Cargo.toml b/Cargo.toml index cb5fbf4d5478a0..192d8264eadabc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ bevy_audio = { path = "crates/bevy_audio", optional = true, version = "0.1" } bevy_gltf = { path = "crates/bevy_gltf", optional = true, version = "0.1" } bevy_wgpu = { path = "crates/bevy_wgpu", optional = true, version = "0.1" } bevy_winit = { path = "crates/bevy_winit", optional = true, version = "0.1" } +bevy_gilrs = { path = "crates/bevy_gilrs", version = "0.1" } [dev-dependencies] rand = "0.7.2" @@ -179,6 +180,10 @@ path = "examples/input/keyboard_input.rs" name = "keyboard_input_events" path = "examples/input/keyboard_input_events.rs" +[[example]] +name = "gamepad_input" +path = "examples/input/gamepad_input.rs" + [[example]] name = "scene" path = "examples/scene/scene.rs" diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml new file mode 100644 index 00000000000000..3c6f74c2356b8f --- /dev/null +++ b/crates/bevy_gilrs/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "bevy_gilrs" +version = "0.1.0" +edition = "2018" +authors = ["Bevy Contributors ", "Carter Anderson "] +description = "Gamepad system made using Gilrs for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT" +keywords = ["bevy"] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.1" } +bevy_ecs = { path = "../bevy_ecs", version = "0.1" } +bevy_input = { path = "../bevy_input", version = "0.1" } + +# other +gilrs = "0.7.4" +log = { version = "0.4", features = ["release_max_level_info"] } \ No newline at end of file diff --git a/crates/bevy_gilrs/src/converter.rs b/crates/bevy_gilrs/src/converter.rs new file mode 100644 index 00000000000000..83756141d50152 --- /dev/null +++ b/crates/bevy_gilrs/src/converter.rs @@ -0,0 +1,46 @@ +use bevy_input::gamepad::{AxisCode, ButtonCode, Gamepad}; + +pub fn convert_gamepad_id(gamepad_id: gilrs::GamepadId) -> Gamepad { + Gamepad { + id: gamepad_id.into(), + } +} + +pub fn convert_button(button: gilrs::Button) -> Option { + match button { + gilrs::Button::South => Some(ButtonCode::South), + gilrs::Button::East => Some(ButtonCode::East), + gilrs::Button::North => Some(ButtonCode::North), + gilrs::Button::West => Some(ButtonCode::West), + gilrs::Button::C => Some(ButtonCode::C), + gilrs::Button::Z => Some(ButtonCode::Z), + gilrs::Button::LeftTrigger => Some(ButtonCode::LeftTrigger), + gilrs::Button::LeftTrigger2 => Some(ButtonCode::LeftTrigger2), + gilrs::Button::RightTrigger => Some(ButtonCode::RightTrigger), + gilrs::Button::RightTrigger2 => Some(ButtonCode::RightTrigger2), + gilrs::Button::Select => Some(ButtonCode::Select), + gilrs::Button::Start => Some(ButtonCode::Start), + gilrs::Button::Mode => Some(ButtonCode::Mode), + gilrs::Button::LeftThumb => Some(ButtonCode::LeftThumb), + gilrs::Button::RightThumb => Some(ButtonCode::RightThumb), + gilrs::Button::DPadUp => Some(ButtonCode::DPadUp), + gilrs::Button::DPadDown => Some(ButtonCode::DPadDown), + gilrs::Button::DPadLeft => Some(ButtonCode::DPadLeft), + gilrs::Button::DPadRight => Some(ButtonCode::DPadRight), + gilrs::Button::Unknown => None, + } +} + +pub fn convert_axis(axis: gilrs::Axis) -> Option { + match axis { + gilrs::Axis::LeftStickX => Some(AxisCode::LeftStickX), + gilrs::Axis::LeftStickY => Some(AxisCode::LeftStickY), + gilrs::Axis::LeftZ => Some(AxisCode::LeftZ), + gilrs::Axis::RightStickX => Some(AxisCode::RightStickX), + gilrs::Axis::RightStickY => Some(AxisCode::RightStickY), + gilrs::Axis::RightZ => Some(AxisCode::RightZ), + gilrs::Axis::DPadX => Some(AxisCode::DPadX), + gilrs::Axis::DPadY => Some(AxisCode::DPadY), + gilrs::Axis::Unknown => None, + } +} diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs new file mode 100644 index 00000000000000..961254d4a1f3fe --- /dev/null +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -0,0 +1,187 @@ +use crate::converter::{convert_axis, convert_button, convert_gamepad_id}; +use bevy_app::Events; +use bevy_ecs::{Res, ResMut}; +use bevy_input::prelude::*; +use gilrs::{Button, EventType, Gilrs}; +use std::sync::{Arc, Mutex}; + +struct GilrsSendWrapper { + gilrs: Gilrs, +} + +unsafe impl Send for GilrsSendWrapper {} + +pub struct GilrsArcMutexWrapper { + gilrs: Arc>, +} + +impl GilrsArcMutexWrapper { + pub fn new(gilrs: Gilrs) -> GilrsArcMutexWrapper { + GilrsArcMutexWrapper { + gilrs: Arc::new(Mutex::new(GilrsSendWrapper { gilrs })), + } + } +} + +pub fn gilrs_startup_system( + gilrs: Res, + mut gamepad_event: ResMut>, + mut inputs: ResMut>, + mut axes: ResMut>, +) { + gamepad_event.update(); + inputs.update(); + let gilrs = &gilrs.gilrs.lock().unwrap().gilrs; + for (gilrs_id, gilrs_gamepad) in gilrs.gamepads() { + connect_gamepad( + gilrs_gamepad, + convert_gamepad_id(gilrs_id), + &mut gamepad_event, + &mut inputs, + &mut axes, + ); + } +} + +pub fn gilrs_update_system( + gilrs: Res, + mut gamepad_event: ResMut>, + mut inputs: ResMut>, + mut axes: ResMut>, +) { + gamepad_event.update(); + inputs.update(); + let gilrs = &mut gilrs.gilrs.lock().unwrap().gilrs; + while let Some(gilrs_event) = gilrs.next_event() { + match gilrs_event.event { + EventType::Connected => { + connect_gamepad( + gilrs.gamepad(gilrs_event.id), + convert_gamepad_id(gilrs_event.id), + &mut gamepad_event, + &mut inputs, + &mut axes, + ); + } + EventType::Disconnected => { + disconnect_gamepad( + convert_gamepad_id(gilrs_event.id), + &mut gamepad_event, + &mut inputs, + &mut axes, + ); + } + EventType::ButtonPressed(gilrs_button, _) => { + if let Some(button) = convert_button(gilrs_button) { + inputs.press(GamepadButton::new( + convert_gamepad_id(gilrs_event.id), + button, + )); + } + } + EventType::ButtonReleased(gilrs_button, _) => { + if let Some(button) = convert_button(gilrs_button) { + inputs.release(GamepadButton::new( + convert_gamepad_id(gilrs_event.id), + button, + )); + } + } + EventType::AxisChanged(gilrs_axis, value, _) => { + if let Some(axis) = convert_axis(gilrs_axis) { + axes.set( + GamepadAxis::new(convert_gamepad_id(gilrs_event.id), axis), + value, + ); + } + } + _ => (), + }; + } + gilrs.inc(); +} + +const ALL_GILRS_BUTTONS: [Button; 19] = [ + Button::South, + Button::East, + Button::North, + Button::West, + Button::C, + Button::Z, + Button::LeftTrigger, + Button::LeftTrigger2, + Button::RightTrigger, + Button::RightTrigger2, + Button::Select, + Button::Start, + Button::Mode, + Button::LeftThumb, + Button::RightThumb, + Button::DPadUp, + Button::DPadDown, + Button::DPadLeft, + Button::DPadRight, +]; + +const ALL_GILRS_AXES: [gilrs::Axis; 8] = [ + gilrs::Axis::LeftStickX, + gilrs::Axis::LeftStickY, + gilrs::Axis::LeftZ, + gilrs::Axis::RightStickX, + gilrs::Axis::RightStickY, + gilrs::Axis::RightZ, + gilrs::Axis::DPadX, + gilrs::Axis::DPadY, +]; + +fn connect_gamepad( + gilrs_gamepad: gilrs::Gamepad, + gamepad: Gamepad, + events: &mut Events, + inputs: &mut Input, + axes: &mut Axis, +) { + for gilrs_button in ALL_GILRS_BUTTONS.iter() { + if let Some(converted_button) = convert_button(*gilrs_button) { + let gamepad_button = GamepadButton::new(gamepad, converted_button); + inputs.reset(gamepad_button); + if gilrs_gamepad.is_pressed(*gilrs_button) { + inputs.press(gamepad_button); + } + } + } + for gilrs_axis in ALL_GILRS_AXES.iter() { + if let Some(converted_axis) = convert_axis(*gilrs_axis) { + let gamepad_axis = GamepadAxis::new(gamepad, converted_axis); + axes.set(gamepad_axis, gilrs_gamepad.value(*gilrs_axis)); + } + } + events.send(GamepadEvent { + gamepad, + event_type: GamepadEventType::Connected, + }); +} + +fn disconnect_gamepad( + gamepad: Gamepad, + events: &mut Events, + inputs: &mut Input, + axes: &mut Axis, +) { + for gilrs_button in ALL_GILRS_BUTTONS.iter() { + if let Some(converted_button) = convert_button(*gilrs_button) { + let gamepad_button = GamepadButton::new(gamepad, converted_button); + inputs.reset(gamepad_button); + } + } + for gilrs_axis in ALL_GILRS_AXES.iter() { + if let Some(converted_axis) = convert_axis(*gilrs_axis) { + let gamepad_axis = GamepadAxis::new(gamepad, converted_axis); + axes.remove(&gamepad_axis); + } + } + events.send(GamepadEvent { + gamepad, + event_type: GamepadEventType::Disconnected, + }); +} diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs new file mode 100644 index 00000000000000..af9657f2d95ae1 --- /dev/null +++ b/crates/bevy_gilrs/src/lib.rs @@ -0,0 +1,22 @@ +mod converter; +mod gilrs_system; + +use bevy_app::prelude::*; +use bevy_ecs::IntoQuerySystem; +use gilrs_system::{gilrs_startup_system, gilrs_update_system, GilrsArcMutexWrapper}; + +#[derive(Default)] +pub struct GilrsPlugin; + +impl Plugin for GilrsPlugin { + fn build(&self, app: &mut AppBuilder) { + match gilrs::Gilrs::new() { + Ok(gilrs) => { + app.add_resource(GilrsArcMutexWrapper::new(gilrs)) + .add_startup_system(gilrs_startup_system.system()) + .add_system_to_stage(stage::EVENT_UPDATE, gilrs_update_system.system()); + } + Err(err) => log::error!("Failed to start Gilrs. {}", err), + } + } +} diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs new file mode 100644 index 00000000000000..6d8937cf7be710 --- /dev/null +++ b/crates/bevy_input/src/axis.rs @@ -0,0 +1,33 @@ +use std::{collections::HashMap, hash::Hash}; + +pub struct Axis { + axis_data: HashMap, +} + +impl Default for Axis +where + T: Copy + Eq + Hash, +{ + fn default() -> Self { + Axis { + axis_data: HashMap::new(), + } + } +} + +impl Axis +where + T: Copy + Eq + Hash, +{ + pub fn set(&mut self, axis: T, value: f32) -> Option { + self.axis_data.insert(axis, value) + } + + pub fn get(&self, axis: &T) -> Option { + self.axis_data.get(axis).copied() + } + + pub fn remove(&mut self, axis: &T) -> Option { + self.axis_data.remove(axis) + } +} diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs new file mode 100644 index 00000000000000..8430f3472256ca --- /dev/null +++ b/crates/bevy_input/src/gamepad.rs @@ -0,0 +1,75 @@ +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Gamepad { + pub id: usize, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum GamepadEventType { + Connected, + Disconnected, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct GamepadEvent { + pub gamepad: Gamepad, + pub event_type: GamepadEventType, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum ButtonCode { + South, + East, + North, + West, + C, + Z, + LeftTrigger, + LeftTrigger2, + RightTrigger, + RightTrigger2, + Select, + Start, + Mode, + LeftThumb, + RightThumb, + DPadUp, + DPadDown, + DPadLeft, + DPadRight, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct GamepadButton { + pub gamepad: Gamepad, + pub code: ButtonCode, +} + +impl GamepadButton { + pub fn new(gamepad: Gamepad, code: ButtonCode) -> GamepadButton { + GamepadButton { gamepad, code } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum AxisCode { + LeftStickX, + LeftStickY, + LeftZ, + RightStickX, + RightStickY, + RightZ, + DPadX, + DPadY, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct GamepadAxis { + pub gamepad: Gamepad, + pub code: AxisCode, +} + +impl GamepadAxis { + pub fn new(gamepad: Gamepad, code: AxisCode) -> GamepadAxis { + GamepadAxis { gamepad, code } + } +} diff --git a/crates/bevy_input/src/input.rs b/crates/bevy_input/src/input.rs index 9065585e4cb6c4..cfccba4626f9e8 100644 --- a/crates/bevy_input/src/input.rs +++ b/crates/bevy_input/src/input.rs @@ -46,6 +46,12 @@ where self.just_released.contains(&input) } + pub fn reset(&mut self, input: T) { + self.pressed.remove(&input); + self.just_pressed.remove(&input); + self.just_released.remove(&input); + } + pub fn update(&mut self) { self.just_pressed.clear(); self.just_released.clear(); diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index fdfe65e17d3321..e97d3ea3b79e25 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -1,12 +1,23 @@ +mod axis; +pub mod gamepad; mod input; pub mod keyboard; pub mod mouse; pub mod system; +pub use axis::*; pub use input::*; pub mod prelude { - pub use crate::{keyboard::KeyCode, mouse::MouseButton, Input}; + pub use crate::{ + gamepad::{ + AxisCode, ButtonCode, Gamepad, GamepadAxis, GamepadButton, GamepadEvent, + GamepadEventType, + }, + keyboard::KeyCode, + mouse::MouseButton, + Axis, Input, + }; } use bevy_app::prelude::*; @@ -14,6 +25,7 @@ use keyboard::{keyboard_input_system, KeyCode, KeyboardInput}; use mouse::{mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion}; use bevy_ecs::IntoQuerySystem; +use gamepad::{GamepadAxis, GamepadButton, GamepadEvent}; /// Adds keyboard and mouse input to an App #[derive(Default)] @@ -33,6 +45,9 @@ impl Plugin for InputPlugin { .add_system_to_stage( bevy_app::stage::EVENT_UPDATE, mouse_button_input_system.system(), - ); + ) + .add_event::() + .init_resource::>() + .init_resource::>(); } } diff --git a/examples/input/gamepad_input.rs b/examples/input/gamepad_input.rs new file mode 100644 index 00000000000000..041568ed3409f6 --- /dev/null +++ b/examples/input/gamepad_input.rs @@ -0,0 +1,99 @@ +use bevy::prelude::*; +use bevy_gilrs::GilrsPlugin; +use bevy_input::gamepad::{Gamepad, GamepadButton, GamepadEvent, GamepadEventType}; +use std::collections::HashSet; + +fn main() { + App::build() + .add_default_plugins() + .add_plugin(GilrsPlugin::default()) + .add_startup_system(connection_system.system()) + .add_system(connection_system.system()) + .add_system(button_system.system()) + .add_system(axis_system.system()) + .add_resource(Lobby::default()) + .run(); +} + +#[derive(Default)] +struct Lobby { + gamepad: HashSet, + gamepad_event_reader: EventReader, +} + +fn connection_system(mut lobby: ResMut, gamepad_event: Res>) { + for event in lobby.gamepad_event_reader.iter(&gamepad_event) { + match event.event_type { + GamepadEventType::Connected => { + lobby.gamepad.insert(event.gamepad); + println!("Connected {:?}", event.gamepad); + } + GamepadEventType::Disconnected => { + lobby.gamepad.remove(&event.gamepad); + println!("Disconnected {:?}", event.gamepad); + } + } + } +} + +fn button_system(manager: Res, inputs: Res>) { + let button_codes = [ + ButtonCode::South, + ButtonCode::East, + ButtonCode::North, + ButtonCode::West, + ButtonCode::C, + ButtonCode::Z, + ButtonCode::LeftTrigger, + ButtonCode::LeftTrigger2, + ButtonCode::RightTrigger, + ButtonCode::RightTrigger2, + ButtonCode::Select, + ButtonCode::Start, + ButtonCode::Mode, + ButtonCode::LeftThumb, + ButtonCode::RightThumb, + ButtonCode::DPadUp, + ButtonCode::DPadDown, + ButtonCode::DPadLeft, + ButtonCode::DPadRight, + ]; + for gamepad in manager.gamepad.iter() { + for button_code in button_codes.iter() { + if inputs.just_pressed(GamepadButton::new(*gamepad, *button_code)) { + println!("Pressed {:?}", GamepadButton::new(*gamepad, *button_code)); + } else if inputs.just_released(GamepadButton::new(*gamepad, *button_code)) { + println!("Released {:?}", GamepadButton::new(*gamepad, *button_code)); + } + } + } +} + +fn axis_system(manager: Res, axes: Res>) { + let axis_codes = [ + AxisCode::LeftStickX, + AxisCode::LeftStickY, + AxisCode::LeftZ, + AxisCode::RightStickX, + AxisCode::RightStickY, + AxisCode::RightZ, + AxisCode::DPadX, + AxisCode::DPadY, + ]; + for gamepad in manager.gamepad.iter() { + for axis_code in axis_codes.iter() { + if let Some(value) = axes.get(&GamepadAxis::new(*gamepad, *axis_code)) { + if value.abs() > 0.01f32 + && (value - 1.0f32).abs() > 0.01f32 + && (value + 1.0f32).abs() > 0.01f32 + { + println!( + "Axis {:?} is {}", + GamepadAxis::new(*gamepad, *axis_code), + value + ); + } + } + } + } +}