Bevy Enhanced Input

repository·master·Indexed 43 days ago

https://github.com/simgine/bevy_enhanced_input

An observer-based input management system for the Bevy engine, inspired by Unreal Engine's Enhanced Input. It allows developers to bind physical inputs to abstract in-game actions, decoupling hardware from game logic. Version 0.26.0 supports Bevy 0.19.0.

Tokens
13.3K
Snippets
37
Records
48
Agent score
55%

What's inside Bevy Enhanced Input

  1. Overview of Bevy Enhanced Input

    master
    Bevy Enhanced Input is an observer-based input manager for the Bevy engine. It allows developers to bind physical inputs (like keys) to abstract in-game actions, a design pattern inspired by Unreal Engine's Enhanced Input plugin. This abstraction decouples input hardware from game logic, making it easier to manage complex input mappings and rebindable controls.
  2. What are input bindings and how to use them

    master

    Input bindings define which physical inputs (keyboard, mouse, gamepad, etc.) map to a specific action.

    To use them, you create a Binding and relate it to an action entity using the BindingOf relationship. This is typically done using the bindings! macro, which should be spawned as a child of the action entity.

    Key concepts:

    • Modifiers: Use input modifiers to alter the captured input value (e.g., adding Shift to a key press).
    • Conditions: Use input conditions to change when an action is triggered.
    • Conversion: If the action's dimension (e.g., f32) differs from the input's dimension (e.g., Axis2D), the value is converted using ActionValue::convert.
    actions!(PlayerCam[
        (
            Action::<Zoom>::new(),
            bindings![(Binding::mouse_wheel(), SwizzleAxis::YXZ)],
        )
    ]);
  3. Use AccumulateBy to sum input values based on another action

    master

    The AccumulateBy modifier allows an action to continuously add its input values together as long as a secondary 'trigger' action is in the TriggerState::Fired state.

    Behavior:

    • When the trigger action is Fired: The current action's input values are added to a running total (self.value).
    • When the trigger action is inactive: The accumulation resets, and the action's value becomes equal to the current frame's input value.

    This is useful for mechanics like movement acceleration, where holding a modifier key (like Shift) causes input (like WASD) to accumulate or scale over time.

    use bevy::{ecs::spawn::SpawnWith, prelude::*};
    use bevy_enhanced_input::prelude::*;
    
    Actions::<TestContext>::spawn(SpawnWith(|context: &mut ActionSpawner<_>| {
        // 1. Define the trigger action (e.g., a modifier key)
        let accelerate = context
            .spawn((
                Action::<Accelerate>::new(),
                bindings![KeyCode::ShiftLeft, KeyCode::ShiftRight],
            ))
            .id();
    
        // 2. Define the primary action that accumulates values based on the trigger
        context.spawn((
            Action::<Movement>::new(),
            AccumulateBy::new(accelerate),
            Bindings::spawn(Cardinal::wasd_keys().with(DeadZone::default())),
        ));
    }));
    
    #[derive(Component)]
    struct TestContext;
    
    #[derive(InputAction)]
    #[action_output(bool)]
    struct Accelerate;
    
    #[derive(InputAction)]
    #[action_output(f32)]
    struct Movement;
  4. How Actions, Bindings, and Contexts work together

    master

    Bevy Enhanced Input uses three core abstractions to manage player input:

    1. Actions: Represent high-level gameplay behaviors (e.g., "Jump", "Move") independent of specific keys. They are defined by implementing the InputAction trait and are stored as entities with the Action<A> component.
    2. Bindings: Connect Actions to physical input sources (e.g., KeyCode::Space, MouseButton::Left). Bindings are entities with the Binding component associated with an action via the BindingOf relationship.
    3. Contexts: Define the active input state (e.g., "On Foot", "In Vehicle"). Contexts are regular components registered as input contexts. They associate actions with specific entities and control when those actions are evaluated.

    Workflow Summary:

    • Register a component as a context using add_input_context.
    • Define an InputAction struct.
    • Spawn an entity with the context component and use the actions! macro to spawn Action entities and their Binding entities in one go.
    use bevy::prelude::*;
    use bevy_enhanced_input::prelude::*;
    
    #[derive(Component)]
    struct Player;
    
    #[derive(InputAction)]
    #[action_output(bool)]
    struct Jump;
    
    #[derive(InputAction)]
    #[action_output(bool)]
    struct Fire;
    
    // In your App setup:
    app.add_plugins(EnhancedInputPlugin)
        .add_input_context::<Player>()
        .finish();
    
    // Spawning the player with actions and bindings:
    app.world_mut().spawn((
        Player,
        actions!(Player[
            (
                Action::<Jump>::new(),
                bindings![KeyCode::Space, GamepadButton::South],
            ),
            (
                Action::<Fire>::new(),
                bindings![MouseButton::Left, GamepadButton::RightTrigger2],
            ),
        ])
    ));
  5. How ConditionKind affects TriggerState combination

    master

    When multiple conditions are present, the ConditionKind determines how their individual TriggerState results are combined to produce the final state for the action:

    • ConditionKind::Explicit: The most significant TriggerState from all explicit conditions will be the resulting state.
    • ConditionKind::Implicit: Similar to Explicit, but TriggerState::Fired is only set if all implicit conditions return it. Otherwise, the most significant state is capped at TriggerState::Ongoing.
    • ConditionKind::Blocker: Any blocking condition that returns TriggerState::None will override the state with TriggerState::None. A blocker does not contribute to the state on its own; it only serves to potentially cancel it.
  6. Use SwizzleAxis to reorder or swap input axes

    master

    The SwizzleAxis component is an input modifier used to reorder or swap the axes of an input value. This is particularly useful for mapping 1D inputs (like a single button or trigger) onto specific axes of a higher-dimensional action (like the Y axis of a 2D movement stick).

    Behavioral Rules:

    • It attempts to preserve the original dimension where possible.
    • If an axis from the original input is promoted to a higher dimension, the ActionValue type will change (e.g., a Bool or Axis1D might become an Axis2D or Axis3D).
    • Missing axes in the target configuration are replaced with zero.

    Common Use Cases:

    • Mapping a 1D input to the Y axis of a 2D action using SwizzleAxis::YXZ.
    • Reordering 3D axes for specific coordinate system requirements.
    use bevy::prelude::*;
    use crate::prelude::*;
    
    // Example of applying a swizzle (conceptual usage via component attachment)
    // entity.insert(SwizzleAxis::YXZ);
  7. Mock input actions for testing or automation

    master

    The ActionMock component allows you to simulate input actions without physical device interaction. This is useful for automated testing, AI-driven inputs, cutscenes, or network-driven input.

    When an action is mocked:

    • Input reading, conditions, and modifiers are skipped.
    • The action reports the provided TriggerState and ActionValue.
    • State transition events (e.g., Start<A>, Fire<A>) are still triggered.
    • Mocking takes effect during the next context evaluation.

    Once the specified MockSpan expires, ActionMock::enabled is set to false, and the action resumes regular evaluation. The component is not automatically removed, allowing for reuse.

    // Example: Spawn and move up for 2 seconds
    world.spawn((
        Player,
        actions!(Player[
            (
                Action::<Movement>::new(),
                ActionMock::new(TriggerState::Fired, Vec2::Y, Duration::from_secs(2)),
                Bindings::spawn(Cardinal::wasd_keys()),
            ),
        ]),
    ));
  8. Understand the relationship between Actions and Bindings

    master

    In bevy_enhanced_input, actions and their bindings are linked using Bevy's relationship system via two components:

    • BindingOf(Entity): This component is attached to a binding entity. It points to the action entity it belongs to. It uses a relationship target of Bindings.
    • Bindings(Vec<Entity>): This component is attached to the action entity. It holds a list of all entities that represent bindings for that specific action.

    This structure allows you to efficiently query all bindings associated with a specific action or find the parent action from a binding entity.

  9. Configure DeadZone behavior using DeadZoneKind

    master

    The DeadZoneKind enum defines how the dead zone logic is applied to multi-axis inputs (Axis2D and Axis3D):

    • Radial (Default): Applies dead zone logic to all axes simultaneously. This provides smooth, circular or spherical input coverage, which is generally preferred for thumbsticks.
    • Axial: Applies the dead zone to each axis individually. This results in input being "chamfered" at the corners (the input area becomes a square/cube rather than a circle/sphere).
    // Radial (smooth circular movement)
    let radial = DeadZone::new(DeadZoneKind::Radial);
    
    // Axial (square/chamfered movement)
    let axial = DeadZone::new(DeadZoneKind::Axial);
  10. How input conditions work in Bevy Enhanced Input

    master

    Input conditions are components that implement the InputCondition trait. They define the logic for when an action is triggered (e.g., "hold for 1 second" or "tap").

    Key Behaviors:

    • Attachment: Conditions can be attached to both actions and bindings.
    • Evaluation: They are evaluated during EnhancedInputSystems::Update after modifiers, in their insertion order. They update the TriggerState on the associated action entity.
    • Default Behavior: If no conditions are attached to an action, it defaults to behaving like a Down condition with a zero actuation threshold (triggering on any non-zero input value).
    • Custom Conditions: If you implement a custom condition, you must register it using InputConditionAppExt::add_input_condition.
    use bevy::prelude::*;
    use bevy_enhanced_input::prelude::*;
    
    #[derive(InputAction)]
    #[action_output(bool)]
    struct Jump;
    
    // Example of attaching a condition to an action
    world.spawn(actions!((
        Action::<Jump>::new(),
        Hold::new(1.0),
        bindings![KeyCode::Space],
    )));
  11. Understand ActionEvents and state transitions

    master

    The ActionEvents bitset tracks events caused by transitions in an action's TriggerState. During EnhancedInputSystems::Trigger, these bitflags are set based on the transition from the previous state to the new state. You can use ActionEvents as a component in systems or react to them via observers.

    Last stateNew stateEvents
    TriggerState::NoneTriggerState::NoneNo events
    TriggerState::NoneTriggerState::OngoingSTART + ONGOING
    TriggerState::NoneTriggerState::FiredSTART + FIRE
    TriggerState::OngoingTriggerState::NoneCANCEL
    TriggerState::OngoingTriggerState::OngoingONGOING
    TriggerState::OngoingTriggerState::FiredFIRE
    TriggerState::FiredTriggerState::FiredFIRE
    TriggerState::FiredTriggerState::OngoingONGOING
    TriggerState::FiredTriggerState::NoneCOMPLETE
    // ActionEvents bitflags
    // START, ONGOING, FIRE, CANCEL, COMPLETE
  12. Use ActionMock to simulate actions

    master

    You can mock an action by inserting an ActionMock component onto the specific action entity, or by using the convenience extension traits on EntityCommands or EntityWorldMut.

    Using EntityCommands (Commands API)

    Use mock, try_mock, mock_once, or try_mock_once on an entity to find the correct action within its Actions<C> container and apply the mock.

    • mock: Emits a warning if the entity or action is not found.
    • try_mock: Silences warnings if the entity or action is not found.
    • mock_once: Mocks the action for a single update (shorthand for MockSpan::once()).
    • try_mock_once: Silences warnings and mocks for a single update.

    Using EntityWorldMut (Direct World Access)

    Use mock, mock_once on EntityWorldMut for immediate application during system execution.

    // Mock a 2 second long press using EntityCommands
    fn mock_fire(mut commands: Commands, player: Single<Entity, With<Player>>) {
        commands
            .entity(player.into_inner())
            .mock::<Player, PrimaryFire>(TriggerState::Fired, true, Duration::from_secs(2));
    }
    
    // Mock a jump action for just the next frame
    fn mock_jump(mut commands: Commands, jump: Single<Entity, With<Action<Jump>>>) {
        commands.entity(*jump).insert(ActionMock::once(TriggerState::Fired, true));
    }