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

Refactor UserInput and InputKind variants into separate implementations #490

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6d1d202
Reimplement keyboard inputs
Shute052 Feb 23, 2024
9c3697a
Update document
Shute052 Feb 23, 2024
94f6f9b
Migrate to new input processors and refactor the trait
Shute052 Apr 27, 2024
ad9153a
Merge branch 'refs/heads/main' into new-architecture
Shute052 Apr 27, 2024
29195ea
Implement mouse inputs
Shute052 Apr 28, 2024
f18ff69
Prefer `FromIter<*AxisProcessor>` over `From<Vec<*AxisProcessor>>`
Shute052 Apr 29, 2024
a1c166c
Implement `GamepadButtonType` and `GamepadAxisType`
Shute052 Apr 29, 2024
1f1a878
Add a `pipeline` method to create input processing pipelines
Shute052 May 4, 2024
6872e2d
Typo
Shute052 May 4, 2024
72adc5b
Replace old `UserInput` and `InputKind` with the new ones
Shute052 May 4, 2024
5cde427
Merge branch 'refs/heads/pipeline-method' into new-architecture
Shute052 May 4, 2024
5f0d528
RELEASES.md
Shute052 May 4, 2024
4110309
Fix clashing inputs
Shute052 May 4, 2024
7cc1e2c
Merge branch 'refs/heads/main' into new-architecture
Shute052 May 4, 2024
3fc96b4
Typo
Shute052 May 4, 2024
e51967e
Docs
Shute052 May 4, 2024
db95418
Rename
Shute052 May 4, 2024
94c12c5
Docs
Shute052 May 4, 2024
61ab859
Docs
Shute052 May 7, 2024
9689734
RELEASES.md
Shute052 May 7, 2024
c935ce5
Merge branch 'refs/heads/main' into new-architecture
Shute052 May 7, 2024
29adad8
Merge branch 'refs/heads/main' into new-architecture
Shute052 May 8, 2024
49d71ef
Fix wrong gamepad button value
Shute052 Jun 1, 2024
0d5e93e
Rename `InputChord::from_multiple` to `new`
Shute052 Jun 1, 2024
ee51aa1
Merge branch 'refs/heads/main' into new-architecture
Shute052 Jun 1, 2024
07dca4e
Fix ci
Shute052 Jun 1, 2024
23c8a28
Merge branch 'refs/heads/main' into new-architecture
Shute052 Jun 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ and a single input can result in multiple actions being triggered, which can be
- Ergonomic insertion API that seamlessly blends multiple input types for you
- Can't decide between `input_map.insert(Action::Jump, KeyCode::Space)` and `input_map.insert(Action::Jump, GamepadButtonType::South)`? Have both!
- Full support for arbitrary button combinations: chord your heart out.
- `input_map.insert_chord(Action::Console, [KeyCode::ControlLeft, KeyCode::Shift, KeyCode::KeyC])`
- `input_map.insert(Action::Console, InputChord::new([KeyCode::ControlLeft, KeyCode::Shift, KeyCode::KeyC]))`
- Sophisticated input disambiguation with the `ClashStrategy` enum: stop triggering individual buttons when you meant to press a chord!
- Create an arbitrary number of strongly typed disjoint action sets by adding multiple copies of this plugin: decouple your camera and player state
- Local multiplayer support: freely bind keys to distinct entities, rather than worrying about singular global state
Expand Down
81 changes: 71 additions & 10 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,84 @@

### Breaking Changes

