Skip to content

Commit

Permalink
Replace old UserInput and InputKind with the new ones
Browse files Browse the repository at this point in the history
  • Loading branch information
Shute052 committed May 4, 2024
1 parent a1c166c commit 72adc5b
Show file tree
Hide file tree
Showing 49 changed files with 1,837 additions and 4,204 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
toolchain: stable
components: rustfmt, clippy
- name: Cache Cargo build files
uses: Leafwing-Studios/cargo-cache@v1.1.0
uses: Leafwing-Studios/cargo-cache@v1.2.0
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
- name: CI job
Expand All @@ -34,7 +34,7 @@ jobs:
with:
toolchain: stable
- name: Cache Cargo build files
uses: Leafwing-Studios/cargo-cache@v1.1.0
uses: Leafwing-Studios/cargo-cache@v1.2.0
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
- name: Build & run tests
Expand All @@ -51,7 +51,7 @@ jobs:
with:
toolchain: stable
- name: Cache Cargo build files
uses: Leafwing-Studios/cargo-cache@v1.1.0
uses: Leafwing-Studios/cargo-cache@v1.2.0
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
- name: Check Compile
Expand All @@ -66,7 +66,7 @@ jobs:
with:
toolchain: stable
- name: Cache Cargo build files
uses: Leafwing-Studios/cargo-cache@v1.1.0
uses: Leafwing-Studios/cargo-cache@v1.2.0
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
if: runner.os == 'linux'
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ and a single input can result in multiple actions being triggered, which can be

