Skip to content

Commit

Permalink
Migrate to new input processors and refactor the trait
Browse files Browse the repository at this point in the history
  • Loading branch information
Shute052 committed Apr 27, 2024
1 parent 9c3697a commit 94f6f9b
Show file tree
Hide file tree
Showing 51 changed files with 5,314 additions and 1,191 deletions.
14 changes: 9 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ opt-level = 3
members = ["./", "tools/ci", "macros"]

[features]
default = ['asset', 'ui']
default = ['asset', 'ui', 'bevy/bevy_gilrs']

# Allow using the `InputMap` as `bevy::asset::Asset`.
asset = ['bevy/bevy_asset']
Expand All @@ -40,16 +40,20 @@ egui = ['dep:bevy_egui']
leafwing_input_manager_macros = { path = "macros", version = "0.13" }
bevy = { version = "0.13", default-features = false, features = [
"serialize",
"bevy_gilrs",
] }
bevy_egui = { version = "0.25", optional = true }
bevy_egui = { version = "0.27", optional = true }

derive_more = { version = "0.99", default-features = false, features = [
"display",
"error",
] }
itertools = "0.12"
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_flexitos = "0.2"
dyn-clone = "1.0"
dyn-eq = "0.1"
dyn-hash = "0.2"
once_cell = "1.19"