- removed `UserInput` and `InputKind` enums in favor of the new `UserInput` trait and its impls (see 'Enhancements: New Inputs' for details).
- renamed `Modifier` enum to `ModifierKey`.
- by default, all input events are unprocessed now, using `With*ProcessingPipelineExt` methods to configure your preferred processing steps.
- applied clashing check to continuous mouse inputs, for example:
- `MouseScrollAxis::Y` will clash with `MouseScrollDirection::UP` and `MouseScrollDirection::DOWN`.
- `MouseMove` will clash with all the two axes and the four directions.
- refactored the method signatures of `InputMap` to fit the new input types.
- removed `InputMap::insert_chord` and `InputMap::insert_modified` due to their limited applicability within the type system.
- the new `InputChord` contructors and builders allow you to define chords with guaranteed type safety.
- the new `ModifierKey::with` method simplifies the creation of input chords that include the modifier and your desired input.
- the `timing` field of the `ActionData` is now disabled by default. Timing information will only be collected
if the `timing` feature is enabled. It is disabled by default because most games don't require timing information.
(how long a button was pressed for)
- removed `ToggleActions` resource in favor of new methods on `ActionState`: `disable_all`, `disable(action)`, `enable_all`, `enable(action)`, and `disabled(action)`.
- removed `InputMap::build` method in favor of new fluent builder pattern (see 'Usability: InputMap' for details).
- renamed `InputMap::which_pressed` method to `process_actions` to better reflect its current functionality for clarity.
- replaced axis-like input handling with new input processors (see 'Enhancements: Input Processors' for details).
- removed `DeadZoneShape` in favor of new dead zone processors.
- removed `DeadZoneShape` in favor of new dead zone processors (see 'Enhancements: Input Processors' for details).
- refactored the fields and methods of `RawInputs` to fit the new input types.
- removed `Direction` type in favor of `bevy::math::primitives::Direction2d`.
- removed the hacky `value` field and `from_value` method from `SingleAxis` and `DualAxis`, in favor of new input mocking.
- removed `MockInput::send_input` methods, in favor of new input mocking APIs (see 'Usability: MockInput' for details).
- made the dependency on bevy's `bevy_gilrs` feature optional.
- it is still enabled by leafwing-input-manager's default features.
- if you're using leafwing-input-manager with `default_features = false`, you can readd it by adding `bevy/bevy_gilrs` as a dependency.

### Enhancements

#### New Inputs

- added `UserInput` trait.
- added `UserInput` impls for gamepad input events:
- implemented `UserInput` for Bevy’s `GamepadAxisType`-related inputs.
- `GamepadStick`: Continuous or discrete movement events of the left or right gamepad stick along both X and Y axes.
- `GamepadControlAxis`: Continuous or discrete movement events of a `GamepadAxisType`.
- `GamepadControlDirection`: Discrete movement direction events of a `GamepadAxisType`, treated as a button press.
- implemented `UserInput` for Bevy’s `GamepadButtonType` directly.
- added `GamepadVirtualAxis`, similar to the old `UserInput::VirtualAxis` using two `GamepadButtonType`s.
- added `GamepadVirtualDPad`, similar to the old `UserInput::VirtualDPad` using four `GamepadButtonType`s.
- added `UserInput` impls for keyboard inputs:
- implemented `UserInput` for Bevy’s `KeyCode` directly.
- implemented `UserInput` for `ModifierKey`.
- added `KeyboardVirtualAxis`, similar to the old `UserInput::VirtualAxis` using two `KeyCode`s.
- added `KeyboardVirtualDPad`, similar to the old `UserInput::VirtualDPad` using four `KeyCode`s.
- added `UserInput` impls for mouse inputs:
- implemented `UserInput` for movement-related inputs.
- `MouseMove`: Continuous or discrete movement events of the mouse both X and Y axes.
- `MouseMoveAxis`: Continuous or discrete movement events of the mouse on an axis, similar to the old `SingleAxis::mouse_motion_*`.
- `MouseMoveDirection`: Discrete movement direction events of the mouse on an axis, similar to the old `MouseMotionDirection`.
- implemented `UserInput` for wheel-related inputs.
- `MouseScroll`: Continuous or discrete movement events of the mouse wheel both X and Y axes.
- `MouseScrollAxis`: Continuous or discrete movement events of the mouse wheel on an axis, similar to the old `SingleAxis::mouse_wheel_*`.
- `MouseScrollDirection`: Discrete movement direction events of the mouse wheel on an axis, similar to the old `MouseWheelDirection`.
- added `InputChord` for combining multiple inputs, similar to the old `UserInput::Chord`.

##### Migration Guide

- the old `SingleAxis` is now:
- `GamepadControlAxis` for gamepad axes.
- `MouseMoveAxis::X` and `MouseMoveAxis::Y` for continuous mouse movement.
- `MouseScrollAxis::X` and `MouseScrollAxis::Y` for continuous mouse wheel movement.
- the old `DualAxis` is now:
- `GamepadStick` for gamepad sticks.
- `MouseMove::default()` for continuous mouse movement.
- `MouseScroll::default()` for continuous mouse wheel movement.
- the old `Modifier` is now `ModifierKey`.
- the old `MouseMotionDirection` is now `MouseMoveDirection`.
- the old `MouseWheelDirection` is now `MouseScrollDirection`.
- the old `UserInput::Chord` is now `InputChord`.
- the old `UserInput::VirtualAxis` is now:
- `GamepadVirtualAxis` for four gamepad buttons.
- `KeyboardVirtualAxis` for four keys.
- `MouseMoveAxis::X.digital()` and `MouseMoveAxis::Y.digital()` for discrete mouse movement.
- `MouseScrollAxis::X.digital()` and `MouseScrollAxis::Y.digital()` for discrete mouse wheel movement.
- the old `UserInput::VirtualDPad` is now:
- `GamepadVirtualDPad` for four gamepad buttons.
- `KeyboardVirtualDPad` for four keys.
- `MouseMove::default().digital()` for discrete mouse movement.
- `MouseScroll::default().digital()` for discrete mouse wheel movement.