- Full keyboard, mouse and joystick support for button-like and axis inputs
- Dual axis support for analog inputs from gamepads and joysticks
- Bind arbitrary button inputs into virtual DPads
- Bind arbitrary button inputs into virtual D-Pads
- Effortlessly wire UI buttons to game state with one simple component!
- When clicked, your button will press the appropriate action on the corresponding entity
- Store all your input mappings in a single `InputMap` component
Expand All @@ -36,13 +36,13 @@ 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::multiple([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
- Networked multiplayer support: serializable structs, and a space-conscious `ActionDiff` representation to send on the wire
- Powerful and easy-to-use input mocking API for integration testing your Bevy applications
- `app.send_input(KeyCode::KeyB)` or `world.send_input(UserInput::chord([KeyCode::KeyB, KeyCode::KeyE, KeyCode::KeyV, KeyCode::KeyY])`
- `app.press_input(KeyCode::KeyB)` or `world.press_input(UserInput::chord([KeyCode::KeyB, KeyCode::KeyE, KeyCode::KeyV, KeyCode::KeyY])`
- Control which state this plugin is active in: stop wandering around while in a menu!
- Leafwing Studio's trademark `#![forbid(missing_docs)]`

Expand Down
2 changes: 1 addition & 1 deletion RELEASE-CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

1. Ensure that `reset_inputs` for `MutableInputStreams` is resetting all relevant fields.
2. Ensure that `RawInputs` struct has fields that cover all necessary input types.
3. Ensure that `send_input` and `release_input` check all possible fields on `RawInputs`.
3. Ensure that `press_input`, `send_axis_values` and `release_input` check all possible fields on `RawInputs`.

## Before release

Expand Down
104 changes: 66 additions & 38 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,75 @@
### Breaking Changes

- removed `Direction` type in favor of `bevy::math::primitives::Direction2d`.
- added input processors for `SingleAxis`, `DualAxis`, `VirtualAxis`, and `VirtualDpad` to refine input values:
- added processor enums:
- `AxisProcessor`: Handles single-axis values.
- `DualAxisProcessor`: Handles dual-axis values.
- added processor traits for defining custom processors:
- `CustomAxisProcessor`: Handles single-axis values.
- `CustomDualAxisProcessor`: Handles dual-axis values.
- added built-in processor variants (no variant versions implemented `Into<Processor>`):
- Pipelines: Handle input values sequentially through a sequence of processors.
- `AxisProcessor::Pipeline`: Pipeline for single-axis inputs.
- `DualAxisProcessor::Pipeline`: Pipeline for dual-axis inputs.
- you can also create them by these methods:
- `AxisProcessor::with_processor` or `From<Vec<AxisProcessor>>::from` for `AxisProcessor::Pipeline`.
- `DualAxisProcessor::with_processor` or `From<Vec<DualAxisProcessor>>::from` for `DualAxisProcessor::Pipeline`.
- Inversion: Reverses control (positive becomes negative, etc.)
- `AxisProcessor::Inverted`: Single-axis inversion.
- `DualAxisInverted`: Dual-axis inversion, implemented `Into<DualAxisProcessor>`.
- Sensitivity: Adjusts control responsiveness (doubling, halving, etc.).
- `AxisProcessor::Sensitivity`: Single-axis scaling.
- `DualAxisSensitivity`: Dual-axis scaling, implemented `Into<DualAxisProcessor>`.
- Value Bounds: Define the boundaries for constraining input values.
- `AxisBounds`: Restricts single-axis values to a range, implemented `Into<AxisProcessor>` and `Into<DualAxisProcessor>`.
- `DualAxisBounds`: Restricts single-axis values to a range along each axis, implemented `Into<DualAxisProcessor>`.
- `CircleBounds`: Limits dual-axis values to a maximum magnitude, implemented `Into<DualAxisProcessor>`.
- Deadzones: Ignores near-zero values, treating them as zero.
- Unscaled versions:
- `AxisExclusion`: Excludes small single-axis values, implemented `Into<AxisProcessor>` and `Into<DualAxisProcessor>`.
- `DualAxisExclusion`: Excludes small dual-axis values along each axis, implemented `Into<DualAxisProcessor>`.
- `CircleExclusion`: Excludes dual-axis values below a specified magnitude threshold, implemented `Into<DualAxisProcessor>`.
- Scaled versions:
- `AxisDeadZone`: Normalizes single-axis values based on `AxisExclusion` and `AxisBounds::default`, implemented `Into<AxisProcessor>` and `Into<DualAxisProcessor>`.
- `DualAxisDeadZone`: Normalizes dual-axis values based on `DualAxisExclusion` and `DualAxisBounds::default`, implemented `Into<DualAxisProcessor>`.
- `CircleDeadZone`: Normalizes dual-axis values based on `CircleExclusion` and `CircleBounds::default`, implemented `Into<DualAxisProcessor>`.
- removed `DeadZoneShape`.
- removed functions for inverting, adjusting sensitivity, and creating deadzones from `SingleAxis` and `DualAxis`.
- added `with_processor`, `replace_processor`, and `no_processor` to manage processors for `SingleAxis`, `DualAxis`, `VirtualAxis`, and `VirtualDpad`.
- added App extensions: `register_axis_processor` and `register_dual_axis_processor` for registration of processors.
- added `serde_typetag` procedural macro attribute for processor type tagging.
- replaced axis-like input handling with new input processors (see 'Enhancements: Input Processors' for details).
- removed `DeadZoneShape` in favor of new dead zone processors.
- 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.
- 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.
- split `MockInput::send_input` method to two methods:
- `fn press_input(&self, input: UserInput)` for focusing on simulating button and key presses.
- `fn send_axis_values(&self, input: UserInput, values: impl IntoIterator<Item = f32>)` for sending value changed events to each axis of the input.
- removed the hacky `value` field and `from_value` method from `SingleAxis` and `DualAxis`, in favor of new input mocking.

### Enhancements

#### Input Processors

Input processors allow you to create custom logic for axis-like input manipulation.

- added processor enums:
- `AxisProcessor`: Handles single-axis values.
- `DualAxisProcessor`: Handles dual-axis values.
- added processor traits for defining custom processors:
- `CustomAxisProcessor`: Handles single-axis values.
- `CustomDualAxisProcessor`: Handles dual-axis values.
- implemented `WithAxisProcessorExt` to manage processors for `SingleAxis` and `VirtualAxis`.
- implemented `WithDualAxisProcessorExt` to manage processors for `DualAxis` and `VirtualDpad`.
- added App extensions: `register_axis_processor` and `register_dual_axis_processor` for registration of processors.
- added built-in processors (variants of processor enums and `Into<Processor>` implementors):
- Pipelines: Handle input values sequentially through a sequence of processors.
- `AxisProcessor::Pipeline`: Pipeline for single-axis inputs.
- `DualAxisProcessor::Pipeline`: Pipeline for dual-axis inputs.
- you can also create them by these methods:
- `AxisProcessor::pipeline` or `AxisProcessor::with_processor` for `AxisProcessor::Pipeline`.
- `DualAxisProcessor::pipeline` or `DualAxisProcessor::with_processor` for `DualAxisProcessor::Pipeline`.
- Inversion: Reverses control (positive becomes negative, etc.)
- `AxisProcessor::Inverted`: Single-axis inversion.
- `DualAxisInverted`: Dual-axis inversion, implemented `Into<DualAxisProcessor>`.
- Sensitivity: Adjusts control responsiveness (doubling, halving, etc.).
- `AxisProcessor::Sensitivity`: Single-axis scaling.
- `DualAxisSensitivity`: Dual-axis scaling, implemented `Into<DualAxisProcessor>`.
- Value Bounds: Define the boundaries for constraining input values.
- `AxisBounds`: Restricts single-axis values to a range, implemented `Into<AxisProcessor>` and `Into<DualAxisProcessor>`.
- `DualAxisBounds`: Restricts single-axis values to a range along each axis, implemented `Into<DualAxisProcessor>`.
- `CircleBounds`: Limits dual-axis values to a maximum magnitude, implemented `Into<DualAxisProcessor>`.
- Deadzones: Ignores near-zero values, treating them as zero.
- Unscaled versions:
- `AxisExclusion`: Excludes small single-axis values, implemented `Into<AxisProcessor>` and `Into<DualAxisProcessor>`.
- `DualAxisExclusion`: Excludes small dual-axis values along each axis, implemented `Into<DualAxisProcessor>`.
- `CircleExclusion`: Excludes dual-axis values below a specified magnitude threshold, implemented `Into<DualAxisProcessor>`.
- Scaled versions:
- `AxisDeadZone`: Normalizes single-axis values based on `AxisExclusion` and `AxisBounds::default`, implemented `Into<AxisProcessor>` and `Into<DualAxisProcessor>`.
- `DualAxisDeadZone`: Normalizes dual-axis values based on `DualAxisExclusion` and `DualAxisBounds::default`, implemented `Into<DualAxisProcessor>`.
- `CircleDeadZone`: Normalizes dual-axis values based on `CircleExclusion` and `CircleBounds::default`, implemented `Into<DualAxisProcessor>`.

### Usability

#### InputMap

Introduce new fluent builders for creating a new `InputMap<A>` with short configurations:

- `fn with(mut self, action: A, input: impl Into<Item = 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_gamepad(mut self, gamepad: Gamepad) -> Self`.

Introduce new iterators over `InputMap<A>`:

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

### Bugs

Expand Down
27 changes: 13 additions & 14 deletions benches/input_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,16 @@ fn construct_input_map_from_iter() -> InputMap<TestAction> {
fn construct_input_map_from_chained_calls() -> InputMap<TestAction> {
black_box(
InputMap::default()
.insert(TestAction::A, KeyCode::KeyA)
.insert(TestAction::B, KeyCode::KeyB)
.insert(TestAction::C, KeyCode::KeyC)
.insert(TestAction::D, KeyCode::KeyD)
.insert(TestAction::E, KeyCode::KeyE)
.insert(TestAction::F, KeyCode::KeyF)
.insert(TestAction::G, KeyCode::KeyG)
.insert(TestAction::H, KeyCode::KeyH)
.insert(TestAction::I, KeyCode::KeyI)
.insert(TestAction::J, KeyCode::KeyJ)
.build(),
.with(TestAction::A, KeyCode::KeyA)
.with(TestAction::B, KeyCode::KeyB)
.with(TestAction::C, KeyCode::KeyC)
.with(TestAction::D, KeyCode::KeyD)
.with(TestAction::E, KeyCode::KeyE)
.with(TestAction::F, KeyCode::KeyF)
.with(TestAction::G, KeyCode::KeyG)
.with(TestAction::H, KeyCode::KeyH)
.with(TestAction::I, KeyCode::KeyI)
.with(TestAction::J, KeyCode::KeyJ),
)
}

Expand All @@ -63,7 +62,7 @@ fn which_pressed(
clash_strategy: ClashStrategy,
) -> HashMap<TestAction, ActionData> {
let input_map = construct_input_map_from_iter();
input_map.which_pressed(input_streams, clash_strategy)
input_map.process_actions(input_streams, clash_strategy)
}

pub fn criterion_benchmark(c: &mut Criterion) {
Expand All @@ -78,8 +77,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
// Constructing our test app / input stream outside the timed benchmark
let mut app = App::new();
app.add_plugins(InputPlugin);
app.send_input(KeyCode::KeyA);
app.send_input(KeyCode::KeyB);
app.press_input(KeyCode::KeyA);
app.press_input(KeyCode::KeyB);
app.update();

let input_streams = InputStreams::from_world(&app.world, None);
Expand Down
10 changes: 3 additions & 7 deletions examples/action_state_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,10 @@ pub enum PlayerAction {
Jump,
}

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

Expand Down
5 changes: 2 additions & 3 deletions examples/arpg_indirection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,8 @@ fn spawn_player(mut commands: Commands) {
(Slot::Ability3, KeyE),
(Slot::Ability4, KeyR),
])
.insert(Slot::Primary, MouseButton::Left)
.insert(Slot::Secondary, MouseButton::Right)
.build(),
.with(Slot::Primary, MouseButton::Left)
.with(Slot::Secondary, MouseButton::Right),
slot_action_state: ActionState::default(),
ability_action_state: ActionState::default(),
ability_slot_map,
Expand Down
14 changes: 6 additions & 8 deletions examples/axis_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,17 @@ 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
.insert(Action::Move, DualAxis::left_stick())
// Use the left stick for the move action
.with(Action::Move, GamepadStick::LEFT)
// Let's bind the right gamepad trigger to the throttle action
.insert(Action::Throttle, GamepadButtonType::RightTrigger2)
.with(Action::Throttle, GamepadButtonType::RightTrigger2)
// And we'll use the right stick's x-axis as a rudder control
.insert(
.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_processor(AxisDeadZone::magnitude(0.1)),
)
.build();
GamepadControlAxis::RIGHT_X.with_deadzone_symmetric(0.1),
);
commands
.spawn(InputManagerBundle::with_map(input_map))
.insert(Player);
Expand Down
15 changes: 8 additions & 7 deletions examples/clash_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@ fn spawn_input_map(mut commands: Commands) {
use KeyCode::*;
use TestAction::*;

let mut input_map = InputMap::default();

// Setting up input mappings in the obvious way
input_map.insert_multiple([(One, Digit1), (Two, Digit2), (Three, Digit3)]);
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::from_multiple([Digit1, Digit2]));
input_map.insert(OneAndThree, InputChord::from_multiple([Digit1, Digit3]));
input_map.insert(TwoAndThree, InputChord::from_multiple([Digit2, Digit3]));

input_map.insert_chord(OneAndTwoAndThree, [Digit1, Digit2, Digit3]);
input_map.insert(
OneAndTwoAndThree,
InputChord::from_multiple([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
Loading

0 comments on commit 72adc5b

Please sign in to comment.