Crate bevy_enhanced_input

Crate bevy_enhanced_input 

Source
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:

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§

action
binding
condition
context
modifier
prelude
preset
SpawnableLists with common modifiers.

Macros§

actions
Returns a SpawnRelatedBundle that will insert the Actions<C> component and spawn a SpawnableList of entities with given bundles that relate to the context entity via the ActionOf<C> component.
bindings
Returns a SpawnRelatedBundle that will insert the Bindings component and spawn a SpawnableList of entities with given bundles that relate to the context entity via the BindingOf component.

Structs§

EnhancedInputPlugin
Initializes contexts and feeds inputs to them.

Enums§

EnhancedInputSet
Label for the system that updates input context instances.