Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sensitivity #377

Merged
merged 15 commits into from
Aug 31, 2023
1 change: 1 addition & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### Enhancements
- Added `DeadZoneShape` for `DualAxis` which allows for different deadzones shapes: cross, rectangle, and ellipse.
- Added sensitivity for `SingleAxis` and `DualAxis`, allowing you to scale mouse, keypad and gamepad inputs differently for each action.

## Version 0.10

Expand Down
31 changes: 31 additions & 0 deletions src/axislike.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ pub struct SingleAxis {
pub negative_low: f32,
/// Whether to invert output values from this axis.
pub inverted: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can replace inverted with a negative sensitivity value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do this mathematically, but I think it makes the API a lot less clear. I think we should keep them separate, and add some kind of non-negative constraint on sensitivity (or at least a warning).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, fair. Let's do that then.

Copy link
Collaborator Author

@100-TomatoJuice 100-TomatoJuice Aug 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but what does "that" refer to? I'm guessing merging sensitivity and inverted as you said below, but what to do about making the API less clear, as plof27 said. I'm guessing just write useful docs on sensitivity to explain how inverting works now? I think this is addressed by leaving invert() functions for use, as it uses the same API as before. It's just inverted means reversing the sign instead of flipping a bool.

Copy link
Collaborator Author

@100-TomatoJuice 100-TomatoJuice Aug 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side note, instead of removing the invert functions, I can just invert the sensitivity to do the same thing.

/// Returns this [`SingleAxis`] inverted.
#[must_use]
pub fn inverted(mut self) -> Self {
    self.sensitivity = -self.sensitivity;
    self
}

This helps to keep the ergonomics of the invert bool without having it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I like that :)

Copy link
Collaborator Author

@100-TomatoJuice 100-TomatoJuice Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I like that :)

So, would you like that re-merged into one value? Or, close this as resolved and keep them separate. I am good with either solution.

/// How sensitive the axis is to input values.
///
/// Since sensitivity is a multiplier, any value `>1.0` will increase sensitivity while any value `<1.0` will decrease sensitivity.
100-TomatoJuice marked this conversation as resolved.
Show resolved Hide resolved
/// This value should always be strictly positive: a value of 0 will cause the axis to stop functioning,
/// while negative values will invert the direction.
pub sensitivity: f32,
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
/// The target value for this input, used for input mocking.
///
/// WARNING: this field is ignored for the sake of [`Eq`] and [`Hash`](std::hash::Hash)
Expand All @@ -45,6 +51,7 @@ impl SingleAxis {
positive_low: threshold,
negative_low: -threshold,
inverted: false,
sensitivity: 1.0,
value: None,
}
}
Expand All @@ -60,6 +67,7 @@ impl SingleAxis {
positive_low: 0.0,
negative_low: 0.0,
inverted: false,
sensitivity: 1.0,
value: Some(value),
}
}
Expand All @@ -72,6 +80,7 @@ impl SingleAxis {
positive_low: 0.,
negative_low: 0.,
inverted: false,
sensitivity: 1.0,
value: None,
}
}
Expand All @@ -84,6 +93,7 @@ impl SingleAxis {
positive_low: 0.,
negative_low: 0.,
inverted: false,
sensitivity: 1.0,
value: None,
}
}
Expand All @@ -96,6 +106,7 @@ impl SingleAxis {
positive_low: 0.,
negative_low: 0.,
inverted: false,
sensitivity: 1.0,
value: None,
}
}
Expand All @@ -108,6 +119,7 @@ impl SingleAxis {
positive_low: 0.,
negative_low: 0.,
inverted: false,
sensitivity: 1.0,
value: None,
}
}
Expand All @@ -121,6 +133,7 @@ impl SingleAxis {
negative_low: threshold,
positive_low: f32::MAX,
inverted: false,
sensitivity: 1.0,
value: None,
}
}
Expand All @@ -134,6 +147,7 @@ impl SingleAxis {
negative_low: f32::MIN,
positive_low: threshold,
inverted: false,
sensitivity: 1.0,
value: None,
}
}
Expand All @@ -146,6 +160,13 @@ impl SingleAxis {
self
}

