From 0707bdc28cbd2c800e4764b3361c9a046c78869d Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Sat, 5 Feb 2022 10:57:45 +0100 Subject: [PATCH 1/2] Add rumble support to bevy_gilrs This adds the `RumbleRequest` event and a system to read them and rumble controllers accordingly. It gives users two ways of controlling controller rumble: 1. A very primitive API with `RumbleIntensity` that is easy to understand and use. 2. A direct access to the girls `ff::Effect` system for complete fine-grained control over how the gamepad rumbles. --- Cargo.toml | 4 + crates/bevy_gilrs/Cargo.toml | 2 + crates/bevy_gilrs/src/lib.rs | 6 ++ crates/bevy_gilrs/src/rumble.rs | 155 +++++++++++++++++++++++++++++++ examples/README.md | 1 + examples/input/gamepad_rumble.rs | 70 ++++++++++++++ 6 files changed, 238 insertions(+) create mode 100644 crates/bevy_gilrs/src/rumble.rs create mode 100644 examples/input/gamepad_rumble.rs diff --git a/Cargo.toml b/Cargo.toml index 22e4005cb2c8b..0b8f9fcd8985a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -389,6 +389,10 @@ path = "examples/input/gamepad_input.rs" name = "gamepad_input_events" path = "examples/input/gamepad_input_events.rs" +[[example]] +name = "gamepad_rumble" +path = "examples/input/gamepad_rumble.rs" + [[example]] name = "keyboard_input" path = "examples/input/keyboard_input.rs" diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index 20e2f83451c87..a89afb62d3b4a 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -11,8 +11,10 @@ keywords = ["bevy"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.6.0" } +bevy_core = { path = "../bevy_core", version = "0.6.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.6.0" } bevy_input = { path = "../bevy_input", version = "0.6.0" } +bevy_log = { path = "../bevy_log", version = "0.6.0" } bevy_utils = { path = "../bevy_utils", version = "0.6.0" } # other diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 37fa70a83316a..a139ef5ef1ab4 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -1,12 +1,15 @@ mod converter; mod gilrs_system; +mod rumble; use bevy_app::{App, CoreStage, Plugin, StartupStage}; use bevy_ecs::schedule::ParallelSystemDescriptorCoercion; use bevy_input::InputSystem; use bevy_utils::tracing::error; +pub use gilrs::ff; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; +pub use rumble::{RumbleIntensity, RumbleRequest}; #[derive(Default)] pub struct GilrsPlugin; @@ -20,10 +23,13 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { app.insert_non_send_resource(gilrs) + .add_event::() + .init_non_send_resource::() .add_startup_system_to_stage( StartupStage::PreStartup, gilrs_event_startup_system, ) + .add_system_to_stage(CoreStage::PostUpdate, rumble::gilrs_rumble_system) .add_system_to_stage( CoreStage::PreUpdate, gilrs_event_system.before(InputSystem), diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs new file mode 100644 index 0000000000000..41fcb81831af1 --- /dev/null +++ b/crates/bevy_gilrs/src/rumble.rs @@ -0,0 +1,155 @@ +//! Handle user specified Rumble request events. +use crate::converter::convert_gamepad_id; +use bevy_app::EventReader; +use bevy_core::Time; +use bevy_ecs::{prelude::Res, system::NonSendMut}; +use bevy_input::gamepad::Gamepad; +use bevy_log as log; +use bevy_utils::HashMap; +use gilrs::{ff, GamepadId, Gilrs}; + +pub enum RumbleIntensity { + Strong, + Medium, + Weak, +} +impl RumbleIntensity { + fn effect_type(&self) -> ff::BaseEffectType { + use RumbleIntensity::*; + match self { + Strong => ff::BaseEffectType::Strong { magnitude: 63_000 }, + Medium => ff::BaseEffectType::Strong { magnitude: 40_000 }, + Weak => ff::BaseEffectType::Weak { magnitude: 40_000 }, + } + } +} + +/// Request `pad` rumble in `gilrs_effect` pattern for `duration_seconds` +/// +/// # Notes +/// +/// * Does nothing if `pad` does not support rumble +/// * If a new `RumbleRequest` is sent while another one is still executing, it +/// replaces the old one. +/// +/// # Example +/// +/// ``` +/// # use bevy_gilrs::{RumbleRequest, RumbleIntensity}; +/// # use bevy_input::gamepad::Gamepad; +/// # use bevy_app::EventWriter; +/// fn rumble_pad_system(mut rumble_requests: EventWriter) { +/// let request = RumbleRequest::with_intensity( +/// RumbleIntensity::Strong, +/// 10.0, +/// Gamepad(0), +/// ); +/// rumble_requests.send(request); +/// } +/// ``` +#[derive(Clone)] +pub struct RumbleRequest { + /// The duration in seconds of the rumble + pub duration_seconds: f32, + /// The gilrs descriptor, use [`RumbleRequest::with_intensity`] if you want + /// a simpler API. + pub gilrs_effect: ff::EffectBuilder, + /// The gamepad to rumble + pub pad: Gamepad, +} +impl RumbleRequest { + /// Causes `pad` to rumble for `duration_seconds` at given `intensity`. + pub fn with_intensity(intensity: RumbleIntensity, duration_seconds: f32, pad: Gamepad) -> Self { + let kind = intensity.effect_type(); + let effect = ff::BaseEffect { + kind, + ..Default::default() + }; + let mut gilrs_effect = ff::EffectBuilder::new(); + gilrs_effect.add_effect(effect); + RumbleRequest { + duration_seconds, + gilrs_effect, + pad, + } + } + /// Stops provided `pad` rumbling. + pub fn stop(pad: Gamepad) -> Self { + RumbleRequest { + duration_seconds: 0.0, + gilrs_effect: ff::EffectBuilder::new(), + pad, + } + } +} + +struct RunningRumble { + deadline: f32, + // We use `effect.drop()` to interact with this, but rustc can't know + // gilrs uses Drop as an API feature. + #[allow(dead_code)] + effect: ff::Effect, +} + +#[derive(Default)] +pub(crate) struct RumblesManager { + rumbles: HashMap, +} + +pub(crate) fn gilrs_rumble_system( + time: Res