#### Input Processors

Input processors allow you to create custom logic for axis-like input manipulation.
Expand Down Expand Up @@ -65,27 +126,27 @@ Input processors allow you to create custom logic for axis-like input manipulati
#### InputMap

- added new fluent builders for creating a new `InputMap<A>` with short configurations:
- `fn with(mut self, action: A, input: impl Into<UserInput>)`.
- `fn with_one_to_many(mut self, action: A, inputs: impl IntoIterator<Item = UserInput>)`.
- `fn with_multiple(mut self, bindings: impl IntoIterator<Item = (A, UserInput)>) -> Self`.
- `fn with(mut self, action: A, input: impl UserInput)`.
- `fn with_one_to_many(mut self, action: A, inputs: impl IntoIterator<Item = impl UserInput>)`.
- `fn with_multiple(mut self, bindings: impl IntoIterator<Item = (A, impl UserInput)>) -> Self`.
- `fn with_gamepad(mut self, gamepad: Gamepad) -> Self`.

- added new iterators over `InputMap<A>`:
- `actions(&self) -> impl Iterator<Item = &A>` for iterating over all registered actions.
- `bindings(&self) -> impl Iterator<Item = (&A, &UserInput)>` for iterating over all registered action-input bindings.
- `bindings(&self) -> impl Iterator<Item = (&A, &dyn UserInput)>` for iterating over all registered action-input bindings.

### MockInput

- added new methods for the `MockInput` trait.
- `fn press_input(&self, input: impl Into<UserInput>)` for simulating button and key presses.
- `fn send_axis_values(&self, input: impl Into<UserInput>, values: impl IntoIterator<Item = f32>)` for sending value changed events to each axis represented by the input.
- `fn press_input(&self, input: impl UserInput)` for simulating button and key presses.
- `fn send_axis_values(&self, input: impl UserInput, values: impl IntoIterator<Item = f32>)` for sending value changed events to each axis represented by the input.
- as well as methods for a specific gamepad.
- implemented the methods for `MutableInputStreams`, `World`, and `App`.

### QueryInput

- added new methods for the `QueryInput` trait.
- `fn read_axis_values(&self, input: impl Into<UserInput>) -> Vec<f32>` to read the values on all axes represented by an input.
- `fn read_axis_values(&self, input: impl UserInput) -> Vec<f32>` to read the values on all axes represented by an input.
- as well as methods for a specific gamepad.
- implemented the methods for `InputStreams`, `World`, and `App`.