/// Returns this [`SingleAxis`] with the sensitivity set to the specified value
#[must_use]
pub fn with_sensitivity(mut self, sensitivity: f32) -> SingleAxis {
self.sensitivity = sensitivity;
self
}

/// Returns this [`SingleAxis`] inverted.
#[must_use]
pub fn inverted(mut self) -> Self {
Expand All @@ -159,6 +180,7 @@ impl PartialEq for SingleAxis {
self.axis_type == other.axis_type
&& FloatOrd(self.positive_low) == FloatOrd(other.positive_low)
&& FloatOrd(self.negative_low) == FloatOrd(other.negative_low)
&& FloatOrd(self.sensitivity) == FloatOrd(other.sensitivity)
}
}
impl Eq for SingleAxis {}
Expand All @@ -167,6 +189,7 @@ impl std::hash::Hash for SingleAxis {
self.axis_type.hash(state);
FloatOrd(self.positive_low).hash(state);
FloatOrd(self.negative_low).hash(state);
FloatOrd(self.sensitivity).hash(state);
}
}

Expand Down Expand Up @@ -281,6 +304,14 @@ impl DualAxis {
self
}

/// Returns this [`DualAxis`] with the sensitivity set to the specified values
#[must_use]
pub fn with_sensitivity(mut self, x_sensitivity: f32, y_sensitivity: f32) -> DualAxis {
self.x.sensitivity = x_sensitivity;
self.y.sensitivity = y_sensitivity;
self
}

