Expand description
Input manager for Bevy, inspired by Unreal Engine Enhanced Input.
§Quick start
§Prelude
We provide a prelude
module, which exports most of the typically used traits and types.
§Plugins
Add EnhancedInputPlugin
to your app:
use bevy::prelude::*;
use bevy_enhanced_input::prelude::*;
let mut app = App::new();
app.add_plugins((MinimalPlugins, EnhancedInputPlugin));
§Defining actions
Actions represent something that the user can do, like “Crouch” or “Fire Weapon”. They are
represented by unit structs that implement the InputAction
trait. Each action has an
associated InputAction::Output
type. It’s the value the action outputs when you assign
bindings to it. More on that later.
To implement the trait, you can use the provided derive macro.
#[derive(Debug, InputAction)]
#[input_action(output = bool)]
struct Jump;
#[derive(Debug, InputAction)]
#[input_action(output = Vec2)]
struct Move;
We also require Debug
, which we use for logging. The output
is the only required parameter.
you can provide additional parameters, see the InputAction
documentation.
§Defining input contexts
Input contexts are a collection of actions that represents a certain context that the player can be in, like “In car” or “On foot”. They describe the rules for what triggers a given action. Contexts can be dynamically added, removed, or prioritized for each user. Depending on your type of game, you may have a single global context or multiple contexts for different gameplay states.
Input contexts are represented by unit structs that implement the InputContext
trait. You can use the provided derive
macro for this. Unlike actions, all contexts need to be registered in the app using InputContextAppExt::add_input_context
.
app.add_input_context::<OnFoot>();
#[derive(InputContext)]
struct OnFoot;
You can provide additional parameters, see the InputContext
documentation.
§Binding actions
Actions must be bound to inputs such as gamepad or keyboard. While input contexts are defined statically at compile
time, bindings must be assigned at runtime. They’re stored inside the Actions<C>
component, where C
is an input
context. Contexts becomes active only when its component exists on an entity. You can attach multiple Actions<C>
components to a single entity.
To bind actions, use Actions::bind
followed by one or more ActionBinding::to
calls to define inputs. You can pass any
input type that implements IntoBindings
, including tuples for multiple inputs (similar to App::add_systems
in Bevy).
All assigned inputs will be treated as “any of”. See ActionBinding::to
for details.
let mut actions = Actions::<OnFoot>::default();
// The action will trigger when space or gamepad south button is pressed.
actions.bind::<Jump>().to((KeyCode::Space, GamepadButton::South));
§Input modifiers
Actions know nothing about input sources and simply convert raw values into the InputAction::Output
. Input is read as the
ActionValue
enum, with the variant depending on the input source. For example, key inputs are captured as bool
, but
if your action’s output type is Vec2
, the value will be assigned to the X axis. See the Input
documentation for how each
source is captured and ActionValue::convert
for details about how values are converted.
However, you might want to apply preprocessing first. For example, invert values, apply sensitivity, or remap axes. This is
where input modifiers come in. They represented as structs that implement the InputModifier
trait.
You can attach modifiers to inputs using BindingBuilder::with_modifiers
. Thanks to traits, this works with any input type.
You can also attach modifiers globally via ActionBinding::with_modifiers
, which applies to all inputs. For details about
how multiple modifiers are merged together, see the ActionBinding
documentation. Both methods also support tuple syntax
for assigning multiple modifiers at once.
actions
.bind::<Move>()
.to((
// Keyboard keys captured as `bool`, but the output of `Move` is defined as `Vec2`,
// so you need to assign keys to axes using swizzle to reorder them and negation.
KeyCode::KeyW.with_modifiers(SwizzleAxis::YXZ),
KeyCode::KeyA.with_modifiers(Negate::all()),
KeyCode::KeyS.with_modifiers((Negate::all(), SwizzleAxis::YXZ)),
KeyCode::KeyD,
// In Bevy sticks split by axes and captured as 1-dimensional inputs,
// so Y stick needs to be sweezled into Y axis.
GamepadAxis::LeftStickX,
GamepadAxis::LeftStickY.with_modifiers(SwizzleAxis::YXZ),
))
.with_modifiers((
// Modifiers applied at the action level.
DeadZone::default(), // Normalizes movement.
SmoothNudge::default(), // Smoothes movement.
));
You can also attach modifiers to input tuples using IntoBindings::with_modifiers_each
. It works similarly to
IntoScheduleConfigs::distributive_run_if
in Bevy.
§Presets
Some bindings are very common. It would be inconvenient to bind WASD and sticks like in the example above every time.
To solve this, we provide presets - structs that store bindings and apply predefined modifiers.
They implement IntoBindings
, so you can pass them directly into ActionBinding::to
.
For example, you can use Cardinal
and Axial
presets to simplify the example above.
// We provide a `with_wasd` method for quick prototyping, but you can
// construct this struct with any input.
actions
.bind::<Move>()
.to((Cardinal::wasd_keys(), Axial::left_stick()))
.with_modifiers((DeadZone::default(), SmoothNudge::default()));
§Input conditions
Instead of hardcoded states like “pressed” or “released”, all actions internally have an abstract ActionState
.
Its meaning depends on assigned input conditions, which decide when the action triggers.
This allows to define flexible conditions, such as “hold for 1 second”.
Input conditions are structs that implement InputCondition
. Similar to modifiers, you can use
BindingBuilder::with_conditions
for per-input conditions or ActionBinding::with_conditions
to define a condition that applies to all action’s inputs. Conditions are evaluated after input modifiers.
For details about how multiple conditions are merged together, see the ActionBinding
documentation.
// The action will trigger only if held for 1 second.
actions.bind::<Jump>().to(KeyCode::Space.with_conditions(Hold::new(1.0)));
If no conditions are assigned, the action will be triggered by any non-zero value.
Similar to modifiers, you can also attach conditions to input tuples using IntoBindings::with_conditions_each
.
§Organizing bindings
It’s convenient to define bindings in a single function that used every time you activate the context or reload your application settings.
To achieve this, we provide a special Binding<C>
event that triggers when you insert or replace
Actions<C>
component. Just create an observer for it and define all your bindings there:
app.add_observer(bind_actions);
/// Setups bindings for [`OnFoot`] context from application settings.
fn bind_actions(
trigger: Trigger<Binding<OnFoot>>,
settings: Res<AppSettings>,
mut actions: Query<&mut Actions<OnFoot>>
) {
let mut actions = actions.get_mut(trigger.target()).unwrap();
actions
.bind::<Jump>()
.to((settings.keyboard.jump, GamepadButton::South));
}
#[derive(Resource)]
struct AppSettings {
keyboard: KeyboardSettings,
}
struct KeyboardSettings {
jump: KeyCode,
}
We also provide a user-triggerable RebuildBindings
event that resets bindings for all inserted
Actions
and also triggers Binding
event for them.
§Reacting on actions
Up to this point, we’ve only defined actions and contexts but haven’t reacted to them yet. We provide both push-style (via observers) and pull-style (by checking components) APIs.
§Push-style
It’s recommended to always use the observer API when possible. Don’t worry about losing parallelism - running a system has its own overhead, so for small logic, it’s actually faster to execute it outside a system. Just avoid heavy logic in action observers.
After each input processing cycle, we calculate a new ActionState
and trigger events based on state transition
(including transition between identical states). For details about transition logic and event types, see the ActionEvents
documentation.
Triggered events are stored as ActionEvents
bitset, but triggered using dedicated types that correspond to bitflags -
Started<A>
, Fired<A>
, etc., where A
is your action type. The trigger target will be the entity with the Actions
component
and the output type will match the action’s InputAction::Output
. Events also store other information, such as timings. See the
documentation on specific event type for more information.
app.add_observer(apply_movement);
/// Apply movement when `Move` action considered fired.
fn apply_movement(trigger: Trigger<Fired<Move>>, mut players: Query<&mut Transform>) {
// Read transform from the context entity.
let mut transform = players.get_mut(trigger.target()).unwrap();
// We defined the output of `Move` as `Vec2`,
// but since translation expects `Vec3`, we extend it to 3 axes.
transform.translation += trigger.value.extend(0.0);
}
The event system is highly flexible. For example, you can use the Hold
condition for an attack action, triggering strong attacks on
Completed
events and regular attacks on Canceled
events.
§Pull-style
You can also query for Actions
within a system. Use Actions::get<A>
to retrieve an Action
, from which you can obtain the
current value, state, or triggered events for this tick as ActionEvents
bitset.
/// Apply movemenet when `Move` action considered fired.
fn system(players: Single<(&Actions<OnFoot>, &mut Transform)>) -> Result<()> {
let (actions, mut transform) = players.into_inner();
if actions.state::<Jump>()? == ActionState::Fired {
// Apply logic...
}
}
§Input and UI
Currently, Bevy doesn’t have focus management or navigation APIs. But we provide ActionSources
resource
that could be used to prevents actions from triggering during UI interactions. See its docs for details.
§Troubleshooting
If you face any issue, try to enable logging to see what is going on.
To enable logging, you can temporarily set RUST_LOG
environment variable to bevy_enhanced_input=debug
(or bevy_enhanced_input=trace
for more noisy output) like this:
RUST_LOG=bevy_enhanced_input=debug cargo run
The exact method depends on the OS shell.
Alternatively you can configure LogPlugin
to make it permanent.
Modules§
- action_
binding - action_
instances - action_
map - action_
value - actions
- events
- input
- input_
action - input_
binding - input_
condition - input_
modifier - input_
reader - prelude
- preset
Structs§
- Enhanced
Input Plugin - Initializes contexts and feeds inputs to them.
- Enhanced
Input System - Label for the system that updates input context instances.