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”, “Move”, 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.
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::<Move>::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 `Move` 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 Move;
§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::<Move>::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 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
It’s recommended to use the 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 `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 simply 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.
fn apply_input(
jump_events: Single<&ActionEvents, With<Action<Jump>>>,
move_action: Single<&Action<Move>>,
mut player_transform: Single<&mut Transform, With<Player>>,
) {
// Jumped this frame
if jump_events.contains(ActionEvents::STARTED) {
// User logic...
}
// We defined the output of `Move` as `Vec2`,
// but since translation expects `Vec3`, we extend it to 3 axes.
player_transform.translation = move_action.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 this issue 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 run
The exact method depends on the OS shell.
Alternatively you can configure LogPlugin
to make it permanent.
Modules§
Macros§
- actions
- Returns a
SpawnRelatedBundle
that will insert theActions<C>
component and spawn aSpawnableList
of entities with given bundles that relate to the context entity via theActionOf<C>
component. - bindings
- Returns a
SpawnRelatedBundle
that will insert theBindings
component and spawn aSpawnableList
of entities with given bundles that relate to the context entity via theBindingOf
component.
Structs§
- Enhanced
Input Plugin - Initializes contexts and feeds inputs to them.
Enums§
- Enhanced
Input Set - Label for the system that updates input context instances.