/// Returns this [`DualAxis`] with an inverted X-axis.
#[must_use]
pub fn inverted_x(mut self) -> DualAxis {
Expand Down
155 changes: 101 additions & 54 deletions src/input_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,60 +19,101 @@ use std::collections::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;

/// Maps from raw inputs to an input-method agnostic representation
///
/// Multiple inputs can be mapped to the same action,
/// and each input can be mapped to multiple actions.
///
/// The provided input types must be able to be converted into a [`UserInput`].
///
/// The maximum number of bindings (total) that can be stored for each action is 16.
/// Insertions will silently fail if you have reached this cap.
///
/// By default, if two actions would be triggered by a combination of buttons,
/// and one combination is a strict subset of the other, only the larger input is registered.
/// For example, pressing both `S` and `Ctrl + S` in your text editor app would save your file,
/// but not enter the letters `s`.
/// Set the [`ClashStrategy`](crate::clashing_inputs::ClashStrategy) resource
/// to configure this behavior.
///
/// # Example
/// ```rust
/// use bevy::prelude::*;
/// use leafwing_input_manager::prelude::*;
/// use leafwing_input_manager::user_input::InputKind;
///
/// // You can Run!
/// // But you can't Hide :(
/// #[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
/// enum Action {
/// Run,
/// Hide,
/// }
///
/// // Construction
/// let mut input_map = InputMap::new([
/// // Note that the type of your iterators must be homogenous;
/// // you can use `InputKind` or `UserInput` if needed
/// // as unifying types
/// (GamepadButtonType::South, Action::Run),
/// (GamepadButtonType::LeftTrigger, Action::Hide),
/// (GamepadButtonType::RightTrigger, Action::Hide),
/// ]);
///
/// // Insertion
/// input_map.insert(MouseButton::Left, Action::Run)
/// .insert(KeyCode::ShiftLeft, Action::Run)
/// // Chords
/// .insert_modified(Modifier::Control, KeyCode::R, Action::Run)
/// .insert_chord([InputKind::Keyboard(KeyCode::H),
/// InputKind::GamepadButton(GamepadButtonType::South),
/// InputKind::Mouse(MouseButton::Middle)],
/// Action::Run);
///
/// // Removal
/// input_map.clear_action(Action::Hide);
///```
/**
Maps from raw inputs to an input-method agnostic representation

Multiple inputs can be mapped to the same action,
and each input can be mapped to multiple actions.

The provided input types must be able to be converted into a [`UserInput`].

The maximum number of bindings (total) that can be stored for each action is 16.
Insertions will silently fail if you have reached this cap.

By default, if two actions would be triggered by a combination of buttons,
and one combination is a strict subset of the other, only the larger input is registered.
For example, pressing both `S` and `Ctrl + S` in your text editor app would save your file,
but not enter the letters `s`.
Set the [`ClashStrategy`](crate::clashing_inputs::ClashStrategy) resource
to configure this behavior.

# Example
```rust
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;
use leafwing_input_manager::user_input::InputKind;

// You can Run!
// But you can't Hide :(
#[derive(Actionlike, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
enum Action {
Run,
Hide,
}

// Construction
let mut input_map = InputMap::new([
// Note that the type of your iterators must be homogenous;
// you can use `InputKind` or `UserInput` if needed
// as unifying types
(GamepadButtonType::South, Action::Run),
(GamepadButtonType::LeftTrigger, Action::Hide),
(GamepadButtonType::RightTrigger, Action::Hide),
]);

// Insertion
input_map.insert(MouseButton::Left, Action::Run)
.insert(KeyCode::ShiftLeft, Action::Run)
// Chords
.insert_modified(Modifier::Control, KeyCode::R, Action::Run)
.insert_chord([InputKind::Keyboard(KeyCode::H),
InputKind::GamepadButton(GamepadButtonType::South),
InputKind::Mouse(MouseButton::Middle)],
Action::Run);

// Removal
input_map.clear_action(Action::Hide);
```

# Example
```rust
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;
use leafwing_input_manager::user_input::InputKind;

#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)]
enum Action {
Look,
}

fn spawn_player(mut commands: Commands){
commands.spawn(InputManagerBundle::<Action> {
action_state: ActionState::default(),
input_map: InputMap::default()
.insert(DualAxis::left_stick().with_sensitivity(1.0, 1.0), Action::Look)
.insert(DualAxis::mouse_motion().with_sensitivity(1.0, 1.0), Action::Look)
.build(),
});
}

fn change_left_stick_values(mut query: Query<&mut InputMap<Action>>){
let mut input_map = query.single_mut();

// Get the input at the 0 index since the left stick was added first in `Action::Look`
let input = input_map.get_mut(Action::Look).get_at_mut(0).unwrap();

// Some pattern matching is needed to get to the `DualAxis`
if let UserInput::Single(kind) = input{
if let InputKind::DualAxis(dual_axis) = kind{
// Here any value of the left stick `DualAxis` can be changed
dual_axis.x.sensitivity = 0.8;
dual_axis.y.sensitivity = 0.8;
dual_axis.deadzone = DeadZoneShape::Rect { width: 1.0, height: 1.0 }
}
}
}
```
**/
100-TomatoJuice marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Resource, Component, Debug, Clone, PartialEq, Eq, TypeUuid)]
#[uuid = "D7DECC78-8573-42FF-851A-F0344C7D05C9"]
pub struct InputMap<A: Actionlike> {
Expand Down Expand Up @@ -425,6 +466,12 @@ impl<A: Actionlike> InputMap<A> {
&self.map[action.index()]
}

/// Returns the `action` mappings
#[must_use]
pub fn get_mut(&mut self, action: A) -> &mut PetitSet<UserInput, 16> {
&mut self.map[action.index()]
}

/// How many input bindings are registered total?
#[must_use]
pub fn len(&self) -> usize {
Expand Down
4 changes: 2 additions & 2 deletions src/input_streams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ impl<'a> InputStreams<'a> {
if value >= axis.negative_low && value <= axis.positive_low && include_deadzone {
0.0
} else if axis.inverted {
-value
-value * axis.sensitivity
} else {
value
value * axis.sensitivity
}
};

Expand Down
Loading