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));§Core Concepts
- Actions represent something a player can do, like “Jump”, “Movement”, or “Open Menu”. They are not tied to specific input.
- Bindings connect those actions to real input sources such as keyboard keys, mouse buttons, gamepad axes, etc.
- Contexts represent a certain input state the player can be in, such as “On foot” or “In car”. They associate actions with entities and define when those actions are evaluated.
Contexts are regular components. Depending on your type of game, you may have a single global context
or multiple contexts for different gameplay states. Contexts can be layered, and any number of them can be active at the same time.
To register a component as an input context, you need to call InputContextAppExt::add_input_context. By default, contexts are
evaluated during PreUpdate, but you can customize this by using InputContextAppExt::add_input_context_to instead.
Actions are represented by entities with the Action<A> component, where A is a user-defined marker that implements the
InputAction trait, which defines InputAction::Output type - the value the action produces. It could be bool, f32,
Vec2 or Vec3. Actions associated with contexts via ActionOf relationship. We provide the actions! macro, which is
similar to related!, but for actions. The relationship is generic over C because a single entity can have multiple associated
contexts.
Bindings are represented by entities with the Binding component. It can be constructed from various input types, such as
KeyCode, MouseButton, GamepadAxis, etc. Bindings associated with actions via BindingOf relationship. Similar to actions!,
we provide the bindings! macro to spawn related bindings. But unlike ActionOf<C>, it’s not generic, since each action is represented
by a separate entity.
use bevy::prelude::*;
use bevy_enhanced_input::prelude::*;
app.add_input_context::<Player>();
world.spawn((
Player,
actions!(Player[
(
Action::<Jump>::new(),
bindings![KeyCode::Space, GamepadButton::South],
),
(
Action::<Fire>::new(),
bindings![MouseButton::Left, GamepadButton::RightTrigger2],
),
])
));
#[derive(Component)]
struct Player;
#[derive(InputAction)]
#[action_output(bool)]
struct Jump;
#[derive(InputAction)]
#[action_output(bool)]
struct Fire;By default, input is read from all connected gamepads. You can customize this by adding the GamepadDevice component to the
context entity.
Context actions will be evaluated in the schedule associated at context registration. Contexts registered in the same
schedule will be evaluated in their spawning order, but you can override it by adding the ContextPriority component.
You can also activate or deactivate contexts by inserting ContextActivity component.
Actions also have ActionSettings component that customizes their behavior.
§Input modifiers
Action values are stored in two forms:
- In a typed form, as the
Action<C>component. - In a dynamically typed form, as the
ActionValue, which is one of the required components ofAction<C>. Its variant depends on theInputAction::Output.
During EnhancedInputSet::Update, input is read for each Binding as an ActionValue, with the variant depending
on the input source. This value is then converted into the ActionValue on the associated action entity. For example,
key inputs are captured as bool, but if the action’s output type is Vec2, the value will be assigned to the X axis
as 0.0 or 1.0. See Binding for details on how each source is captured, and ActionValue::convert for how values
are transformed.
Then, during EnhancedInputSet::Apply, the value from ActionValue is written into Action<C>.
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 are components that implement the InputModifier trait and can
be attached to both actions and bindings. Binding-level modifiers are applied first, followed by action-level modifiers.
Within a single level, modifiers are evaluated in their insertion order. Use action-level modifiers as global modifiers that
are applied to all bindings of the action.
use bevy::prelude::*;
use bevy_enhanced_input::prelude::*;
world.spawn((
Player,
actions!(Player[
(
Action::<Movement>::new(),
// Modifier components at the action level.
DeadZone::default(), // Applies non-uniform normalization.
SmoothNudge::default(), // Smoothes movement.
bindings![
// Keyboard keys captured as `bool`, but the output of `Movement` is defined as `Vec2`,
// so you need to assign keys to axes using swizzle to reorder them and negation.
(KeyCode::KeyW, SwizzleAxis::YXZ),
(KeyCode::KeyA, Negate::all()),
(KeyCode::KeyS, 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, SwizzleAxis::YXZ),
]
),
]),
));
#[derive(Component)]
struct Player;
#[derive(InputAction)]
#[action_output(Vec2)]
struct Movement;§Presets
Some bindings are very common. It would be inconvenient to bind WASD keys and analog sticks manually, like in the example above,
every time. To solve this, we provide presets - structs that implement SpawnableList and store bindings that
will be spawned with predefined modifiers. To spawn them, you need to to call SpawnRelated::spawn implemented for Bindings
directly instead of the bindings! macro.
For example, you can use Cardinal and Axial presets to simplify the example above.
world.spawn((
Player,
actions!(Player[
(
Action::<Movement>::new(),
DeadZone::default(),
SmoothNudge::default(),
Bindings::spawn((
Cardinal::wasd_keys(),
Axial::left_stick(),
)),
),
]),
));You can also assign custom bindings or attach additional modifiers, see the preset module for more details.
§Input conditions
Instead of hardcoded states like “pressed” or “released”, all actions use an abstract ActionState component
(which is a required component of Action<C>). Its meaning depends on the assigned input conditions,
which determine when the action is triggered. This allows you to define flexible behaviors, such as “hold for 1 second”.
Input conditions are components that implement InputCondition trait. Similar to modifiers, you can attach them to
both actions and bindings. They also evaluated during EnhancedInputSet::Update right after modifiers in their insertion
order and update ActionState on the associated action entity.
If no conditions are attached, the action behaves like with Down condition with a zero actuation threshold,
meaning it will trigger on any non-zero input value.
world.spawn((
Player,
actions!(Player[
(
// The action will trigger only if held for 1 second.
Action::<Jump>::new(),
Hold::new(1.0),
bindings![KeyCode::Space, GamepadButton::South],
),
(
Action::<Fire>::new(),
Pulse::new(0.5), // The action will trigger every 0.5 seconds while held.
bindings![
(GamepadButton::RightTrigger2, Down::new(0.3)), // Additionally the right trigger only counts if its value is greater than 0.3.
MouseButton::Left,
]
),
])
));§Mocking
You can also mock actions using the ActionMock component. When it’s present on an action with ActionMock::enabled, it will drive
the ActionState and ActionValue for the specified MockSpan duration. During this time, all bindings for this action will be ignored.
For more details, see the ActionMock documentation.
If you only need mocking, you can disable InputPlugin entirely. However, bevy_input is a required dependency
because we use its input types.
§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
For most cases it’s better to use observer API, especially for actions that trigger rarely. 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.
During EnhancedInputSet::Apply, events are triggered based on transitions of ActionState, such as
[Started<A>], [Fired<A>], and others, where A is your action type. This includes transitions between identical states.
For a full list of transition events, see the ActionEvents component documentation.
Just like with the ActionState, their meaning depends on the assigned input conditions,
The event target will be the entity with the context component, and the output type will match the action’s InputAction::Output.
Events also include additional data, such as timings and state. See the documentation for each event type
for more details.
app.add_observer(apply_movement);
/// Apply movement when `Movement` action considered fired.
fn apply_movement(movement: On<Fire<Movement>>, mut players: Query<&mut Transform>) {
// Read transform from the context entity.
let mut transform = players.get_mut(movement.context).unwrap();
// We defined the output of `Movement` as `Vec2`,
// but since translation expects `Vec3`, we extend it to 3 axes.
transform.translation += movement.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
Complete events and regular attacks on Cancel events.
This approach can be mixed with the pull-style API if you need to access values of other actions in your observer.
§Pull-style
Sometimes you may want to access multiple actions at the same time, or check an action state during other gameplay logic. For cases like you can use pull-style API.
Since actions just entities, you can query Action<C> in a system to get the action value in a strongly typed form.
Alternatively, you can query ActionValue in its dynamically typed form.
To access the action state, use the ActionState component. State transitions from the last action evaluation are recorded
in the ActionEvents component, which lets you detect when an action has just started or stopped triggering.
Timing information provided via ActionTime component.
You can also use Bevy’s change detection - these components marked as changed only if their values actually change.
For single-player games you can use Single for convenient access:
fn apply_input(
jump_events: Single<&ActionEvents, With<Action<Jump>>>,
movement: Single<&Action<Movement>>,
mut player_transform: Single<&mut Transform, With<Player>>,
) {
// Jumped this frame
if jump_events.contains(ActionEvents::STARTED) {
// User logic...
}
// We defined the output of `Movement` as `Vec2`,
// but since translation expects `Vec3`, we extend it to 3 axes.
player_transform.translation = movement.extend(0.0);
}For games with multiple contexts you can query for specific action or iterate over action contexts.
fn apply_input(
jumps: Query<&ActionEvents, With<Action<Jump>>>,
movements: Query<&Action<Movement>>,
mut players: Query<(&mut Transform, &Actions<Player>)>,
) {
for (mut transform, actions) in &mut players {
let Some(jump_events) = jumps.iter_many(actions).next() else {
continue;
};
let Some(movement) = movements.iter_many(actions).next() else {
continue;
};
// Jumped this frame
if jump_events.contains(ActionEvents::STARTED) {
// User logic...
}
// We defined the output of `Movement` as `Vec2`,
// but since translation expects `Vec3`, we extend it to 3 axes.
transform.translation = movement.extend(0.0);
}
}§Removing contexts
If you despawn an entity with its context, the actions and bindings will also be despawned. However, if you only want to remove a context from an entity, you must remove the required components and manually despawn its actions.
let mut player = world.spawn((
OnFoot,
actions!(OnFoot[
(Action::<Jump>::new(), bindings![KeyCode::Space, GamepadButton::South]),
(Action::<Fire>::new(), bindings![MouseButton::Left, GamepadButton::RightTrigger2]),
])
));
player
.remove_with_requires::<OnFoot>()
.despawn_related::<Actions<OnFoot>>();
assert_eq!(world.entities().len(), 1, "only the player entity should be left");Actions aren’t despawned automatically via EntityWorldMut::remove_with_requires, since Bevy doesn’t automatically
despawn related entities when their relationship targets (like Actions<C>) are removed. For this reason, Actions<C>
is not a required component for C. See #20252 for more details.
When an action is despawned, it automatically transitions its state to ActionState::None with ActionValue::zero,
triggering the corresponding events. Depending on your use case, using ContextActivity might be more convenient than removal.
§Input and UI
Currently, we don’t integrate bevy_input_focus directly. 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 runThe exact method depends on the OS shell.
Alternatively you can configure LogPlugin to make it permanent.
Modules§
Macros§
- actions
- Returns a
SpawnRelatedBundlethat will insert theActions<C>component and spawn aSpawnableListof entities with given bundles that relate to the context entity via theActionOf<C>component. - bindings
- Returns a
SpawnRelatedBundlethat will insert theBindingscomponent and spawn aSpawnableListof entities with given bundles that relate to the context entity via theBindingOfcomponent.
Structs§
- Enhanced
Input Plugin - Initializes contexts and feeds inputs to them.
Enums§
- Enhanced
Input Systems - Label for the system that updates input context instances.