diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index fd45219a30614..4dbc00dcadd97 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -3,65 +3,115 @@ use bevy_ecs::event::{EventReader, EventWriter}; use bevy_ecs::system::{Res, ResMut}; use bevy_utils::{tracing::info, HashMap, HashSet}; +/// A gamepad with an associated `ID`. +/// +/// ## 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. +/// +/// ## Note +/// +/// The `ID` of a gamepad is fixed until the gamepad disconnects or the app is restarted. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Gamepad { + /// The `ID` of the gamepad. pub id: usize, } impl Gamepad { + /// Creates a new [`Gamepad`]. pub fn new(id: usize) -> Self { Self { id } } } -#[derive(Default, Debug)] -/// Container of unique connected [`Gamepad`]s +/// A collection of connected [`Gamepad`]s. /// -/// [`Gamepad`]s are registered and deregistered in [`gamepad_connection_system`] +/// ## Usage +/// +/// It is stored in a `bevy` resource which tracks all of the currently connected [`Gamepad`]s. +/// +/// ## Updating +/// +/// The [`Gamepad`]s are registered and deregistered in the [`gamepad_connection_system`] +/// whenever a [`GamepadEventType::Connected`] or [`GamepadEventType::Disconnected`] +/// event is received. +#[derive(Default, Debug)] pub struct Gamepads { + /// The collection of the connected [`Gamepad`]s. gamepads: HashSet, } impl Gamepads { - /// Returns true if the [Gamepads] contains a [Gamepad]. + /// Returns `true` if the `gamepad` is connected. pub fn contains(&self, gamepad: &Gamepad) -> bool { self.gamepads.contains(gamepad) } - /// Iterates over registered [Gamepad]s + /// An iterator visiting all connected [`Gamepad`]s in arbitrary order. pub fn iter(&self) -> impl Iterator + '_ { self.gamepads.iter() } - /// Registers [Gamepad]. + /// Registers the `gamepad` marking it as connected. fn register(&mut self, gamepad: Gamepad) { self.gamepads.insert(gamepad); } - /// Deregisters [Gamepad. + /// Deregisters the `gamepad` marking it as disconnected. fn deregister(&mut self, gamepad: &Gamepad) { self.gamepads.remove(gamepad); } } +/// The data contained in a [`GamepadEvent`] or [`GamepadEventRaw`]. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum GamepadEventType { + /// A [`Gamepad`] has been connected. Connected, + /// 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)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::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, @@ -70,14 +120,100 @@ impl GamepadEvent { } } +/// 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; +/// # use bevy_app::prelude::*; +/// # use bevy_ecs::prelude::*; +/// # +/// // This system sets the `bool` resource 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 = 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(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. +/// app.world.send_event(GamepadEventRaw::new(gamepad, GamepadEventType::Connected)); +/// +/// // 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` resource was updated from `false` to `true`. +/// assert!(app.world.resource::()); +/// +/// // 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` resource was updated from `true` to `false`. +/// assert!(!app.world.resource::()); +/// # +/// # bevy_ecs::system::assert_is_system(change_resource_on_gamepad_button_press); +/// ``` #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::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, @@ -86,38 +222,93 @@ impl GamepadEventRaw { } } +/// 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`] +/// which in turn is used to create the [`Input`] or +/// [`Axis`] `bevy` resources. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum GamepadButtonType { + /// The bottom action button of the action pad (i.e. PS: Cross, Xbox: A). South, + /// The right action button of the action pad (i.e. PS: Circle, Xbox: B). East, + /// The upper action button of the action pad (i.e. PS: Triangle, Xbox: Y). North, + /// The left action button of the action pad (i.e. PS: Square, Xbox: X). West, + + /// The C button. C, + /// The Z button. Z, + + /// The first left trigger. LeftTrigger, + /// The second left trigger. LeftTrigger2, + /// The first right trigger. RightTrigger, + /// The second right trigger. RightTrigger2, + /// The select button. Select, + /// The start button. Start, + /// The mode button. Mode, + + /// The left thumb stick button. LeftThumb, + /// The right thumb stick button. RightThumb, + + /// The up button of the D-Pad. DPadUp, + /// The down button of the D-Pad. DPadDown, + /// The left button of the D-Pad. DPadLeft, + /// The right button of the D-Pad. DPadRight, } +/// A button of a [`Gamepad`]. +/// +/// ## 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. +/// +/// ## Updating +/// +/// The resources are updated inside of the [`gamepad_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct GamepadButton { + /// The gamepad on which the button is located on. pub gamepad: Gamepad, + /// The type of the button. pub button_type: GamepadButtonType, } impl GamepadButton { + /// Creates a new [`GamepadButton`]. + /// + /// # Examples + /// + /// ``` + /// # use bevy_input::gamepad::{GamepadButton, GamepadButtonType, Gamepad}; + /// # + /// let gamepad_button = GamepadButton::new( + /// Gamepad::new(1), + /// GamepadButtonType::South, + /// ); + /// ``` pub fn new(gamepad: Gamepad, button_type: GamepadButtonType) -> Self { Self { gamepad, @@ -126,53 +317,148 @@ impl GamepadButton { } } +/// A type of a [`GamepadAxis`]. +/// +/// ## Usage +/// +/// This is used to determine which axis has changed its value when receiving a +/// [`GamepadEventType::AxisChanged`]. 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)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum GamepadAxisType { + /// The horizontal value of the left stick. LeftStickX, + /// The vertical value of the left stick. LeftStickY, + /// The value of the left `Z` button. LeftZ, + + /// The horizontal value of the right stick. RightStickX, + /// The vertical value of the right stick. RightStickY, + /// The value of the right `Z` button. RightZ, } +/// An axis of a [`Gamepad`]. +/// +/// ## 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. +/// +/// ## Updating +/// +/// The resource is updated inside of the [`gamepad_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct GamepadAxis { + /// The gamepad on which the axis is located on. pub gamepad: Gamepad, + /// The type of the axis. pub axis_type: GamepadAxisType, } impl GamepadAxis { + /// Creates a new [`GamepadAxis`]. + /// + /// # Examples + /// + /// ``` + /// # use bevy_input::gamepad::{GamepadAxis, GamepadAxisType, Gamepad}; + /// # + /// let gamepad_axis = GamepadAxis::new( + /// Gamepad::new(1), + /// GamepadAxisType::LeftStickX, + /// ); + /// ``` pub fn new(gamepad: Gamepad, axis_type: GamepadAxisType) -> Self { Self { gamepad, axis_type } } } +/// Settings for all [`Gamepad`]s. +/// +/// ## Usage +/// +/// It is used to create a `bevy` resource that stores the settings of every [`GamepadButton`] and +/// [`GamepadAxis`]. If no user defined [`ButtonSettings`], [`AxisSettings`], or [`ButtonAxisSettings`] +/// are defined, the default settings of each are used as a fallback accordingly. +/// +/// ## 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. #[derive(Default, Debug)] pub struct GamepadSettings { + /// The default button settings. pub default_button_settings: ButtonSettings, + /// The default axis settings. pub default_axis_settings: AxisSettings, + /// The default button axis settings. pub default_button_axis_settings: ButtonAxisSettings, + /// The user defined button settings. pub button_settings: HashMap, + /// The user defined axis settings. pub axis_settings: HashMap, + /// The user defined button axis settings. pub button_axis_settings: HashMap, } impl GamepadSettings { + /// Returns the [`ButtonSettings`] of the `button`. + /// + /// If no user defined [`ButtonSettings`] are specified the default [`ButtonSettings`] get returned. + /// + /// # Examples + /// + /// ``` + /// # use bevy_input::gamepad::{GamepadSettings, GamepadButton, Gamepad, GamepadButtonType}; + /// # + /// # let settings = GamepadSettings::default(); + /// let button = GamepadButton::new(Gamepad::new(1), GamepadButtonType::South); + /// let button_settings = settings.get_button_settings(button); + /// ``` pub fn get_button_settings(&self, button: GamepadButton) -> &ButtonSettings { self.button_settings .get(&button) .unwrap_or(&self.default_button_settings) } + /// Returns the [`AxisSettings`] of the `axis`. + /// + /// If no user defined [`AxisSettings`] are specified the default [`AxisSettings`] get returned. + /// + /// # Examples + /// + /// ``` + /// # use bevy_input::gamepad::{GamepadSettings, GamepadAxis, Gamepad, GamepadAxisType}; + /// # + /// # let settings = GamepadSettings::default(); + /// let axis = GamepadAxis::new(Gamepad::new(1), GamepadAxisType::LeftStickX); + /// let axis_settings = settings.get_axis_settings(axis); + /// ``` pub fn get_axis_settings(&self, axis: GamepadAxis) -> &AxisSettings { self.axis_settings .get(&axis) .unwrap_or(&self.default_axis_settings) } + /// Returns the [`ButtonAxisSettings`] of the `button`. + /// + /// If no user defined [`ButtonAxisSettings`] are specified the default [`ButtonAxisSettings`] get returned. + /// + /// # Examples + /// + /// ``` + /// # use bevy_input::gamepad::{GamepadSettings, GamepadButton, Gamepad, GamepadButtonType}; + /// # + /// # let settings = GamepadSettings::default(); + /// let button = GamepadButton::new(Gamepad::new(1), GamepadButtonType::South); + /// let button_axis_settings = settings.get_button_axis_settings(button); + /// ``` pub fn get_button_axis_settings(&self, button: GamepadButton) -> &ButtonAxisSettings { self.button_axis_settings .get(&button) @@ -180,9 +466,22 @@ impl GamepadSettings { } } +/// Settings for a [`GamepadButton`]. +/// +/// ## Usage +/// +/// It is used inside of the [`GamepadSettings`] to define the threshold for a gamepad button +/// to be considered pressed or released. A button is considered pressed if the `press` +/// value is surpassed and released if the `release` value is undercut. +/// +/// ## Updating +/// +/// The current value of a button is received through the [`GamepadEvent`]s or [`GamepadEventRaw`]s. #[derive(Debug, Clone)] pub struct ButtonSettings { + /// The threshold for the button to be considered as pressed. pub press: f32, + /// The threshold for the button to be considered as released. pub release: f32, } @@ -196,30 +495,49 @@ impl Default for ButtonSettings { } impl ButtonSettings { + /// Returns `true` if the button is pressed. + /// + /// A button is considered pressed if the `value` passed is greater than or equal to the `press` threshold. fn is_pressed(&self, value: f32) -> bool { value >= self.press } + /// Returns `true` if the button is released. + /// + /// A button is considered released if the `value` passed is lower than or equal to the `release` threshold. fn is_released(&self, value: f32) -> bool { value <= self.release } } -/// Defines the sensitivity range and threshold for an axis. +/// Settings for a [`GamepadAxis`]. +/// +/// It is used inside of the [`GamepadSettings`] to define the sensitivity range and +/// threshold for an axis. /// -/// Values that are lower than `negative_high` will be rounded to -1.0. -/// Values that are higher than `positive_high` will be rounded to 1.0. -/// Values that are in-between `negative_low` and `positive_low` will be rounded to 0.0. -/// Otherwise, values will not be rounded. +/// ## Logic +/// +/// - Values that are in-between `negative_low` and `positive_low` will be rounded to 0.0. +/// - Values that are higher than or equal to `positive_high` will be rounded to 1.0. +/// - Values that are lower than or equal to `negative_high` will be rounded to -1.0. +/// - Otherwise, values will not be rounded. /// /// The valid range is from -1.0 to 1.0, inclusive. +/// +/// ## Updating +/// +/// The current value of an axis is received through the [`GamepadEvent`]s or [`GamepadEventRaw`]s. #[derive(Debug, Clone)] pub struct AxisSettings { + /// The positive high value at which to apply rounding. pub positive_high: f32, + /// The positive low value at which to apply rounding. pub positive_low: f32, + /// The negative high value at which to apply rounding. pub negative_high: f32, + /// The negative low value at which to apply rounding. pub negative_low: f32, - ///`threshold` defines the minimum difference between old and new values to apply the changes. + /// The threshold defining the minimum difference between the old and new values to apply the changes. pub threshold: f32, } @@ -236,6 +554,16 @@ impl Default for AxisSettings { } impl AxisSettings { + /// Filters the `new_value` according to the specified settings. + /// + /// If the `new_value` is: + /// - in-between `negative_low` and `positive_low` it will be rounded to 0.0. + /// - higher than or equal to `positive_high` it will be rounded to 1.0. + /// - lower than or equal to `negative_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.positive_low && new_value >= self.negative_low { 0.0 @@ -257,10 +585,29 @@ impl AxisSettings { } } +/// Settings for a [`GamepadButton`]. +/// +/// It is used inside of the [`GamepadSettings`] to define the sensitivity range and +/// threshold for a button axis. +/// +/// ## Logic +/// +/// - Values that are higher than or equal to `high` will be rounded to 1.0. +/// - Values that are lower than or equal to `low` will be rounded to 0.0. +/// - Otherwise, values will not be rounded. +/// +/// The valid range is from 0.0 to 1.0, inclusive. +/// +/// ## Updating +/// +/// The current value of a button is received through the [`GamepadEvent`]s or [`GamepadEventRaw`]s. #[derive(Debug, Clone)] pub struct ButtonAxisSettings { + /// The high value at which to apply rounding. pub high: f32, + /// The low value at which to apply rounding. pub low: f32, + /// The threshold to apply rounding. pub threshold: f32, } @@ -275,6 +622,15 @@ impl Default for ButtonAxisSettings { } impl ButtonAxisSettings { + /// Filters the `new_value` according to the specified settings. + /// + /// If the `new_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 @@ -294,9 +650,11 @@ impl ButtonAxisSettings { } } -/// Monitors gamepad connection and disconnection events, updating the [`Gamepads`] resource accordingly +/// Monitors gamepad connection and disconnection events and updates the [`Gamepads`] resource accordingly. /// -/// By default, runs during `CoreStage::PreUpdate` when added via [`InputPlugin`](crate::InputPlugin). +/// ## 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, @@ -316,6 +674,16 @@ pub fn gamepad_connection_system( } } +/// 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( mut button_input: ResMut>, mut axis: ResMut>, @@ -388,6 +756,7 @@ pub fn gamepad_event_system( } } +/// An array of every [`GamepadButtonType`] variant. const ALL_BUTTON_TYPES: [GamepadButtonType; 19] = [ GamepadButtonType::South, GamepadButtonType::East, @@ -410,6 +779,7 @@ const ALL_BUTTON_TYPES: [GamepadButtonType; 19] = [ GamepadButtonType::DPadRight, ]; +/// An array of every [`GamepadAxisType`] variant. const ALL_AXIS_TYPES: [GamepadAxisType; 6] = [ GamepadAxisType::LeftStickX, GamepadAxisType::LeftStickY,