From 30c1c1a2696a53a135e29eba27fc724919d368c4 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 6 Aug 2024 14:16:33 -0400 Subject: [PATCH] Add ActionState-level disabling (#573) --- RELEASES.md | 6 ++ src/action_state/mod.rs | 126 ++++++++++++++++++++++++++++------------ src/systems.rs | 4 +- tests/integration.rs | 6 +- 4 files changed, 99 insertions(+), 43 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 47a4bf60..345df4ef 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -84,6 +84,12 @@ Input processors allow you to create custom logic for axis-like input manipulati - implemented `WithAxisProcessingPipelineExt` to manage processors for `SingleAxis` and `VirtualAxis`, integrating the common processing configuration. - implemented `WithDualAxisProcessingPipelineExt` to manage processors for `DualAxis` and `VirtualDpad`, integrating the common processing configuration. +#### Better disabling + +- Actions can now be disabled at both the individual action and `ActionState` level +- Disabling actions now resets their value, exposed via the `ActionState::reset` method +- `ActionState::release_all` has been renamed to `ActionState::reset_all` and now resets the values of `Axislike` and `DualAxislike` actions + ### Usability #### InputMap diff --git a/src/action_state/mod.rs b/src/action_state/mod.rs index b4d16217..6a6b55d4 100644 --- a/src/action_state/mod.rs +++ b/src/action_state/mod.rs @@ -20,6 +20,16 @@ pub use action_data::*; /// /// Can be used as either a resource or as a [`Component`] on entities that you wish to control directly from player input. /// +/// Actions can be disabled in four different ways, with increasing granularity: +/// +/// 1. By disabling updates to all actions using a run condition on [`InputManagerSystem::Update`](crate::plugin::InputManagerSystem::Update). +/// 2. By disabling updates to all actions of type `A` using a run condition on [`tick_action_state::`](crate::systems::tick_action_state). +/// 3. By setting a specific action state to disabled using [`ActionState::disable`]. +/// 4. By disabling a specific action using [`ActionState::disable_action`]. +/// +/// More general mechanisms of disabling actions will cause specific mechanisms to be ignored. +/// For example, if an entire action state is disabled, then enabling or disabling individual actions will have no effect. +/// /// # Example /// ```rust /// use bevy::reflect::Reflect; @@ -63,6 +73,8 @@ pub use action_data::*; /// ``` #[derive(Resource, Component, Clone, Debug, PartialEq, Serialize, Deserialize, Reflect)] pub struct ActionState { + /// Whether or not all of the actions are disabled. + disabled: bool, /// The shared action data for each action action_data: HashMap, } @@ -72,6 +84,7 @@ pub struct ActionState { impl Default for ActionState { fn default() -> Self { Self { + disabled: false, action_data: HashMap::default(), } } @@ -107,7 +120,13 @@ impl ActionState { /// /// The `action_data` is typically constructed from [`InputMap::process_actions`](crate::input_map::InputMap::process_actions), /// which reads from the assorted [`ButtonInput`](bevy::input::ButtonInput) resources. + /// + /// If this [`ActionState`] is disabled, it will not be updated. pub fn update(&mut self, updated_actions: UpdatedActions) { + if self.disabled { + return; + } + for (action, updated_value) in updated_actions.iter() { match updated_value { UpdatedValue::Button(pressed) => { @@ -590,15 +609,31 @@ impl ActionState { action_data.state.release(); } - /// Releases all [`Buttonlike`](crate::user_input::Buttonlike) actions. - pub fn release_all(&mut self) { + /// Resets an action to its default state. + /// + /// Buttons will be released, and axes will be set to 0. + pub fn reset(&mut self, action: &A) { + match action.input_control_kind() { + InputControlKind::Button => self.release(action), + InputControlKind::Axis => { + let axis_data = self.axis_data_mut_or_default(action); + axis_data.value = 0.0; + } + InputControlKind::DualAxis => { + let dual_axis_data = self.dual_axis_data_mut_or_default(action); + dual_axis_data.pair = Vec2::ZERO; + } + } + } + + /// Releases all [`Buttonlike`](crate::user_input::Buttonlike) actions, + /// sets all [`Axislike`](crate::user_input::Axislike) actions to 0, + /// and sets all [`DualAxislike`](crate::user_input::DualAxislike) actions to [`Vec2::ZERO`]. + pub fn reset_all(&mut self) { // Collect out to avoid angering the borrow checker let all_actions = self.action_data.keys().cloned().collect::>(); - for action in all_actions - .into_iter() - .filter(|action| action.input_control_kind() == InputControlKind::Button) - { - self.release(&action); + for action in all_actions.into_iter() { + self.reset(&action); } } @@ -606,7 +641,7 @@ impl ActionState { /// /// The action will be released, and will not be able to be pressed again /// until it would have otherwise been released by [`ActionState::release`], - /// [`ActionState::release_all`] or [`ActionState::update`]. + /// [`ActionState::reset`], [`ActionState::reset_all`] or [`ActionState::update`]. /// /// No initial instant will be recorded /// Instead, this is set through [`ActionState::tick()`] @@ -667,35 +702,62 @@ impl ActionState { matches!(self.button_data(action), Some(action_data) if action_data.consumed) } - /// Disables the `action` + /// Is the entire [`ActionState`] currently disabled? + pub fn disabled(&self) -> bool { + self.disabled + } + + /// Is this `action` currently disabled? #[inline] - pub fn disable(&mut self, action: &A) { + #[must_use] + pub fn action_disabled(&self, action: &A) -> bool { + if self.disabled { + return true; + } + + match self.action_data(action) { + Some(action_data) => action_data.disabled, + None => false, + } + } + + /// Disables the entire [`ActionState`]. + /// + /// All values will be reset to their default state. + #[inline] + pub fn disable(&mut self) { + self.disabled = true; + self.reset_all(); + } + + /// Disables the `action`. + /// + /// The action's value will be reset to its default state. + #[inline] + pub fn disable_action(&mut self, action: &A) { let action_data = self.action_data_mut_or_default(action); action_data.disabled = true; + self.reset(action); } /// Disables all actions #[inline] - pub fn disable_all(&mut self) { + pub fn disable_all_actions(&mut self) { for action in self.keys() { - self.disable(&action); + self.disable_action(&action); } } - /// Is this `action` currently disabled? + /// Enables the entire [`ActionState`] #[inline] - #[must_use] - pub fn disabled(&self, action: &A) -> bool { - match self.action_data(action) { - Some(action_data) => action_data.disabled, - None => false, - } + pub fn enable(&mut self) { + self.disabled = false; } /// Enables the `action` #[inline] - pub fn enable(&mut self, action: &A) { + pub fn enable_action(&mut self, action: &A) { let action_data = self.action_data_mut_or_default(action); action_data.disabled = false; @@ -703,9 +765,9 @@ impl ActionState { /// Enables all actions #[inline] - pub fn enable_all(&mut self) { + pub fn enable_all_actions(&mut self) { for action in self.keys() { - self.enable(&action); + self.enable_action(&action); } } @@ -720,10 +782,7 @@ impl ActionState { pub fn pressed(&self, action: &A) -> bool { debug_assert_eq!(action.input_control_kind(), InputControlKind::Button); - if self - .action_data(action) - .map_or(false, |action_data| action_data.disabled) - { + if self.action_disabled(action) { return false; } @@ -744,10 +803,7 @@ impl ActionState { pub fn just_pressed(&self, action: &A) -> bool { debug_assert_eq!(action.input_control_kind(), InputControlKind::Button); - if self - .action_data(action) - .map_or(false, |action_data| action_data.disabled) - { + if self.action_disabled(action) { return false; } @@ -770,10 +826,7 @@ impl ActionState { pub fn released(&self, action: &A) -> bool { debug_assert_eq!(action.input_control_kind(), InputControlKind::Button); - if self - .action_data(action) - .map_or(false, |action_data| action_data.disabled) - { + if self.action_disabled(action) { return true; } @@ -794,10 +847,7 @@ impl ActionState { pub fn just_released(&self, action: &A) -> bool { debug_assert_eq!(action.input_control_kind(), InputControlKind::Button); - if self - .action_data(action) - .map_or(false, |action_data| action_data.disabled) - { + if self.action_disabled(action) { return false; } diff --git a/src/systems.rs b/src/systems.rs index aba8f473..e740a073 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -484,7 +484,7 @@ pub fn release_on_input_map_removed( ) { let mut iter = action_state_query.iter_many_mut(removed_components.read()); while let Some(mut action_state) = iter.fetch_next() { - action_state.release_all(); + action_state.reset_all(); } // Detect when an InputMap resource is removed. @@ -496,7 +496,7 @@ pub fn release_on_input_map_removed( // so we know the input map was removed. if let Some(mut action_state) = action_state_resource { - action_state.release_all(); + action_state.reset_all(); } // Reset our local so our removal detection is only triggered once. diff --git a/tests/integration.rs b/tests/integration.rs index b856dec6..0f29f4b5 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -78,7 +78,7 @@ fn disable_input() { // Disable the global input let mut action_state = app.world_mut().resource_mut::>(); - action_state.disable_all(); + action_state.disable_all_actions(); // But the player is still paying respects app.update(); @@ -90,7 +90,7 @@ fn disable_input() { .world_mut() .query_filtered::<&mut ActionState, With>() .single_mut(app.world_mut()); - action_state.disable_all(); + action_state.disable_all_actions(); // Now, all respect has faded app.update(); @@ -105,7 +105,7 @@ fn disable_input() { // Re-enable the global input let mut action_state = app.world_mut().resource_mut::>(); - action_state.enable_all(); + action_state.enable_all_actions(); // And it will start paying respects again app.update();