[dev-dependencies]
bevy = { version = "0.13", default-features = false, features = [
Expand All @@ -61,7 +65,7 @@ bevy = { version = "0.13", default-features = false, features = [
"bevy_core_pipeline",
"x11",
] }
bevy_egui = "0.25"
bevy_egui = "0.27"
serde_test = "1.0"
criterion = "0.5"

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ fn jump(query: Query<&ActionState<Action>, With<Player>>) {
}
```

This snippet is the `minimal.rs` example from the [`examples`](https://github.com/Leafwing-Studios/leafwing-input-manager/tree/latest/examples) folder: check there for more in-depth learning materials!
This snippet is the `minimal.rs` example from the [`examples`](https://github.com/Leafwing-Studios/leafwing-input-manager/tree/main/examples) folder: check there for more in-depth learning materials!

## Crate Feature Flags

This crate has four feature flags: `asset`, `ui`, `no_ui_priority`, and `egui`.
Please refer to the `[features]` section in the [`Cargo.toml`](https://github.com/Leafwing-Studios/leafwing-input-manager/tree/latest/Cargo.toml) for detailed information about their configurations.
Please refer to the `[features]` section in the [`Cargo.toml`](https://github.com/Leafwing-Studios/leafwing-input-manager/tree/main/Cargo.toml) for detailed information about their configurations.
49 changes: 47 additions & 2 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,51 @@
### 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.
- 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.

### Bugs

- fixed a bug in `InputStreams::button_pressed()` where unrelated gamepads were not filtered out when an `associated_gamepad` is defined.

## Version 0.13.3

Expand Down Expand Up @@ -81,7 +126,7 @@

- improved deadzone handling for both `DualAxis` and `SingleAxis` deadzones
- all deadzones now scale the input so that it is continuous.
- `DeadZoneShape::Cross` handles each axis seperately, making a per-axis "snapping" effect
- `DeadZoneShape::Cross` handles each axis separately, making a per-axis "snapping" effect
- an input that falls on the exact boundary of a deadzone is now considered inside it
- added support in `ActionDiff` for value and axis_pair changes

Expand Down Expand Up @@ -449,7 +494,7 @@
- `InputManagerPlugin` now works even if some input stream resources are missing
- added the `input_pressed` method to `InputMap`, to check if a single input is pressed
- renamed `InputMap::assign_gamepad` to `InputMap::set_gamepad` for consistency and clarity (it does not uniquely assign a gamepad)
- removed `strum` dependency by reimplementing the funcitonality, allowing users to define actions with only the `Actionlike` trait
- removed `strum` dependency by reimplementing the functionality, allowing users to define actions with only the `Actionlike` trait
- added the `get_at` and `index` methods on the `Actionlike` trait, allowing you to fetch a specific action by its position in the defining enum and vice versa
- `Copy` bound on `Actionlike` trait relaxed to `Clone`, allowing you to store non-copy data in your enum variants
- `Clone`, `PartialEq` and `Debug` trait impls for `ActionState`
Expand Down
2 changes: 1 addition & 1 deletion examples/action_state_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! and include it as a resource in a bevy app.
use bevy::prelude::*;
use leafwing_input_manager::{prelude::*, user_input::InputKind};
use leafwing_input_manager::prelude::*;

fn main() {
App::new()
Expand Down
6 changes: 3 additions & 3 deletions examples/arpg_indirection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,19 @@ fn spawn_player(mut commands: Commands) {

fn copy_action_state(
mut query: Query<(
&ActionState<Slot>,
&mut ActionState<Slot>,
&mut ActionState<Ability>,
&AbilitySlotMap,
)>,
) {
for (slot_state, mut ability_state, ability_slot_map) in query.iter_mut() {
for (mut slot_state, mut ability_state, ability_slot_map) in query.iter_mut() {
for slot in Slot::variants() {
if let Some(matching_ability) = ability_slot_map.get(&slot) {
// This copies the `ActionData` between the ActionStates,
// including information about how long the buttons have been pressed or released
ability_state.set_action_data(
*matching_ability,
slot_state.action_data(&slot).unwrap().clone(),
slot_state.action_data_mut_or_default(&slot).clone(),
);
}
}
Expand Down
4 changes: 3 additions & 1 deletion examples/axis_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ fn spawn_player(mut commands: Commands) {
.insert(Action::Throttle, GamepadButtonType::RightTrigger2)
// And we'll use the right stick's x-axis as a rudder control
.insert(
// 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::symmetric(GamepadAxisType::RightStickX, 0.1),
SingleAxis::new(GamepadAxisType::RightStickX)
.with_processor(AxisDeadZone::magnitude(0.1)),
)
.build();
commands
Expand Down
1 change: 0 additions & 1 deletion examples/clash_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
//! See [`ClashStrategy`] for more details.
use bevy::prelude::*;
use leafwing_input_manager::clashing_inputs::ClashStrategy;
use leafwing_input_manager::prelude::*;

fn main() {
Expand Down
1 change: 0 additions & 1 deletion examples/consuming_actions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Demonstrates how to "consume" actions, so they can only be responded to by a single system
use bevy::ecs::system::Resource;
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;

Expand Down
4 changes: 2 additions & 2 deletions examples/default_controls.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Demonstrates how to create default controls for an `Actionlike` and add it to an `InputMap`
use bevy::prelude::*;
use leafwing_input_manager::{prelude::*, user_input::InputKind};
use leafwing_input_manager::prelude::*;

fn main() {
App::new()
Expand All @@ -20,7 +20,7 @@ enum PlayerAction {
}

impl PlayerAction {
/// Define the default binding to the input
/// Define the default bindings to the input
fn default_input_map() -> InputMap<Self> {
let mut input_map = InputMap::default();

Expand Down
69 changes: 69 additions & 0 deletions examples/input_processing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(InputManagerPlugin::<Action>::default())
.add_systems(Startup, spawn_player)
.add_systems(Update, check_data)
.run();
}

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

#[derive(Component)]
struct Player;

fn spawn_player(mut commands: Commands) {
let mut input_map = InputMap::default();
input_map
.insert(
Action::Move,
VirtualDPad::wasd()
// You can add a processor to handle axis-like user inputs by using the `with_processor`.
//
// This processor is a circular deadzone that normalizes input values
// by clamping their magnitude to a maximum of 1.0,
// excluding those with a magnitude less than 0.1,
// and scaling other values linearly in between.
.with_processor(CircleDeadZone::new(0.1))
// Followed by appending Y-axis inversion for the next processing step.
.with_processor(DualAxisInverted::ONLY_Y),
)
.insert(
Action::Move,
DualAxis::left_stick()
// You can replace the currently used processor with another processor.
.replace_processor(CircleDeadZone::default())
// Or remove the processor directly, leaving no processor applied.
.no_processor(),
)
.insert(
Action::LookAround,
// You can also use a sequence of processors as the processing pipeline.
DualAxis::mouse_motion().replace_processor(DualAxisProcessor::from(vec![
// The first processor is a circular deadzone.
CircleDeadZone::new(0.1).into(),
// The next processor doubles inputs normalized by the deadzone.
DualAxisSensitivity::all(2.0).into(),
])),
);
commands
.spawn(InputManagerBundle::with_map(input_map))
.insert(Player);
}

fn check_data(query: Query<&ActionState<Action>, With<Player>>) {
let action_state = query.single();
for action in action_state.get_pressed() {
println!(
"Pressed {action:?}! Its data: {:?}",
action_state.axis_pair(&action)
);
}
}
3 changes: 1 addition & 2 deletions examples/mouse_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ fn update_cursor_state_from_window(

if let Some(val) = window.cursor_position() {
action_state
.action_data_mut(&driver.action)
.unwrap()
.action_data_mut_or_default(&driver.action)
.axis_pair = Some(DualAxisData::from_xy(val));
}
}
Expand Down
12 changes: 11 additions & 1 deletion examples/multiplayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fn main() {
.add_plugins(DefaultPlugins)
.add_plugins(InputManagerPlugin::<Action>::default())
.add_systems(Startup, spawn_players)
.add_systems(Update, move_players)
.run();
}

Expand All @@ -16,7 +17,7 @@ enum Action {
Jump,
}

#[derive(Component)]
#[derive(Component, Debug)]
enum Player {
One,
Two,
Expand Down Expand Up @@ -75,3 +76,12 @@ fn spawn_players(mut commands: Commands) {
input_manager: InputManagerBundle::with_map(PlayerBundle::input_map(Player::Two)),
});
}

fn move_players(player_query: Query<(&Player, &ActionState<Action>)>) {
for (player, action_state) in player_query.iter() {
let actions = action_state.get_just_pressed();
if !actions.is_empty() {
info!("Player {player:?} performed actions {actions:?}");
}
}
}
6 changes: 1 addition & 5 deletions examples/send_actions_over_network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! Note that [`ActionState`] can also be serialized and sent directly.
//! This approach will be less bandwidth efficient, but involve less complexity and CPU work.
use bevy::ecs::event::{Events, ManualEventReader};
use bevy::ecs::event::ManualEventReader;
use bevy::input::InputPlugin;
use bevy::prelude::*;
use leafwing_input_manager::action_diff::ActionDiffEvent;
Expand All @@ -23,10 +23,6 @@ enum FpsAction {
Shoot,
}

/// This identifier uniquely identifies entities across the network
#[derive(Component, Clone, PartialEq, Eq, Hash, Debug)]
struct StableId(u64);

/// Processes an [`Events`] stream of [`ActionDiff`] to update an [`ActionState`]
///
/// In a real scenario, you would have to map the entities between the server and client world.
Expand Down
2 changes: 1 addition & 1 deletion examples/twin_stick_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub enum PlayerAction {
}

impl PlayerAction {
/// Define the default binding to the input
/// Define the default bindings to the input
fn default_input_map() -> InputMap<Self> {
let mut input_map = InputMap::default();

Expand Down
1 change: 1 addition & 0 deletions examples/virtual_dpad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn spawn_player(mut commands: Commands) {
down: KeyCode::KeyS.into(),
left: KeyCode::KeyA.into(),
right: KeyCode::KeyD.into(),
processor: DualAxisProcessor::None,
},
)]);
commands
Expand Down
26 changes: 3 additions & 23 deletions macros/src/actionlike.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use proc_macro2::Span;
use crate::utils;
use proc_macro2::TokenStream;
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{DeriveInput, Ident};
use syn::DeriveInput;

/// This approach and implementation is inspired by the `strum` crate,
/// Copyright (c) 2019 Peter Glotfelty
Expand All @@ -13,26 +12,7 @@ pub(crate) fn actionlike_inner(ast: &DeriveInput) -> TokenStream {
let enum_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();

let crate_path = if let Ok(found_crate) = crate_name("leafwing_input_manager") {
// The crate was found in the Cargo.toml
match found_crate {
FoundCrate::Itself => quote!(leafwing_input_manager),
FoundCrate::Name(name) => {
let ident = Ident::new(&name, Span::call_site());
quote!(#ident)
}
}
} else {
// The crate was not found in the Cargo.toml,
// so we assume that we are in the owning_crate itself
//
// In order for this to play nicely with unit tests within the crate itself,
// `use crate as leafwing_input_manager` at the top of each test module
//
// Note that doc tests, integration tests and examples want the full standard import,
// as they are evaluated as if they were external
quote!(leafwing_input_manager)
};
let crate_path = utils::crate_path();

quote! {
impl #impl_generics #crate_path::Actionlike for #enum_name #type_generics #where_clause {}
Expand Down
Loading

0 comments on commit 94f6f9b

Please sign in to comment.