Skip to content

Commit

Permalink
Add value for button-like inputs (#649)
Browse files Browse the repository at this point in the history
* Bump macro crate version

* Add Triggerlike trait

* Add Triggerlike variants everywhere

* Implement Triggerlike for GamepadButtonType and GamepadButton

* Implement Triggerlike for MouseButton and KeyCode

* Update changelog

* Fix broken doc links

* Add ActionState::clamped_trigger_value

* Use the right type name in RELEASES

Co-authored-by: Shute <nagame2036@163.com>

* Fix warning in RELEASES.md

* Add inputmap insertion methods

* Write basic integration tests

* More integration testing

* Fix CI

* Change `ActionDiff::Pressed`

* Fix example

* Reuse `set_value_as_gamepad` of `GamepadButtonType` and `GamepadButton`

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
  • Loading branch information
Shute052 and alice-i-cecile authored Oct 16, 2024
1 parent 5bb37ff commit 54a361b
Show file tree
Hide file tree
Showing 16 changed files with 652 additions and 120 deletions.
14 changes: 11 additions & 3 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Version 0.16.0 (Unreleased)

### Bugs (0.16.0)

- fixed the bug where the values of buttonlike `ActionState`s cann't be retrieved
- now you can use `button_value` function of `ActionState` to get the value of a buttonlike action
- added `value` and friends as the fields of `action_data::ButtonData`
- `GamepadButton` and `GamepadButtonType` now get values from `Axis<GamepadButton>`
- `VirtualAxis`, `VirtualDPad`, and `VirtualDPad3D` now report axis values based on the values of the constitute buttons

### Usability (0.16.0)

- made virtual axial controls more flexible, accepting any kind of `Buttonlike`
Expand All @@ -26,15 +34,15 @@

### Usability (0.15.1)

#### InputMap
#### InputMap reflection

- Reflect `Component` and `Resource`, which enables accessing the data in the type registry

#### Actionlike
#### Actionlike macro improvements

- added `#[actionlike]` for actions to set their input kinds, either on an enum or on its individual variants.

#### ActionState
#### ActionState reflection

- Reflect `Component` and `Resource`, which enables accessing the data in the type registry

Expand Down
2 changes: 1 addition & 1 deletion examples/axis_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn move_player(query: Query<&ActionState<Action>, With<Player>>) {
//
// If you don't have a variable trigger, this will just return 0.0 when not pressed and 1.0
// when pressed.
let value = action_state.clamped_value(&Action::Throttle);
let value = action_state.clamped_button_value(&Action::Throttle);
println!("Throttle: {value}");
}

Expand Down
25 changes: 17 additions & 8 deletions src/action_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use bevy::{
};
use serde::{Deserialize, Serialize};

use crate::buttonlike::ButtonValue;
use crate::{action_state::ActionKindData, prelude::ActionState, Actionlike};

/// Stores presses and releases of buttons without timing information
Expand All @@ -31,6 +32,8 @@ pub enum ActionDiff<A: Actionlike> {
Pressed {
/// The value of the action
action: A,
/// The new value of the action
value: f32,
},
/// The action was released
Released {
Expand Down Expand Up @@ -88,7 +91,7 @@ impl<A: Actionlike> MapEntities for ActionDiffEvent<A> {
/// Inside of the hashmap, [`Entity::PLACEHOLDER`] represents the global / resource state of the action.
#[derive(Debug, PartialEq, Clone)]
pub struct SummarizedActionState<A: Actionlike> {
button_state_map: HashMap<Entity, HashMap<A, bool>>,
button_state_map: HashMap<Entity, HashMap<A, ButtonValue>>,
axis_state_map: HashMap<Entity, HashMap<A, f32>>,
dual_axis_state_map: HashMap<Entity, HashMap<A, Vec2>>,
triple_axis_state_map: HashMap<Entity, HashMap<A, Vec3>>,
Expand Down Expand Up @@ -141,7 +144,8 @@ impl<A: Actionlike> SummarizedActionState<A> {
for (action, action_data) in global_action_state.all_action_data() {
match &action_data.kind_data {
ActionKindData::Button(button_data) => {
per_entity_button_state.insert(action.clone(), button_data.pressed());
per_entity_button_state
.insert(action.clone(), button_data.to_button_value());
}
ActionKindData::Axis(axis_data) => {
per_entity_axis_state.insert(action.clone(), axis_data.value);
Expand Down Expand Up @@ -171,7 +175,8 @@ impl<A: Actionlike> SummarizedActionState<A> {
for (action, action_data) in action_state.all_action_data() {
match &action_data.kind_data {
ActionKindData::Button(button_data) => {
per_entity_button_state.insert(action.clone(), button_data.pressed());
per_entity_button_state
.insert(action.clone(), button_data.to_button_value());
}
ActionKindData::Axis(axis_data) => {
per_entity_axis_state.insert(action.clone(), axis_data.value);
Expand Down Expand Up @@ -207,15 +212,18 @@ impl<A: Actionlike> SummarizedActionState<A> {
/// Previous values will be treated as default if they were not present.
pub fn button_diff(
action: A,
previous_button: Option<bool>,
current_button: Option<bool>,
previous_button: Option<ButtonValue>,
current_button: Option<ButtonValue>,
) -> Option<ActionDiff<A>> {
let previous_button = previous_button.unwrap_or_default();
let current_button = current_button?;

(previous_button != current_button).then(|| {
if current_button {
ActionDiff::Pressed { action }
if current_button.pressed {
ActionDiff::Pressed {
action,
value: current_button.value,
}
} else {
ActionDiff::Released { action }
}
Expand Down Expand Up @@ -377,6 +385,7 @@ mod tests {
use crate as leafwing_input_manager;

use super::*;
use crate::buttonlike::ButtonValue;
use bevy::{ecs::system::SystemState, prelude::*};

#[derive(Actionlike, Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
Expand Down Expand Up @@ -409,7 +418,7 @@ mod tests {
let mut triple_axis_state_map = HashMap::default();

let mut global_button_state = HashMap::default();
global_button_state.insert(TestAction::Button, true);
global_button_state.insert(TestAction::Button, ButtonValue::from_pressed(true));
button_state_map.insert(entity, global_button_state);

let mut global_axis_state = HashMap::default();
Expand Down
27 changes: 27 additions & 0 deletions src/action_state/action_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bevy::{
};
use serde::{Deserialize, Serialize};

use crate::buttonlike::ButtonValue;
#[cfg(feature = "timing")]
use crate::timing::Timing;
use crate::{buttonlike::ButtonState, InputControlKind};
Expand Down Expand Up @@ -77,7 +78,9 @@ impl ActionKindData {
match self {
Self::Button(data) => {
data.fixed_update_state = data.state;
data.fixed_update_value = data.value;
data.state = data.update_state;
data.value = data.update_value;
}
Self::Axis(data) => {
data.fixed_update_value = data.value;
Expand All @@ -100,7 +103,9 @@ impl ActionKindData {
match self {
Self::Button(data) => {
data.update_state = data.state;
data.update_value = data.value;
data.state = data.fixed_update_state;
data.value = data.fixed_update_value;
}
Self::Axis(data) => {
data.update_value = data.value;
Expand All @@ -127,6 +132,12 @@ pub struct ButtonData {
pub update_state: ButtonState,
/// The `state` of the action in the `FixedMain` schedule
pub fixed_update_state: ButtonState,
/// How far has the button been pressed
pub value: f32,
/// The `value` of the action in the `Main` schedule
pub update_value: f32,
/// The `value` of the action in the `FixedMain` schedule
pub fixed_update_value: f32,
/// When was the button pressed / released, and how long has it been held for?
#[cfg(feature = "timing")]
pub timing: Timing,
Expand All @@ -138,6 +149,9 @@ impl ButtonData {
state: ButtonState::JustPressed,
update_state: ButtonState::JustPressed,
fixed_update_state: ButtonState::JustPressed,
value: 1.0,
update_value: 1.0,
fixed_update_value: 1.0,
#[cfg(feature = "timing")]
timing: Timing::NEW,
};
Expand All @@ -147,6 +161,9 @@ impl ButtonData {
state: ButtonState::JustReleased,
update_state: ButtonState::JustReleased,
fixed_update_state: ButtonState::JustReleased,
value: 0.0,
update_value: 0.0,
fixed_update_value: 0.0,
#[cfg(feature = "timing")]
timing: Timing::NEW,
};
Expand All @@ -160,6 +177,9 @@ impl ButtonData {
state: ButtonState::Released,
update_state: ButtonState::Released,
fixed_update_state: ButtonState::Released,
value: 0.0,
update_value: 0.0,
fixed_update_value: 0.0,
#[cfg(feature = "timing")]
timing: Timing::NEW,
};
Expand Down Expand Up @@ -191,6 +211,13 @@ impl ButtonData {
pub fn just_released(&self) -> bool {
self.state.just_released()
}

/// Convert `self` to a [`ButtonValue`].
#[inline]
#[must_use]
pub fn to_button_value(&self) -> ButtonValue {
ButtonValue::new(self.state.pressed(), self.value)
}
}

/// The raw data for an [`ActionState`](super::ActionState) corresponding to a single virtual axis.
Expand Down
80 changes: 61 additions & 19 deletions src/action_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,23 +538,65 @@ impl<A: Actionlike> ActionState<A> {
triple_axis_data
}

/// Get the value associated with the corresponding `action` if present.
/// Get the value associated with the corresponding buttonlike `action` if present.
///
/// Different kinds of bindings have different ways of calculating the value:
/// # Warnings
///
/// - Binary buttons will have a value of `0.0` when the button is not pressed, and a value of `1.0` when the button is pressed.
/// - Some axes, such as an analog stick, will have a value in the range `[-1.0, 1.0]`.
/// - Some axes, such as a variable trigger, will have a value in the range `[0.0, 1.0]`.
/// - Some buttons will also return a value in the range `[0.0, 1.0]`, such as analog gamepad triggers which may be tracked as buttons or axes. Examples of these include the Xbox LT/Rtriggers and the Playstation L2/R2 triggers. See also the `axis_inputs` example in the repository.
/// - Dual axis inputs will return the magnitude of its [`Vec2`] and will be in the range `0.0..=1.0`.
/// - Chord inputs will return the value of its first input.
/// This value may not be bounded as you might expect.
/// Consider clamping this to account for multiple triggering inputs,
/// typically using the [`clamped_button_value`](Self::clamped_button_value) method instead.
#[inline]
#[must_use]
#[track_caller]
pub fn button_value(&self, action: &A) -> f32 {
debug_assert_eq!(action.input_control_kind(), InputControlKind::Button);

if self.action_disabled(action) {
return 0.0;
}

let action_data = self.button_data(action);
action_data.map_or(0.0, |action_data| action_data.value)
}

/// Sets the value of the buttonlike `action` to the provided `value`.
#[track_caller]
pub fn set_button_value(&mut self, action: &A, value: f32) {
debug_assert_eq!(action.input_control_kind(), InputControlKind::Button);

let button_data = self.button_data_mut_or_default(action);
button_data.value = value;

if value > 0.0 {
#[cfg(feature = "timing")]
if button_data.state.released() {
button_data.timing.flip();
}

button_data.state.press();
} else {
#[cfg(feature = "timing")]
if button_data.state.pressed() {
button_data.timing.flip();
}

button_data.state.release();
}
}

/// Get the value associated with the corresponding `action`, clamped to `[-1.0, 1.0]`.
///
/// If multiple inputs trigger the same game action at the same time, the value of each
/// triggering input will be added together.
/// # Warning
///
/// # Warnings
/// This value will be 0. by default,
/// even if the action is not a buttonlike action.
pub fn clamped_button_value(&self, action: &A) -> f32 {
self.button_value(action).clamp(-1., 1.)
}

/// Get the value associated with the corresponding axislike `action` if present.
///
/// This value will be 0. if the action has never been pressed or released.
/// # Warnings
///
/// This value may not be bounded as you might expect.
/// Consider clamping this to account for multiple triggering inputs,
Expand All @@ -569,13 +611,11 @@ impl<A: Actionlike> ActionState<A> {
return 0.0;
}

match self.axis_data(action) {
Some(axis_data) => axis_data.value,
None => 0.0,
}
let action_data = self.axis_data(action);
action_data.map_or(0.0, |action_data| action_data.value)
}

/// Sets the value of the `action` to the provided `value`.
/// Sets the value of the axislike `action` to the provided `value`.
#[track_caller]
pub fn set_value(&mut self, action: &A, value: f32) {
debug_assert_eq!(action.input_control_kind(), InputControlKind::Axis);
Expand Down Expand Up @@ -754,6 +794,7 @@ impl<A: Actionlike> ActionState<A> {
}

action_data.state.press();
action_data.value = 1.0;
}

/// Release the `action`
Expand All @@ -772,6 +813,7 @@ impl<A: Actionlike> ActionState<A> {
}

action_data.state.release();
action_data.value = 0.0;
}

/// Resets an action to its default state.
Expand Down Expand Up @@ -1067,8 +1109,8 @@ impl<A: Actionlike> ActionState<A> {
/// This lets you reconstruct an [`ActionState`] from a stream of [`ActionDiff`]s
pub fn apply_diff(&mut self, action_diff: &ActionDiff<A>) {
match action_diff {
ActionDiff::Pressed { action } => {
self.press(action);
ActionDiff::Pressed { action, value } => {
self.set_button_value(action, *value);
}
ActionDiff::Released { action } => {
self.release(action);
Expand Down
39 changes: 39 additions & 0 deletions src/buttonlike.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@
use bevy::reflect::Reflect;
use serde::{Deserialize, Serialize};

/// Current values of a button.
#[derive(Debug, Default, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
pub struct ButtonValue {
/// Is the button currently pressed?
pub pressed: bool,

/// How far has the button been pressed,
/// ranging from 0.0 (not pressed) to 1.0 (fully pressed).
pub value: f32,
}

impl ButtonValue {
/// Create a new [`ButtonValue`] with the given `pressed` state and `value`.
#[inline]
pub fn new(pressed: bool, value: f32) -> Self {
Self { pressed, value }
}

/// Create a new [`ButtonValue`] with the given `pressed` state.
///
/// The value will set to 1.0 if `pressed` is true, and 0.0 otherwise
#[inline]
pub fn from_pressed(pressed: bool) -> Self {
Self::new(pressed, f32::from(pressed))
}
}

impl From<ButtonState> for ButtonValue {
fn from(value: ButtonState) -> Self {
Self::from_pressed(value.pressed())
}
}

impl From<bevy::input::ButtonState> for ButtonValue {
fn from(value: bevy::input::ButtonState) -> Self {
Self::from_pressed(value.is_pressed())
}
}

/// The current state of a particular button,
/// usually corresponding to a single [`Actionlike`](crate::Actionlike) action.
///
Expand Down
2 changes: 1 addition & 1 deletion src/input_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn find_gamepad(_gamepads: &Gamepads) -> Gamepad {
}

/// A Multi-Map that allows you to map actions to multiple [`UserInputs`](crate::user_input::UserInput)s,
/// whether they are [`Buttonlike`], [`Axislike`] or [`DualAxislike`].
/// whether they are [`Buttonlike`], [`Axislike`], [`DualAxislike`], or [`TripleAxislike`].
///
/// When inserting a binding, the [`InputControlKind`] of the action variant must match that of the input type.
/// Use [`InputMap::insert`] to insert buttonlike inputs,
Expand Down
Loading

0 comments on commit 54a361b

Please sign in to comment.