Skip to content

Commit

Permalink
Added gamepad support using Gilrs
Browse files Browse the repository at this point in the history
  • Loading branch information
simpuid committed Aug 21, 2020
1 parent 1ebb7e4 commit 2114a18
Show file tree
Hide file tree
Showing 11 changed files with 513 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- name: Install alsa
run: sudo apt-get install libasound2-dev

- name: Install libudev
run: sudo apt-get install libudev-dev

- name: Build
run: cargo check

Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
20 changes: 20 additions & 0 deletions crates/bevy_gilrs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "bevy_gilrs"
version = "0.1.0"
edition = "2018"
authors = ["Bevy Contributors <[email protected]>", "Carter Anderson <[email protected]>"]
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"] }
46 changes: 46 additions & 0 deletions crates/bevy_gilrs/src/converter.rs
Original file line number Diff line number Diff line change
@@ -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<ButtonCode> {
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<AxisCode> {
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,
}
}
187 changes: 187 additions & 0 deletions crates/bevy_gilrs/src/gilrs_system.rs
Original file line number Diff line number Diff line change
@@ -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<Mutex<GilrsSendWrapper>>,
}

impl GilrsArcMutexWrapper {
pub fn new(gilrs: Gilrs) -> GilrsArcMutexWrapper {
GilrsArcMutexWrapper {
gilrs: Arc::new(Mutex::new(GilrsSendWrapper { gilrs })),
}
}
}

pub fn gilrs_startup_system(
gilrs: Res<GilrsArcMutexWrapper>,
mut gamepad_event: ResMut<Events<GamepadEvent>>,
mut inputs: ResMut<Input<GamepadButton>>,
mut axes: ResMut<Axis<GamepadAxis>>,
) {
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<GilrsArcMutexWrapper>,
mut gamepad_event: ResMut<Events<GamepadEvent>>,
mut inputs: ResMut<Input<GamepadButton>>,
mut axes: ResMut<Axis<GamepadAxis>>,
) {
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<GamepadEvent>,
inputs: &mut Input<GamepadButton>,
axes: &mut Axis<GamepadAxis>,
) {
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<GamepadEvent>,
inputs: &mut Input<GamepadButton>,
axes: &mut Axis<GamepadAxis>,
) {
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,
});
}
22 changes: 22 additions & 0 deletions crates/bevy_gilrs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
}
33 changes: 33 additions & 0 deletions crates/bevy_input/src/axis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::{collections::HashMap, hash::Hash};

pub struct Axis<T> {
axis_data: HashMap<T, f32>,
}

impl<T> Default for Axis<T>
where
T: Copy + Eq + Hash,
{
fn default() -> Self {
Axis {
axis_data: HashMap::new(),
}
}
}

impl<T> Axis<T>
where
T: Copy + Eq + Hash,
{
pub fn set(&mut self, axis: T, value: f32) -> Option<f32> {
self.axis_data.insert(axis, value)
}

pub fn get(&self, axis: &T) -> Option<f32> {
self.axis_data.get(axis).copied()
}

pub fn remove(&mut self, axis: &T) -> Option<f32> {
self.axis_data.remove(axis)
}
}
Loading

0 comments on commit 2114a18

Please sign in to comment.