Expand Down
4 changes: 2 additions & 2 deletions examples/action_state_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pub enum PlayerAction {

// Exhaustively match `PlayerAction` and define the default bindings to the input
impl PlayerAction {
fn mkb_input_map() -> InputMap<PlayerAction> {
InputMap::new([(Self::Jump, KeyCode::Space)]).with(Self::Move, VirtualDPad::wasd())
fn mkb_input_map() -> InputMap<Self> {
InputMap::new([(Self::Jump, KeyCode::Space)]).with(Self::Move, KeyboardVirtualDPad::WASD)
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/axis_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ struct Player;
fn spawn_player(mut commands: Commands) {
// Describes how to convert from player inputs into those actions
let input_map = InputMap::default()
// Configure the left stick as a dual-axis control
.with(Action::Move, DualAxis::left_stick())
// Let's bind the right gamepad trigger to the throttle action
// Let's bind the left stick for the move action
.with(Action::Move, GamepadStick::LEFT)
// And then bind the right gamepad trigger to the throttle action
.with(Action::Throttle, GamepadButtonType::RightTrigger2)
// And we'll use the right stick's x-axis as a rudder control
.with(
// Add an AxisDeadzone to process horizontal values of the right stick.
// This will trigger if the axis is moved 10% or more in either direction.
Action::Rudder,
SingleAxis::new(GamepadAxisType::RightStickX).with_deadzone_symmetric(0.1),
GamepadControlAxis::RIGHT_X.with_deadzone_symmetric(0.1),
);
commands
.spawn(InputManagerBundle::with_map(input_map))
Expand Down
8 changes: 4 additions & 4 deletions examples/clash_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ fn spawn_input_map(mut commands: Commands) {
// Setting up input mappings in the obvious way
let mut input_map = InputMap::new([(One, Digit1), (Two, Digit2), (Three, Digit3)]);

input_map.insert_chord(OneAndTwo, [Digit1, Digit2]);
input_map.insert_chord(OneAndThree, [Digit1, Digit3]);
input_map.insert_chord(TwoAndThree, [Digit2, Digit3]);
input_map.insert(OneAndTwo, InputChord::new([Digit1, Digit2]));
input_map.insert(OneAndThree, InputChord::new([Digit1, Digit3]));
input_map.insert(TwoAndThree, InputChord::new([Digit2, Digit3]));

input_map.insert_chord(OneAndTwoAndThree, [Digit1, Digit2, Digit3]);
input_map.insert(OneAndTwoAndThree, InputChord::new([Digit1, Digit2, Digit3]));

commands.spawn(InputManagerBundle::with_map(input_map));
}
Expand Down
4 changes: 2 additions & 2 deletions examples/default_controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ impl PlayerAction {
let mut input_map = InputMap::default();

// Default gamepad input bindings
input_map.insert(Self::Run, DualAxis::left_stick());
input_map.insert(Self::Run, GamepadStick::LEFT);
input_map.insert(Self::Jump, GamepadButtonType::South);
input_map.insert(Self::UseItem, GamepadButtonType::RightTrigger2);

// Default kbm input bindings
input_map.insert(Self::Run, VirtualDPad::wasd());
input_map.insert(Self::Run, KeyboardVirtualDPad::WASD);
input_map.insert(Self::Jump, KeyCode::Space);
input_map.insert(Self::UseItem, MouseButton::Left);

Expand Down
4 changes: 2 additions & 2 deletions examples/input_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn spawn_player(mut commands: Commands) {
let input_map = InputMap::default()
.with(
Action::Move,
VirtualDPad::wasd()
KeyboardVirtualDPad::WASD
// You can configure a processing pipeline to handle axis-like user inputs.
//
// This step adds a circular deadzone that normalizes input values
Expand All @@ -39,7 +39,7 @@ fn spawn_player(mut commands: Commands) {
.with(
Action::LookAround,
// You can also use a sequence of processors as the processing pipeline.
DualAxis::mouse_motion().replace_processing_pipeline([
MouseMove::default().replace_processing_pipeline([
// The first processor is a circular deadzone.
CircleDeadZone::new(0.1).into(),
// The next processor doubles inputs normalized by the deadzone.
Expand Down
4 changes: 2 additions & 2 deletions examples/mouse_motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ fn setup(mut commands: Commands) {
let input_map = InputMap::new([
// This will capture the total continuous value, for direct use.
// Note that you can also use discrete gesture-like motion,
// via the `MouseMotionDirection` enum.
(CameraMovement::Pan, DualAxis::mouse_motion()),
// via the `MouseMoveDirection` enum.
(CameraMovement::Pan, MouseMove::default()),
]);
commands
.spawn(Camera2dBundle::default())
Expand Down
17 changes: 8 additions & 9 deletions examples/mouse_wheel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ enum CameraMovement {
fn setup(mut commands: Commands) {
let input_map = InputMap::default()
// This will capture the total continuous value, for direct use.
.with(CameraMovement::Zoom, SingleAxis::mouse_wheel_y())
.with(CameraMovement::Zoom, MouseScrollAxis::Y)
// This will return a binary button-like output.
.with(CameraMovement::PanLeft, MouseWheelDirection::Left)
.with(CameraMovement::PanRight, MouseWheelDirection::Right)
// Alternatively, you could model this as a virtual D-pad.
// It's extremely useful for modeling 4-directional button-like inputs with the mouse wheel
.with(CameraMovement::Pan, VirtualDPad::mouse_wheel())
// Or even a continuous `DualAxis`!
.with(CameraMovement::Pan, DualAxis::mouse_wheel());
.with(CameraMovement::PanLeft, MouseScrollDirection::LEFT)
.with(CameraMovement::PanRight, MouseScrollDirection::RIGHT)
// Alternatively, you could model them as a continuous dual-axis input
.with(CameraMovement::Pan, MouseScroll::default())
// Or even a digital dual-axis input!
.with(CameraMovement::Pan, MouseScroll::default().digital());
commands
.spawn(Camera2dBundle::default())
.insert(InputManagerBundle::with_map(input_map));
Expand Down Expand Up @@ -62,7 +61,7 @@ fn pan_camera(mut query: Query<(&mut Transform, &ActionState<CameraMovement>), W

let (mut camera_transform, action_state) = query.single_mut();

// When using the `MouseWheelDirection` type, mouse wheel inputs can be treated like simple buttons
// When using the `MouseScrollDirection` type, mouse wheel inputs can be treated like simple buttons
if action_state.pressed(&CameraMovement::PanLeft) {
camera_transform.translation.x -= CAMERA_PAN_RATE;
}
Expand Down
8 changes: 4 additions & 4 deletions examples/twin_stick_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ impl PlayerAction {
let mut input_map = InputMap::default();

// Default gamepad input bindings
input_map.insert(Self::Move, DualAxis::left_stick());
input_map.insert(Self::Look, DualAxis::right_stick());
input_map.insert(Self::Move, GamepadStick::LEFT);
input_map.insert(Self::Look, GamepadStick::RIGHT);
input_map.insert(Self::Shoot, GamepadButtonType::RightTrigger);

// Default kbm input bindings
input_map.insert(Self::Move, VirtualDPad::wasd());
input_map.insert(Self::Look, VirtualDPad::arrow_keys());
input_map.insert(Self::Move, KeyboardVirtualDPad::WASD);
input_map.insert(Self::Look, KeyboardVirtualDPad::ARROW_KEYS);
input_map.insert(Self::Shoot, MouseButton::Left);

input_map
Expand Down
8 changes: 6 additions & 2 deletions examples/virtual_dpad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ struct Player;

fn spawn_player(mut commands: Commands) {
// Stores "which actions are currently activated"
// Map some arbitrary keys into a virtual direction pad that triggers our move action
let input_map = InputMap::new([(Action::Move, VirtualDPad::wasd())]);
let input_map = InputMap::new([(
Action::Move,
// Define a virtual D-pad using four arbitrary keys.
// You can also use GamepadVirtualDPad to create similar ones using gamepad buttons.
KeyboardVirtualDPad::new(KeyCode::KeyW, KeyCode::KeyS, KeyCode::KeyA, KeyCode::KeyD),
)]);
commands
.spawn(InputManagerBundle::with_map(input_map))
.insert(Player);
Expand Down
16 changes: 7 additions & 9 deletions src/action_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,9 @@ impl<A: Actionlike> ActionState<A> {
///
/// - 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
/// - 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/RT
/// triggers and the Playstation L2/R2 triggers. See also the `axis_inputs` example in the
/// repository.
Expand Down Expand Up @@ -328,11 +328,8 @@ impl<A: Actionlike> ActionState<A> {

/// Get the [`DualAxisData`] from the binding that triggered the corresponding `action`.
///
/// Only certain events such as [`VirtualDPad`][crate::axislike::VirtualDPad] and
/// [`DualAxis`][crate::axislike::DualAxis] provide an [`DualAxisData`], and this
/// will return [`None`] for other events.
///
/// Chord inputs will return the [`DualAxisData`] of it's first input.
/// Only events that represent dual-axis control provide an [`DualAxisData`],
/// and this will return [`None`] for other events.
///
/// If multiple inputs with an axis pair trigger the same game action at the same time, the
/// value of each axis pair will be added together.
Expand Down Expand Up @@ -720,6 +717,7 @@ mod tests {
use crate::input_map::InputMap;
use crate::input_mocking::MockInput;
use crate::input_streams::InputStreams;
use crate::prelude::InputChord;
use bevy::input::InputPlugin;
use bevy::prelude::*;
use bevy::utils::{Duration, Instant};
Expand Down Expand Up @@ -811,7 +809,7 @@ mod tests {
let mut input_map = InputMap::default();
input_map.insert(Action::One, Digit1);
input_map.insert(Action::Two, Digit2);
input_map.insert_chord(Action::OneAndTwo, [Digit1, Digit2]);
input_map.insert(Action::OneAndTwo, InputChord::new([Digit1, Digit2]));

let mut app = App::new();
app.add_plugins(InputPlugin);
Expand Down
Loading