Skip to content

DragonFoxCollective/bevy_pretty_nice_input

Repository files navigation

bevy_pretty_nice_input

crates.io

bevy bevy_pretty_nice_input
0.17 0.1, 0.2

A refreshingly complex input crate for Bevy.

It works similarly to bevy_enhanced_input.

An input system entity relates to several Action entities, which each relate to several Binding entities (which then related to several BindingPart entities) and Condition entities.

When an input occurs, it may trigger one of several Bevy events:

  • JustPressed<A: Action>: When an input goes from zero to nonzero.
  • Pressed<A: Action>: When an input is nonzero.
  • JustReleased<A: Action>: When an input goes from nonzero to zero.
  • Updated<A: Action>: Any input

Actions keep track of previous successful inputs in order to trigger events. If the previous input is nonexistant, for instance if the input is invalidated, the action is ignored, since the events require a previous input to compare against.

An input system entity can be disabled by inserting InputDisabled on it.

Usage

Add the PrettyNiceInputPlugin plugin to your app.

input!

The input system starts with the simple input! macro:

// input!(action, Axis_D[bindings], [conditions])
input!(MyAction, Axis2D[binding2d::wasd()], [Cooldown::new(0.5)])

input! builds a bundle that can be added to the entity representing the input system or player controller.

Action

MyAction is a struct that implements Action:

#[derive(Action)]
#[action(invalidate = true)]
pub struct MyAction;

invalidate represents what happens when the input system is disabled. Either the system remembers the previous input state if false, or forgets it if true. It defaults to true if omitted. invalidate = false is best used for actions that need to be maintained while in a certain state. invalidate = true is best used for actions that work regardless of state.

Binding

Bindings can be N-axis, where N is 1 (for single button presses or single joystick axes), 2 (for wasd or entire joysticks), or 3 (composed of other bindings).

Many bindings can be found in the binding1d or binding2d modules. These are the arbitrary bindings:

  • binding1d::key(key: KeyCode): Binding for a single key in the range [0,1].
  • binding1d::key_axis(key_pos: KeyCode, key_neg: KeyCode): Binding for two keys in the range [-1,1], with one being positive and the other negative.
  • binding1d::gamepad_axis(axis: GamepadAxis): Binding for a single gamepad axis in the range [-1,1].
  • binding1d::mouse_button(button: MouseButton): Binding for a single mouse button in the range [0,1].
  • binding1d::mouse_move_axis(axis: AxisDirection): Binding for a single axis of mouse movement in the range [-inf,inf].
  • binding1d::mouse_scroll(direction: MouseScrollDirection): Binding for a single direction of mouse scroll in the range [0,inf].
  • binding1d::mouse_scroll_axis(axis: AxisDirection): Binding for a single axis of mouse scroll in the range [-inf,inf].

There are also shorthand bindings for common scenarios such as binding1d::space() and binding2d::wasd().

Bindings may be composed into one bundle:

input!(... Axis2D[binding2d::wasd()] ...)
// is equivalent to
input!(... Axis2D[(key_axis(KeyCode::KeyD, KeyCode::KeyA), key_axis(KeyCode::KeyW, KeyCode::KeyS))] ...)

The bindings given to input! must match the dimensionality of the Axis1D, Axis2D, or Axis3D keyword.

Condition

Conditions allow input to be filtered or modified.

The Condition trait may be implemented for custom conditions. The only function is fn bundle<A: Action>(&self) -> impl Bundle, which returns a bundle added to the condition entity.

Common conditions include:

  • Cooldown: Only lets one valid input pass every duration.
  • Filter<F: QueryFilter>: Only lets the input pass if the query filter matches.
  • InvalidatingFilter<F: QueryFilter>: Only lets the input pass if the query filter matches. Otherwise, invalidates the input.
  • ButtonPress: Rising edge filter.
  • ButtonRelease: Falling edge filter.
  • Invert: Inverts the update between zero and nonzero, using the last nonzero input when the current input is zero.
  • InputBuffer: Continues sending nonzero updates for a duration after the input stops being nonzero.
  • ResetBuffer: Stops any previous input buffers. Doesn't affect the current input in any way.

The conditions array in input! may be omitted entirely.

input_transition!

Where BPNI really shines is its state-machine macro.

// input_transition!(action: states <=/<=>/=> states, Axis_D[bindings], [conditions])
input_transition!(MyAction: Standing <=> Walking, Axis2D[binding2d::wasd()])

In this example, Standing and Walking are two states that transition back and forth depending on the WASD bindings, being inserted and removed from the input system entity during the transition.

There are many types of transitions. The transition arrow can be unidirectional in either direction, representing whether the transition ocurrs through the JustPressed or JustReleased events.

input_transition!(MyActionPressed: Standing => Walking, Axis2D[binding2d::wasd()]) // Transition from Standing to Walking on JustPressed<MyActionPressed>
input_transition!(MyActionReleased: Standing <= Walking, Axis2D[binding2d::wasd()]) // Transition from Walking to Standing on JustReleased<MyActionReleased>

The input system may transition out of multiple states at once, but not into multiple states.

input_transition!(MyAction: Standing => (Walking, Sprinting), Axis2D[binding2d::wasd()]) // Compile error
input_transition!(MyAction: Standing <= (Walking, Sprinting), Axis2D[binding2d::wasd()]) // Transition from Walking or Sprinting to Standing on JustReleased<MyAction>

When transitioning from multiple states, one state may be pointed back to in its bundle.

input_transition!(MyAction: Standing <= (Sprinting, => Walking), Axis2D[binding2d::wasd()]) // Transition from Walking or Sprinting to Standing on JustReleased<MyAction>, and from Standing to Walking on JustPressed<MyAction>

For clarity, the state to transition to may be replaced with *, representing a manually-controlled transition. This is equivalent to the input! macro with an extra filter.

input_transition!(Jump: (Standing, Walking) => *, Axis1D[binding1d::space()]) // Triggers JustPressed<Jump>, but only in the Standing or Walking states

Conditions may be used, but only on unidirectional transitions.

input_transition!(MyAction: Standing => Walking, Axis2D[binding2d::wasd()], [Filter::<Grounded>::default()]) // Transition from Standing to Walking on JustPressed<MyAction>, when the Grounded component is present on the input system
input_transition!(MyAction: Standing <=> Walking, Axis2D[binding2d::wasd()], [Filter::<Grounded>::default()]) // Compile error

ComponentBuffer

Component buffering is the compliment to input buffering. A common use case for component buffering is "coyote time".

Component buffering manages a separate state component ComponentBuffer<T: Component>, adding it when T is added, and removing it a certain amount of time after T is removed.

Component buffers can be set up by adding ComponentBuffer::observe(duration: f32) to the input system.

ComponentBuffer::<Grounded>::observe(0.2) // Adds ComponentBuffer<Grounded> when Grounded is added, and removes it 0.2 seconds after Grounded is removed

For convenience, there is an alias for Filter<With<ComponentBuffer<T>>> that is FilterBuffered<T>.

bundles::observe and bundles::add_system

observe and add_system are bundle effect versions of entity.observe and schedule.add_system respectively. observe already exists in Bevy, but only in the experiemental UI crate that I didn't want to depend on.

About

A refreshingly complex input crate for Bevy

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages