diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index d0bb67c5c28c6..de1d0afb3c733 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,30 +1,45 @@ use crate::converter::{convert_axis, convert_button, convert_gamepad_id}; use bevy_ecs::event::EventWriter; -use bevy_ecs::system::{NonSend, NonSendMut}; -use bevy_input::gamepad::GamepadInfo; -use bevy_input::{gamepad::GamepadEventRaw, prelude::*}; +use bevy_ecs::system::{NonSend, NonSendMut, Res}; +use bevy_input::gamepad::{ + GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection, GamepadConnectionEvent, + GamepadSettings, +}; +use bevy_input::gamepad::{GamepadEvent, GamepadInfo}; +use bevy_input::prelude::{GamepadAxis, GamepadButton}; +use bevy_input::Axis; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs}; -pub fn gilrs_event_startup_system(gilrs: NonSend, mut events: EventWriter) { +pub fn gilrs_event_startup_system( + gilrs: NonSend, + mut connection_events: EventWriter, +) { for (id, gamepad) in gilrs.gamepads() { let info = GamepadInfo { name: gamepad.name().into(), }; - events.send(GamepadEventRaw::new( - convert_gamepad_id(id), - GamepadEventType::Connected(info), - )); + connection_events.send(GamepadConnectionEvent { + gamepad: convert_gamepad_id(id), + connection: GamepadConnection::Connected(info), + }); } } -pub fn gilrs_event_system(mut gilrs: NonSendMut, mut events: EventWriter) { +pub fn gilrs_event_system( + mut gilrs: NonSendMut, + mut events: EventWriter, + gamepad_axis: Res>, + gamepad_buttons: Res>, + gamepad_settings: Res, +) { while let Some(gilrs_event) = gilrs .next_event() .filter_ev(&axis_dpad_to_button, &mut 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); @@ -32,31 +47,39 @@ pub fn gilrs_event_system(mut gilrs: NonSendMut, mut events: EventWriter< name: pad.name().into(), }; - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::Connected(info), - )); + events.send( + GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)).into(), + ); } - EventType::Disconnected => { - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::Disconnected, - )); - } - EventType::ButtonChanged(gilrs_button, value, _) => { + EventType::Disconnected => events + .send(GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into()), + EventType::ButtonChanged(gilrs_button, raw_value, _) => { if let Some(button_type) = convert_button(gilrs_button) { - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::ButtonChanged(button_type, value), - )); + 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(), + ); + } } } - EventType::AxisChanged(gilrs_axis, value, _) => { + EventType::AxisChanged(gilrs_axis, raw_value, _) => { if let Some(axis_type) = convert_axis(gilrs_axis) { - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::AxisChanged(axis_type, value), - )); + 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(), + ); + } } } _ => (), diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 2cd56d94301b7..d0f17aa53d339 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -65,8 +65,8 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// ## Usage /// /// The primary way to access the individual connected gamepads is done through the [`Gamepads`] -/// `bevy` resource. It is also used inside of [`GamepadEvent`]s and [`GamepadEventRaw`]s to distinguish -/// which gamepad an event corresponds to. +/// `bevy` resource. It is also used inside of [`GamepadConnectionEvent`]s to correspond a gamepad +/// with a connection event. /// /// ## Note /// @@ -111,8 +111,7 @@ pub struct GamepadInfo { /// ## Updating /// /// The [`Gamepad`]s are registered and deregistered in the [`gamepad_connection_system`] -/// whenever a [`GamepadEventType::Connected`] or [`GamepadEventType::Disconnected`] -/// event is received. +/// whenever a [`GamepadConnectionEvent`] is received. #[derive(Resource, Default, Debug)] pub struct Gamepads { /// The collection of the connected [`Gamepad`]s. @@ -145,184 +144,12 @@ impl Gamepads { } } -/// The data contained in a [`GamepadEvent`] or [`GamepadEventRaw`]. -#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub enum GamepadEventType { - /// A [`Gamepad`] has been connected. - Connected(GamepadInfo), - /// A [`Gamepad`] has been disconnected. - Disconnected, - - /// The value of a [`Gamepad`] button has changed. - ButtonChanged(GamepadButtonType, f32), - /// The value of a [`Gamepad`] axis has changed. - AxisChanged(GamepadAxisType, f32), -} - -/// An event of a [`Gamepad`]. -/// -/// This event is the translated version of the [`GamepadEventRaw`]. It is available to -/// the end user and can be used for game logic. -/// -/// ## Differences -/// -/// The difference between the [`GamepadEventRaw`] and the [`GamepadEvent`] is that the -/// former respects user defined [`GamepadSettings`] for the gamepad inputs when translating it -/// to the latter. The former also updates the [`Input`], [`Axis`], -/// and [`Axis`] resources accordingly. -/// -/// ## Gamepad input mocking -/// -/// When mocking gamepad input, use [`GamepadEventRaw`]s instead of [`GamepadEvent`]s. -/// Otherwise [`GamepadSettings`] won't be respected and the [`Input`], -/// [`Axis`], and [`Axis`] resources won't be updated correctly. -/// -/// An example for gamepad input mocking can be seen in the documentation of the [`GamepadEventRaw`]. -#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub struct GamepadEvent { - /// The gamepad this event corresponds to. - pub gamepad: Gamepad, - /// The type of the event. - pub event_type: GamepadEventType, -} - -impl GamepadEvent { - /// Creates a new [`GamepadEvent`]. - pub fn new(gamepad: Gamepad, event_type: GamepadEventType) -> Self { - Self { - gamepad, - event_type, - } - } -} - -/// A raw event of a [`Gamepad`]. -/// -/// This event is the translated version of the `EventType` from the `GilRs` crate. -/// It is available to the end user and can be used for game logic. -/// -/// ## Differences -/// -/// The difference between the `EventType` from the `GilRs` crate and the [`GamepadEventRaw`] -/// is that the latter has less events, because the button pressing logic is handled through the generic -/// [`Input`] instead of through events. -/// -/// The difference between the [`GamepadEventRaw`] and the [`GamepadEvent`] can be seen in the documentation -/// of the [`GamepadEvent`]. -/// -/// ## Gamepad input mocking -/// -/// The following example showcases how to mock gamepad input by manually sending [`GamepadEventRaw`]s. -/// -/// ``` -/// # use bevy_input::prelude::*; -/// # use bevy_input::InputPlugin; -/// # use bevy_input::gamepad::{GamepadEventRaw, GamepadInfo}; -/// # use bevy_app::prelude::*; -/// # use bevy_ecs::prelude::*; -/// #[derive(Resource)] -/// struct MyResource(bool); -/// -/// // This system sets the bool inside `MyResource` to `true` if the `South` button of the first gamepad is pressed. -/// fn change_resource_on_gamepad_button_press( -/// mut my_resource: ResMut, -/// gamepads: Res, -/// button_inputs: ResMut>, -/// ) { -/// let gamepad = gamepads.iter().next().unwrap(); -/// let gamepad_button = GamepadButton::new(gamepad, GamepadButtonType::South); -/// -/// my_resource.0 = button_inputs.pressed(gamepad_button); -/// } -/// -/// // Create our app. -/// let mut app = App::new(); -/// -/// // Add the input plugin and the system/resource we want to test. -/// app.add_plugin(InputPlugin) -/// .insert_resource(MyResource(false)) -/// .add_system(change_resource_on_gamepad_button_press); -/// -/// // Define our dummy gamepad input data. -/// let gamepad = Gamepad::new(0); -/// let button_type = GamepadButtonType::South; -/// -/// // Send the gamepad connected event to mark our gamepad as connected. -/// // This updates the `Gamepads` resource accordingly. -/// let info = GamepadInfo { name: "Mock Gamepad".into() }; -/// app.world.send_event(GamepadEventRaw::new(gamepad, GamepadEventType::Connected(info))); -/// -/// // Send the gamepad input event to mark the `South` gamepad button as pressed. -/// // This updates the `Input` resource accordingly. -/// app.world.send_event(GamepadEventRaw::new( -/// gamepad, -/// GamepadEventType::ButtonChanged(button_type, 1.0) -/// )); -/// -/// // Advance the execution of the schedule by a single cycle. -/// app.update(); -/// -/// // At this point you can check if your game logic corresponded correctly to the gamepad input. -/// // In this example we are checking if the bool in `MyResource` was updated from `false` to `true`. -/// assert!(app.world.resource::().0); -/// -/// // Send the gamepad input event to mark the `South` gamepad button as released. -/// // This updates the `Input` resource accordingly. -/// app.world.send_event(GamepadEventRaw::new( -/// gamepad, -/// GamepadEventType::ButtonChanged(button_type, 0.0) -/// )); -/// -/// // Advance the execution of the schedule by another cycle. -/// app.update(); -/// -/// // Check if the bool in `MyResource` was updated from `true` to `false`. -/// assert!(!app.world.resource::().0); -/// # -/// # bevy_ecs::system::assert_is_system(change_resource_on_gamepad_button_press); -/// ``` -#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub struct GamepadEventRaw { - /// The gamepad this event corresponds to. - pub gamepad: Gamepad, - /// The type of the event. - pub event_type: GamepadEventType, -} - -impl GamepadEventRaw { - /// Creates a new [`GamepadEventRaw`]. - pub fn new(gamepad: Gamepad, event_type: GamepadEventType) -> Self { - Self { - gamepad, - event_type, - } - } -} - /// A type of a [`GamepadButton`]. /// /// ## Usage /// /// This is used to determine which button has changed its value when receiving a -/// [`GamepadEventType::ButtonChanged`]. It is also used in the [`GamepadButton`] +/// [`GamepadButtonChangedEvent`]. It is also used in the [`GamepadButton`] /// which in turn is used to create the [`Input`] or /// [`Axis`] `bevy` resources. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] @@ -385,11 +212,11 @@ pub enum GamepadButtonType { /// ## Usage /// /// It is used as the generic `T` value of an [`Input`] and [`Axis`] to create `bevy` resources. These -/// resources store the data of the buttons and axes of a gamepad and can be accessed inside of a system. +/// resources store the data of the buttons of a gamepad and can be accessed inside of a system. /// /// ## Updating /// -/// The resources are updated inside of the [`gamepad_event_system`]. +/// The gamepad button resources are updated inside of the [`gamepad_button_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] #[reflect(Debug, Hash, PartialEq)] #[cfg_attr( @@ -430,7 +257,7 @@ impl GamepadButton { /// ## Usage /// /// This is used to determine which axis has changed its value when receiving a -/// [`GamepadEventType::AxisChanged`]. It is also used in the [`GamepadAxis`] +/// [`GamepadAxisChangedEvent`]. It is also used in the [`GamepadAxis`] /// which in turn is used to create the [`Axis`] `bevy` resource. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] #[reflect(Debug, Hash, PartialEq)] @@ -462,12 +289,12 @@ pub enum GamepadAxisType { /// /// ## Usage /// -/// It is used as the generic `T` value of an [`Axis`] to create a `bevy` resource. This resource -/// stores the data of the axes of a gamepad and can be accessed inside of a system. +/// It is used as the generic `T` value of an [`Axis`] to create `bevy` resources. These +/// resources store the data of the axes of a gamepad and can be accessed inside of a system. /// /// ## Updating /// -/// The resource is updated inside of the [`gamepad_event_system`]. +/// The gamepad axes resources are updated inside of the [`gamepad_axis_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] #[reflect(Debug, Hash, PartialEq)] #[cfg_attr( @@ -510,8 +337,9 @@ impl GamepadAxis { /// /// ## Note /// -/// The [`GamepadSettings`] are used inside of the [`gamepad_event_system`], but are never written to -/// inside of `bevy`. To modify these settings, mutate the corresponding resource. +/// The [`GamepadSettings`] are used inside of `bevy_gilrs` to determine when raw gamepad events from `girls`, +/// should register as a [`GamepadEvent`]. Events that don't meet the change thresholds defined in [`GamepadSettings`] +/// will not register. To modify these settings, mutate the corresponding resource. #[derive(Resource, Default, Debug, Reflect, FromReflect)] #[reflect(Debug, Default)] pub struct GamepadSettings { @@ -1032,25 +860,40 @@ impl AxisSettings { self.threshold } - fn filter(&self, new_value: f32, old_value: Option) -> Option { - let new_value = - if self.deadzone_lowerbound <= new_value && new_value <= self.deadzone_upperbound { - 0.0 - } else if new_value >= self.livezone_upperbound { - 1.0 - } else if new_value <= self.livezone_lowerbound { - -1.0 - } else { - new_value - }; - - if let Some(old_value) = old_value { - if (new_value - old_value).abs() <= self.threshold { - return None; - } + /// Clamps the `raw_value` according to the `AxisSettings`. + pub fn clamp(&self, new_value: f32) -> f32 { + if self.deadzone_lowerbound <= new_value && new_value <= self.deadzone_upperbound { + 0.0 + } else if new_value >= self.livezone_upperbound { + 1.0 + } else if new_value <= self.livezone_lowerbound { + -1.0 + } else { + new_value + } + } + + /// Determines whether the change from `old_value` to `new_value` should + /// be registered as a change, according to the `AxisSettings`. + fn should_register_change(&self, new_value: f32, old_value: Option) -> bool { + if old_value.is_none() { + return true; } - Some(new_value) + f32::abs(new_value - old_value.unwrap()) > self.threshold + } + + /// Filters the `new_value` based on the `old_value`, according to the [`AxisSettings`]. + /// + /// Returns the clamped `new_value` if the change exceeds the settings threshold, + /// and `None` otherwise. + pub fn filter(&self, new_value: f32, old_value: Option) -> Option { + let new_value = self.clamp(new_value); + + if self.should_register_change(new_value, old_value) { + return Some(new_value); + } + None } } @@ -1069,7 +912,7 @@ impl AxisSettings { /// /// ## Updating /// -/// The current value of a button is received through the [`GamepadEvent`]s or [`GamepadEventRaw`]s. +/// The current value of a button is received through the [`GamepadButtonChangedEvent`]. #[derive(Debug, Clone, Reflect, FromReflect)] #[reflect(Debug, Default)] pub struct ButtonAxisSettings { @@ -1092,137 +935,275 @@ impl Default for ButtonAxisSettings { } impl ButtonAxisSettings { - /// Filters the `new_value` according to the specified settings. + /// Clamps the `raw_value` according to the specified settings. /// - /// If the `new_value` is: + /// If the `raw_value` is: /// - lower than or equal to `low` it will be rounded to 0.0. /// - higher than or equal to `high` it will be rounded to 1.0. /// - Otherwise it will not be rounded. - /// - /// If the difference between the calculated value and the `old_value` is lower or - /// equal to the `threshold`, [`None`] will be returned. - fn filter(&self, new_value: f32, old_value: Option) -> Option { - let new_value = if new_value <= self.low { - 0.0 - } else if new_value >= self.high { - 1.0 - } else { - new_value - }; + fn clamp(&self, raw_value: f32) -> f32 { + if raw_value <= self.low { + return 0.0; + } + if raw_value >= self.high { + return 1.0; + } - if let Some(old_value) = old_value { - if (new_value - old_value).abs() <= self.threshold { - return None; - } + raw_value + } + + /// Determines whether the change from an `old_value` to a `new_value` should + /// be registered as a change event, according to the specified settings. + fn should_register_change(&self, new_value: f32, old_value: Option) -> bool { + if old_value.is_none() { + return true; } - Some(new_value) + f32::abs(new_value - old_value.unwrap()) > self.threshold + } + + /// Filters the `new_value` based on the `old_value`, according to the `ButtonAxisSettings`. + /// + /// Returns the clamped `new_value`, according to the [`ButtonAxisSettings`], if the change + /// exceeds the settings threshold, and `None` otherwise. + pub fn filter(&self, new_value: f32, old_value: Option) -> Option { + let new_value = self.clamp(new_value); + + if self.should_register_change(new_value, old_value) { + return Some(new_value); + } + None } } -/// Monitors gamepad connection and disconnection events and updates the [`Gamepads`] resource accordingly. +/// Handles [`GamepadConnectionEvent`]s and updates gamepad resources. +/// +/// Updates the [`Gamepads`] resource and resets and/or initializes +/// the [`Axis`] and [`Input`] resources. /// /// ## Note /// /// Whenever a [`Gamepad`] connects or disconnects, an information gets printed to the console using the [`info!`] macro. pub fn gamepad_connection_system( mut gamepads: ResMut, - mut gamepad_event: EventReader, + mut connection_events: EventReader, + mut axis: ResMut>, + mut button_axis: ResMut>, + mut button_input: ResMut>, ) { - for event in gamepad_event.iter() { - match &event.event_type { - GamepadEventType::Connected(info) => { - gamepads.register(event.gamepad, info.clone()); - info!("{:?} Connected", event.gamepad); + button_input.bypass_change_detection().clear(); + for connection_event in connection_events.iter() { + let gamepad = connection_event.gamepad; + + if let GamepadConnection::Connected(info) = &connection_event.connection { + gamepads.register(gamepad, info.clone()); + info!("{:?} Connected", gamepad); + + for button_type in &ALL_BUTTON_TYPES { + let gamepad_button = GamepadButton::new(gamepad, *button_type); + button_input.reset(gamepad_button); + button_axis.set(gamepad_button, 0.0); + } + for axis_type in &ALL_AXIS_TYPES { + axis.set(GamepadAxis::new(gamepad, *axis_type), 0.0); } + } else { + gamepads.deregister(gamepad); + info!("{:?} Disconnected", gamepad); - GamepadEventType::Disconnected => { - gamepads.deregister(event.gamepad); - info!("{:?} Disconnected", event.gamepad); + for button_type in &ALL_BUTTON_TYPES { + let gamepad_button = GamepadButton::new(gamepad, *button_type); + button_input.reset(gamepad_button); + button_axis.remove(gamepad_button); + } + for axis_type in &ALL_AXIS_TYPES { + axis.remove(GamepadAxis::new(gamepad, *axis_type)); } - _ => (), } } } -/// Modifies the gamepad resources and sends out gamepad events. -/// -/// The resources [`Input`], [`Axis`], and [`Axis`] are updated -/// and the [`GamepadEvent`]s are sent according to the received [`GamepadEventRaw`]s respecting the [`GamepadSettings`]. -/// -/// ## Differences -/// -/// The main difference between the events and the resources is that the latter allows you to check specific -/// buttons or axes, rather than reading the events one at a time. This is done through convenient functions -/// like [`Input::pressed`], [`Input::just_pressed`], [`Input::just_released`], and [`Axis::get`]. -pub fn gamepad_event_system( +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub enum GamepadConnection { + Connected(GamepadInfo), + Disconnected, +} + +/// A Gamepad connection event. Created when a connection to a gamepad +/// is established and when a gamepad is disconnected. +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct GamepadConnectionEvent { + /// The gamepad whose connection status changed. + pub gamepad: Gamepad, + /// The change in the gamepads connection. + pub connection: GamepadConnection, +} + +impl GamepadConnectionEvent { + pub fn new(gamepad: Gamepad, connection: GamepadConnection) -> Self { + Self { + gamepad, + connection, + } + } + + pub fn connected(&self) -> bool { + matches!(self.connection, GamepadConnection::Connected(_)) + } + + pub fn disconnected(&self) -> bool { + !self.connected() + } +} + +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct GamepadAxisChangedEvent { + pub gamepad: Gamepad, + pub axis_type: GamepadAxisType, + pub value: f32, +} + +impl GamepadAxisChangedEvent { + pub fn new(gamepad: Gamepad, axis_type: GamepadAxisType, value: f32) -> Self { + Self { + gamepad, + axis_type, + value, + } + } +} + +/// Gamepad event for when the "value" (amount of pressure) on the button +/// changes by an amount larger than the threshold defined in [`GamepadSettings`]. +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct GamepadButtonChangedEvent { + pub gamepad: Gamepad, + pub button_type: GamepadButtonType, + pub value: f32, +} + +impl GamepadButtonChangedEvent { + pub fn new(gamepad: Gamepad, button_type: GamepadButtonType, value: f32) -> Self { + Self { + gamepad, + button_type, + value, + } + } +} + +/// Uses [`GamepadAxisChangedEvent`]s to update update the relevant `Input` and `Axis` values. +pub fn gamepad_axis_event_system( + mut button_input: ResMut>, + mut gamepad_axis: ResMut>, + mut axis_events: EventReader, +) { + button_input.bypass_change_detection().clear(); + for axis_event in axis_events.iter() { + let axis = GamepadAxis::new(axis_event.gamepad, axis_event.axis_type); + gamepad_axis.set(axis, axis_event.value); + } +} + +/// Uses [`GamepadButtonChangedEvent`]s to update update the relevant `Input` and `Axis` values. +pub fn gamepad_button_event_system( + mut button_events: EventReader, mut button_input: ResMut>, - mut axis: ResMut>, mut button_axis: ResMut>, - mut raw_events: EventReader, - mut events: EventWriter, settings: Res, ) { button_input.bypass_change_detection().clear(); - for event in raw_events.iter() { - match &event.event_type { - GamepadEventType::Connected(_) => { - events.send(GamepadEvent::new(event.gamepad, event.event_type.clone())); - for button_type in &ALL_BUTTON_TYPES { - let gamepad_button = GamepadButton::new(event.gamepad, *button_type); - button_input.reset(gamepad_button); - button_axis.set(gamepad_button, 0.0); - } - for axis_type in &ALL_AXIS_TYPES { - axis.set(GamepadAxis::new(event.gamepad, *axis_type), 0.0); - } - } - GamepadEventType::Disconnected => { - events.send(GamepadEvent::new(event.gamepad, event.event_type.clone())); - for button_type in &ALL_BUTTON_TYPES { - let gamepad_button = GamepadButton::new(event.gamepad, *button_type); - button_input.reset(gamepad_button); - button_axis.remove(gamepad_button); - } - for axis_type in &ALL_AXIS_TYPES { - axis.remove(GamepadAxis::new(event.gamepad, *axis_type)); - } - } - GamepadEventType::AxisChanged(axis_type, value) => { - let gamepad_axis = GamepadAxis::new(event.gamepad, *axis_type); - if let Some(filtered_value) = settings - .get_axis_settings(gamepad_axis) - .filter(*value, axis.get(gamepad_axis)) - { - axis.set(gamepad_axis, filtered_value); - events.send(GamepadEvent::new( - event.gamepad, - GamepadEventType::AxisChanged(*axis_type, filtered_value), - )); - } - } - GamepadEventType::ButtonChanged(button_type, value) => { - let gamepad_button = GamepadButton::new(event.gamepad, *button_type); - if let Some(filtered_value) = settings - .get_button_axis_settings(gamepad_button) - .filter(*value, button_axis.get(gamepad_button)) - { - button_axis.set(gamepad_button, filtered_value); - events.send(GamepadEvent::new( - event.gamepad, - GamepadEventType::ButtonChanged(*button_type, filtered_value), - )); - } + for button_event in button_events.iter() { + let button = GamepadButton::new(button_event.gamepad, button_event.button_type); + let value = button_event.value; + let button_property = settings.get_button_settings(button); + + if button_property.is_released(value) { + // We don't have to check if the button was previously pressed + // because that check is performed within Input::release() + button_input.release(button); + } else if button_property.is_pressed(value) { + button_input.press(button); + }; - let button_property = settings.get_button_settings(gamepad_button); - if button_input.pressed(gamepad_button) { - if button_property.is_released(*value) { - button_input.release(gamepad_button); - } - } else if button_property.is_pressed(*value) { - button_input.press(gamepad_button); - } + button_axis.set(button, value); + } +} + +/// A gamepad event. +/// +/// This event type is used over the [`GamepadConnectionEvent`], +/// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when +/// the in-frame relative ordering of events is important. +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub enum GamepadEvent { + Connection(GamepadConnectionEvent), + Button(GamepadButtonChangedEvent), + Axis(GamepadAxisChangedEvent), +} + +impl From for GamepadEvent { + fn from(value: GamepadConnectionEvent) -> Self { + Self::Connection(value) + } +} + +impl From for GamepadEvent { + fn from(value: GamepadButtonChangedEvent) -> Self { + Self::Button(value) + } +} + +impl From for GamepadEvent { + fn from(value: GamepadAxisChangedEvent) -> Self { + Self::Axis(value) + } +} + +/// Splits the [`GamepadEvent`] event stream into it's component events. +pub fn gamepad_event_system( + mut gamepad_events: EventReader, + mut connection_events: EventWriter, + mut button_events: EventWriter, + mut axis_events: EventWriter, +) { + for gamepad_event in gamepad_events.iter() { + match gamepad_event { + GamepadEvent::Connection(connection_event) => { + connection_events.send(connection_event.clone()); } + GamepadEvent::Button(button_event) => button_events.send(button_event.clone()), + GamepadEvent::Axis(axis_event) => axis_events.send(axis_event.clone()), } } } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 23583667ae973..fccc2027f56b4 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -12,8 +12,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ gamepad::{ - Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, GamepadEvent, - GamepadEventType, Gamepads, + Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, Gamepads, }, keyboard::{KeyCode, ScanCode}, mouse::MouseButton, @@ -23,7 +22,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel}; +use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel, SystemSet}; use bevy_reflect::{FromReflect, Reflect}; use keyboard::{keyboard_input_system, KeyCode, KeyboardInput, ScanCode}; use mouse::{ @@ -33,9 +32,11 @@ use mouse::{ use touch::{touch_screen_input_system, ForceTouch, TouchInput, TouchPhase, Touches}; use gamepad::{ - gamepad_connection_system, gamepad_event_system, AxisSettings, ButtonAxisSettings, - ButtonSettings, Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, - GamepadEvent, GamepadEventRaw, GamepadEventType, GamepadSettings, Gamepads, + gamepad_axis_event_system, gamepad_button_event_system, gamepad_connection_system, + gamepad_event_system, AxisSettings, ButtonAxisSettings, ButtonSettings, Gamepad, GamepadAxis, + GamepadAxisChangedEvent, GamepadAxisType, GamepadButton, GamepadButtonChangedEvent, + GamepadButtonType, GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadSettings, + Gamepads, }; #[cfg(feature = "serialize")] @@ -69,20 +70,23 @@ impl Plugin for InputPlugin { mouse_button_input_system.label(InputSystem), ) // gamepad + .add_event::() + .add_event::() + .add_event::() .add_event::() - .add_event::() .init_resource::() .init_resource::() .init_resource::>() .init_resource::>() .init_resource::>() - .add_system_to_stage( - CoreStage::PreUpdate, - gamepad_event_system.label(InputSystem), - ) - .add_system_to_stage( + .add_system_set_to_stage( CoreStage::PreUpdate, - gamepad_connection_system.after(InputSystem), + SystemSet::new() + .with_system(gamepad_event_system) + .with_system(gamepad_button_event_system.after(gamepad_event_system)) + .with_system(gamepad_axis_event_system.after(gamepad_event_system)) + .with_system(gamepad_connection_system.after(gamepad_event_system)) + .label(InputSystem), ) // touch .add_event::() @@ -114,9 +118,7 @@ impl Plugin for InputPlugin { // Register gamepad types app.register_type::() - .register_type::() - .register_type::() - .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/examples/input/gamepad_input_events.rs b/examples/input/gamepad_input_events.rs index 3d33a0e32b138..d4d9725d0c444 100644 --- a/examples/input/gamepad_input_events.rs +++ b/examples/input/gamepad_input_events.rs @@ -1,7 +1,9 @@ //! Iterates and prints gamepad input and connection events. use bevy::{ - input::gamepad::{GamepadEvent, GamepadEventType}, + input::gamepad::{ + GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnectionEvent, GamepadEvent, + }, prelude::*, }; @@ -9,30 +11,41 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_system(gamepad_events) + .add_system(gamepad_ordered_events) .run(); } -fn gamepad_events(mut gamepad_event: EventReader) { - for event in gamepad_event.iter() { - match event.event_type { - GamepadEventType::Connected(_) => { - info!("{:?} Connected", event.gamepad); - } - GamepadEventType::Disconnected => { - info!("{:?} Disconnected", event.gamepad); - } - GamepadEventType::ButtonChanged(button_type, value) => { - info!( - "{:?} of {:?} is changed to {}", - button_type, event.gamepad, value - ); - } - GamepadEventType::AxisChanged(axis_type, value) => { - info!( - "{:?} of {:?} is changed to {}", - axis_type, event.gamepad, value - ); - } +fn gamepad_events( + mut gamepad_connection_events: EventReader, + mut gamepad_axis_events: EventReader, + mut gamepad_button_events: EventReader, +) { + for connection_event in gamepad_connection_events.iter() { + info!("{:?}", connection_event); + } + for axis_event in gamepad_axis_events.iter() { + info!( + "{:?} of {:?} is changed to {}", + axis_event.axis_type, axis_event.gamepad, axis_event.value + ); + } + for button_event in gamepad_button_events.iter() { + info!( + "{:?} of {:?} is changed to {}", + button_event.button_type, button_event.gamepad, button_event.value + ); + } +} + +// If you require in-frame relative event ordering, you can also read the `Gamepad` event +// stream directly. For standard use-cases, reading the events individually or using the +// `Input` or `Axis` resources is preferable. +fn gamepad_ordered_events(mut gamepad_events: EventReader) { + for gamepad_event in gamepad_events.iter() { + match gamepad_event { + GamepadEvent::Connection(connection_event) => info!("{:?}", connection_event), + GamepadEvent::Button(button_event) => info!("{:?}", button_event), + GamepadEvent::Axis(axis_event) => info!("{:?}", axis_event), } } } diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index bbc59a9d31cb5..2f7a77a57c04b 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -3,7 +3,9 @@ use std::f32::consts::PI; use bevy::{ - input::gamepad::{GamepadButton, GamepadSettings}, + input::gamepad::{ + GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadSettings, + }, prelude::*, sprite::{MaterialMesh2dBundle, Mesh2dHandle}, }; @@ -468,42 +470,40 @@ fn update_buttons( } fn update_button_values( - mut events: EventReader, + mut events: EventReader, mut query: Query<(&mut Text, &TextWithButtonValue)>, ) { - for event in events.iter() { - if let GamepadEventType::ButtonChanged(button_type, value) = event.event_type { - for (mut text, text_with_button_value) in query.iter_mut() { - if button_type == **text_with_button_value { - text.sections[0].value = format!("{value:.3}"); - } + for button_event in events.iter() { + for (mut text, text_with_button_value) in query.iter_mut() { + if button_event.button_type == **text_with_button_value { + text.sections[0].value = format!("{:.3}", button_event.value); } } } } fn update_axes( - mut events: EventReader, + mut axis_events: EventReader, mut query: Query<(&mut Transform, &MoveWithAxes)>, mut text_query: Query<(&mut Text, &TextWithAxes)>, ) { - for event in events.iter() { - if let GamepadEventType::AxisChanged(axis_type, value) = event.event_type { - for (mut transform, move_with) in query.iter_mut() { - if axis_type == move_with.x_axis { - transform.translation.x = value * move_with.scale; - } - if axis_type == move_with.y_axis { - transform.translation.y = value * move_with.scale; - } + for axis_event in axis_events.iter() { + let axis_type = axis_event.axis_type; + let value = axis_event.value; + for (mut transform, move_with) in query.iter_mut() { + if axis_type == move_with.x_axis { + transform.translation.x = value * move_with.scale; } - for (mut text, text_with_axes) in text_query.iter_mut() { - if axis_type == text_with_axes.x_axis { - text.sections[0].value = format!("{value:.3}"); - } - if axis_type == text_with_axes.y_axis { - text.sections[2].value = format!("{value:.3}"); - } + if axis_type == move_with.y_axis { + transform.translation.y = value * move_with.scale; + } + } + for (mut text, text_with_axes) in text_query.iter_mut() { + if axis_type == text_with_axes.x_axis { + text.sections[0].value = format!("{value:.3}"); + } + if axis_type == text_with_axes.y_axis { + text.sections[2].value = format!("{value:.3}"); } } }