#bevy_enhanced_input

1 messages Β· Page 5 of 1

proud coral
#

So I guess in my mind contexts are basically for reusing the actions across different situations

#

but maybe that's not the philosophy here

#

This is the pattern I'm following rn

#

it works dont get me wrong

#

It just gives me vibes of I'm not doing it correctly

heady mauve
#

I do think it might depend on how you view contexts. In my mind, contexts are used to separate actions based on what state an entity or the game might be in. For me, a context isn't for reusing actions necessarily but being able to bind the same input to different actions depending on the specific circumstances that exist for the context to be active.

I'm not sure if that is the philosophy of the library though.

proud coral
# proud coral

(The multiple repeated cycle actions are missing, but you get the point :P)

heady mauve
#

I think the key might be that if multiple actions across contexts lead to different effects then they should probably be separately defined actions. If you had a cycle action that changed something in the UI that did the same thing on different entities then it makes sense to keep it the same. If the cycle action depends on specific code depending for what is being cycled, then the actions should probably be different.

proud coral
#

I'll keep experimenting, thanks for the feedback @heady mauve

#

one more question b4 leaving, is there an straightforward way to have contextless actions? or actions that trigger on all contexts

#

(same thing called differently :/)

heady mauve
# proud coral one more question b4 leaving, is there an straightforward way to have contextles...

I don't think there are context-less actions. So if you want some action to be a global thing that can be done from anywhere it would have to be in a context on an entity that would live for the length of time you are looking for.

I'm a little confused by wanting actions that trigger on all contexts. There is context layering so you can have certain actions in a single context and then can have other contexts on the same entity also. Those other contexts could be deactivated, removed, or replaced with other contexts and as long as you leave the context with the action you want alone it should still activate as long as none of the other contexts in a higher priority consume the same input.

If I am misunderstanding your question, then perhaps you could provide an example that explains the scenario you are looking for.

proud coral
#

It was just a question, but yeah I just want to throw in some actions mostly for trying out things

#

that do not go into any context per se

#

I just converted my entity marker (Player) into a context and will put these there

ionic sundial
modern nebula
#

I'm trying to figure out how to use chording to combine mouse motion with a key. I.e. you press middle mouse button and move the mouse to pan the camera around.

I'm currently using the actions! macro approach from the documentation.

actions!(MainCameraControllerContext[
            (
                Action::<Pan>::new(),
                Down::new(0.0),
                Bindings::spawn((
                    Cardinal::wasd_keys(),
                    Cardinal::arrows(),
                    Axial::left_stick(),
                )),
            )

Etc.

But the other recent example of someone tackling that problem in here seems to be using a different approach. Not real sure what I need to be doing here.

sharp warren
modern nebula
# sharp warren For `Chord` you need access to the entity. See its documentation for details.

For the life of me I still cannot figure out how to get chorded inputs to work. I also can't get the action to mock. I've tried what's in the docs to the best of my understanding, and I've dug back through this thread for other discussions of it. I've tried also moving this into a different function from the one that initializes the similar portions of the context, and using the BindingOf / ActionOf relationship stuff. I'm not sure what I'm missing / doing wrong.

fn init_camera_controller(mut commands: Commands) {
    commands.spawn((
        Name::new("Main Camera Controller"),
        MainCameraController::default(),
        Transform::from_translation(Vec3::ZERO),
        MainCameraControllerContext,
        actions!(MainCameraControllerContext[
            // Removed standard action! macro even spawn on WASD and scollwheel for discord            ),
        ]),
        
        Actions::<MainCameraController>::spawn(SpawnWith(|context: &mut ActionSpawner<_>| {
            let rmb_chord = context
                .spawn((
                    Action::<RMBChord>::new(),
                    Down::new(1.0),
                    bindings![MouseButton::Middle],
                ))
                .id();

            context.spawn((
                Action::<Orbit>::new(),
                ActionMock::new(ActionState::Fired, Vec2::ZERO, Duration::new(5, 0)),
                Chord::single(rmb_chord),
                bindings![Binding::mouse_motion()],
            ));
        })),
    ));
}
sharp warren
modern nebula
sharp warren
modern nebula
modern nebula
#

Okay, I'll give that a shot, thanks

cursive gorge
sharp warren
sharp warren
#

Previosly sent to the wrong chat πŸ˜…

cursive gorge
# sharp warren

I think this might happen because they are deprecated and thus exist, while the docs only check for wrong links

sharp warren
#

Yep, but they have doc(hidden)...

cursive gorge
#

Also depending on your lints you also won't receive errors for things that are clearly types but aren't formatted as links, which then causes no error either

#

Like what is that [Fired] thing, that's clearly supposed to be a link ferris_sob

sharp warren
#

I'll draft a patch release after the merge

inner sky
# sharp warren

whoops looks like when I renamed them my IDE didn't feel like following suit for the docs bavy

sharp warren
#

Mine also doesn't rename docs. But I think it should be implemented on the rust-analyzer side to work πŸ™‚

sharp warren
inner sky
#

Just wanna mention that BEI's code is a lovely well to draw from for an API I'm drafting up in an AI behavior crate πŸ™‚ #game-ai message

#

there's so much high quality code I can adapt heart_lime

inner sky
#

Is there a difference between these two?

bindings![A, B, C]
bindings![(A, B, C)]
inner sky
#

Because it seems to me like the API is going through a few hoops to support the latter, even though it seems to me as if that's semantically wrong?

#

Since they all get converted into Binding, and you can't have a single entity with multiple Binding components

#

Or did I get something wrong?

coarse brook
#

I would love to have the tuple one be an easy way to bind a chord. To me it seems like it is semantically wrong if it currently works πŸ€”

sharp warren
#

I hope when BSN comes we'll be able to drop this and actions! macro πŸ™‚

inner sky
#

But is there a semantic difference for BEI?

sharp warren
inner sky
sharp warren
#

So if A, B, and C are all Binding, second example will simply panic.

inner sky
sharp warren
#

They work with bindings too

inner sky
#

Ooooooooooh

#

Okay, now it all makes sense

#

And a user just needs to make sure the first component is the binding

sharp warren
#

Action-level modifiers and conditions just applied to all bindings.
Binding-level ones applied to specific bindings.
For example, you may want to apply sensetivity to a binding.

inner sky
sharp warren
#

It's a bit magic, but I decided it's worth it over Binding::from.

inner sky
#

Again I cannot stress enough how incredibly helpful the BEI source code is for me right now haha

sharp warren
#

Thanks, I really glad people like this design πŸ™‚

inner sky
#

Looking at ContextInstance right now

#

I love the trick with the function pointers

#

Am I understanding it correctly that you're using function pointers to functions derives from generic functions so that the struct itself is not generic?

#

that's brilliant

sharp warren
# inner sky that's brilliant

Thank you!
I learned it from @cursive gorge, she suggested this approach for ser/de customization in bevy_replicon πŸ™‚

inner sky
#

I just noticed that IntoBindingBundle can have an on_unimplemented diagnostic

#

that makes it more clear why something is not working πŸ™‚

sharp warren
#

I definitely need to add something like this.

inner sky
#

@sharp warren this text fine?

{Self} is not a valid binding bundle. The first element must be a Binding.

#

Or more wordy?

sharp warren
#

Maybe "must be convertible into a Binding".

cursive gorge
#

I need the movement input in another action (that happens to be on another context on the same entity), do we have an easy way to access that or order it so that I can read where it wrote its data?

sharp warren
cursive gorge
#

How exactly would I query it? Do I iterate trough the related actions on the context entity?

sharp warren
cursive gorge
#

They are currently unique, but I'm writing my code on the assumption I'll want to add split screen support later (which I am planning to do)

#

iter_many seems to work, nice

inner sky
#

@sharp warren do you have experience with the foo!(Bar[ ... ]) syntax confusing rustfmt?

#

e.g. this is not indenting for me

tasks!(Sequence[
     op("a"   ),
                tasks!(Sequence[op("b"), op("c")]),
]),
sharp warren
inner sky
#

But I kinda like the fact that right now tasks! ensures that you specified either Sequence, Select, or custom

inner sky
#

@sharp warren I'm done with the AI library and now moved on to finally doing a solid kinematic character controller

#

I'd like to hardcode it to work with BEI out of the box as nicely as possible

#

Seems like the best way to do that is to do all input accumulation internally (so the user doesn't get to see that nasty boilerplate), and expose the... idk the precise term, the structs that have #[action_output(Vec2)]

#

then userland code can just do


pub(super) fn plugin(app: &mut App) {
    app.add_input_context::<PlayerInput>();
}

#[derive(Component, Default)]
#[component(on_add = PlayerInput::on_add)]
pub(crate) struct PlayerInput;

impl PlayerInput {
    fn on_add(mut world: DeferredWorld, ctx: HookContext) {
        world
            .commands()
            .entity(ctx.entity)
            .insert(actions!(PlayerInput[
                (
                    Action::<Movement>::new(),
                    DeadZone::default(),
                    SmoothNudge::default(),
                    Bindings::spawn((
                        Cardinal::wasd_keys(),
                        Axial::left_stick()
                    ))
                ),
                (
                    Action::<Jump>::new(),
                    bindings![KeyCode::Space, GamepadButton::South],
                )
            ]));
    }
}
#

where Movement and Jump are exported by the character controller

#

first question: does that seem reasonable?

#

second question: what about NPCs?

#

It would be neat if NPCs could use the same character controller as the player, but how would such a BEI-native library deal with that? Is it good design to tell the users to mock the inputs to the NPCs?

#

Or I guess I can just tell users to directly manipulate the accumulated input component that is handled behind the scenes anyways:

/// Input accumulated since the last fixed update loop. Is cleared after every fixed update loop.
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct AccumulatedInput {
    // The last non-zero move that was input since the last fixed update loop
    last_movement: Option<Vec2>,
    // Whether any frame since the last fixed update loop input a jump
    jumped: bool,
}
#

not sure what the cleanest design for NPC inputs here is

sharp warren
sharp warren
inner sky
#

That means I need to do very little on the library side πŸ™‚

#

I wonder if there's a smarter way to accumulate the inputs than

#
fn apply_movement(
    movement: On<Fire<Movement>>,
    mut accumulated_inputs: Query<&mut AccumulatedInput>,
) {
    if let Ok(mut accumulated_inputs) = accumulated_inputs.get_mut(movement.context) {
        accumulated_inputs.last_movement = Some(movement.value);
    }
}

fn apply_jump(jump: On<Fire<Jump>>, mut accumulated_inputs: Query<&mut AccumulatedInput>) {
    if let Ok(mut accumulated_inputs) = accumulated_inputs.get_mut(jump.context) {
        accumulated_inputs.jumped = true;
    }
}

fn clear_accumulated_input(mut accumulated_inputs: Query<&mut AccumulatedInput>) {
    for mut accumulated_input in &mut accumulated_inputs {
        *accumulated_input = default();
    }
}
#

In another project I used the pull-based API instead

#

But in there I just hardcoded it to PreUpdate

#

But that wouldn't work in the general case, where a user may be running BEI in any schedule

#

so I resorted to having these trivial observers

sharp warren
inner sky
#

Since if I allow e.g. 10 inputs, I will end up with 10 observers that look the same

#

guess I can do a macro bavy

sharp warren
inner sky
sharp warren
#

But you still need to assign which struct corresponds to each field

sharp warren
#

Unless you use TypeIdMap πŸ€”

#

And no fields

inner sky
#

at least this is not an issue for the end-user code

sharp warren
inner sky
#

since from their perspective this design Just Works if they use BEI "normally"

sharp warren
#

Because TypeId is already a hash

inner sky
sharp warren
inner sky
sharp warren
#

You have a bunch of action-structs and you need to map it to the fields on your accumulation struct, right?
So with TypeIdMap you can have keys that directly represented by types.

inner sky
sharp warren
#

But if it's only a few, I'd just repeat the code. Sometimes repetition is okay.

inner sky
inner sky
#

especially since the code in question is like 10 LOC per repetition

sharp warren
inner sky
inner sky
#

gotcha

#

I'll stick with your suggestion of repeating the code, but I'll remember this for future reference. Thanks!

cursive gorge
#
Bindings::spawn(
    Axial::<Binding, Binding> {
        x: GamepadButton::LeftThumb.into(),
        y: GamepadButton::RightThumb.into(),
    },
)
```This one seems rather awkward πŸ€”
sharp warren
cursive gorge
#

I guess a ::new() could work with Into<Binding> if that's the intended behavior here? πŸ€”

sharp warren
#

But it might worth reducing the amount of helpers in favor of new

cursive gorge
#

Is Axial even the right thing here, I'm trying to map it to a f32, just feels a bit wrong πŸ€”

sharp warren
cursive gorge
#

In this case I had a 0 or 1 button, and another 0 or 1 button and I need -1 to 1

#

Ah, there is Bidirectional for this

sharp warren
cursive gorge
#

My camera control actions are truly cursed bavy

(
    Action::<Rotate>::new(),
    Scale::new(Vec3::new(0.0035, 0.002, 1.)),
    Bindings::spawn_one(Binding::mouse_motion()),
),
(
    Action::<Rotate>::new(),
    DeadZone{kind: DeadZoneKind::Axial, lower_threshold: 0.2, upper_threshold: 1.},
    Scale::new(Vec3::new(0.032, -0.012, 1.)),
    DeltaScale,
    Bindings::spawn(Axial::right_stick()),
),
(
    Action::<Zoom>::new(),
    Scale::new(Vec3::new(0.3, 0.3, 1.)),
    Bindings::spawn((
        Spawn((Binding::mouse_wheel(), SwizzleAxis::YXZ)),
        Bidirectional::<Binding, Binding> {
            positive: GamepadButton::LeftThumb.into(),
            negative: GamepadButton::RightThumb.into(),
        },
    ))
),
#

But unlike the rest of my game they have some simple observers like:

fn zoom_camera(trigger: On<Fire<Zoom>>, mut control: Query<&mut CameraDistance>) -> Result {
    let mut zoom = control.get_mut(trigger.context)?;
    **zoom = (**zoom - trigger.value).clamp(1., 15.);
    Ok(())
}
sharp warren
sharp warren
cursive gorge
#
[main bf0395d] Switch from leafwing-input-manager to bevy_enhanced_input
 43 files changed, 461 insertions(+), 564 deletions(-)
```The extra deletions are somewhat misleading, but overall the code is a lot nicer now πŸ₯³
valid harbor
#

does anyone has an idea on how

app
            .add_plugins(EnhancedInputPlugin)
            .add_input_context::<Player>()
            .finish()
        ;

can hard freezee my app (can't even kill with the terminal I have to kill my terminal)
Should I make a bugreport on that?

#

the engine is absolutely unresponsive when it hits the finish()

#

bevy = { version = "0.17.2" andbevy_enhanced_input = "0.19.3"

sharp warren
valid harbor
#

I'll do that later and file a bug report if the bug persist

#

I'm using an example tho, so that sort of count as that

sharp warren
valid harbor
#

you want me to download the repo and try from there, copy

#

okay examples works, so the issue was my code somehow

#

believing that was to be called example

#
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;

pub struct ControlTestPlugin;

impl Plugin for ControlTestPlugin{
    fn build(&self, app: &mut App) {
        app.add_plugins(EnhancedInputPlugin)
            .add_input_context::<Player>()
            .finish()
        ;

        app.world_mut().spawn((
            Player,
            actions!(Player[
                (
                    Action::<Jump>::new(),
                    bindings![KeyCode::Space, GamepadButton::South],
                ),
                (
                    Action::<Fire>::new(),
                    bindings![MouseButton::Left, GamepadButton::RightTrigger2],
                ),
            ])
        ));
    }
#

giving me this code

#

but that is flawed somehow and causes the issue described earlier

#

the main only has:

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(ControlTestPlugin)
        .run();
}```
#

reducing the BEI to

pub struct ControlTestPlugin;

impl Plugin for ControlTestPlugin{
    fn build(&self, app: &mut App) {
        app.add_plugins(EnhancedInputPlugin)
            .finish()
        ;
    }
}```
still causes the issue
#

ah probably because of finish being used not in the main

#

I guess you don't use finish?

#

removing finishes now causes other issues, what's the most minimal functionnal example?
The rust community truly hates examples that are sufficiently small to be visibly in full screen, making them totally accessible for someone that just landed in

sharp warren
#

Calling run already does finish.

sharp warren
sharp warren
#

Ping @thick kiln to update Lightyear. Should be just a version bump.

inner sky
#

@sharp warren I've got a little problem with the setup we discussed a while ago

#

Here:

fn apply_movement(
    movement: On<Fire<Movement>>,
    mut accumulated_inputs: Query<&mut AccumulatedInput>,
) {
    if let Ok(mut accumulated_inputs) = accumulated_inputs.get_mut(movement.context) {
        accumulated_inputs.last_movement = Some(movement.value);
    }
}

fn apply_jump(jump: On<Fire<Jump>>, mut accumulated_inputs: Query<&mut AccumulatedInput>) {
    if let Ok(mut accumulated_inputs) = accumulated_inputs.get_mut(jump.context) {
        accumulated_inputs.jumped = true;
    }
}

fn apply_crouch(crouch: On<Fire<Crouch>>, mut accumulated_inputs: Query<&mut AccumulatedInput>) {
    if let Ok(mut accumulated_inputs) = accumulated_inputs.get_mut(crouch.context) {
        accumulated_inputs.crouched = true;
    }
}

fn clear_accumulated_input(mut accumulated_inputs: Query<&mut AccumulatedInput>) {
    for mut accumulated_input in &mut accumulated_inputs {
        *accumulated_input = default();
    }
}
#

if this is my library code, the user wouldn't be able to change this to e.g. only jump when Jump has been released hmm

#

right?

sharp warren
inner sky
#

thanks!

#

do we also have some kind of builtin toggle?

sharp warren
inner sky
#

Asking since usually I handle that in the observer that observes the Fire, but since this is now in the library code, that would need to be configured by the user

sharp warren
#

In Unreal you don't really need it because it's all nodes and there is a built-in node. So you just use it before you connect logic to the input.

inner sky
sharp warren
#

But it makes sense for Bevy

sharp warren
inner sky
sharp warren
#

What I like about conditions is that users can write them by simply implementing a trait.
So even before the upstreaming, people can try it in their codebase without any patching and later upstream if they want.

ionic sundial
cursive gorge
# inner sky do we also have some kind of builtin toggle?

That would actually be really neat as long as it's easy for code to reset the toggle, eg you toggle sprinting but it stops if you stop moving (not the desired game in every game ofc, and for example with crouching you're gonna want to keep crouching even if you stop moving)

sharp warren
cursive gorge
#

Having the ability to spawn bindings like that without having to change actual handling of the actions would be great though

#

Would greatly simplify adding important accessability features like toggle sprint/crouch/etc

sharp warren
#

Ah, you mean having the same logic that processes the action in observer/system with and without toggle bindings?
I think this should already work the same since the concept of press and release is abstracted. Like in the example above, user slaps Release modifier and it still works since the reacting logic only cares about Fired.

cursive gorge
#

Yea, just in this case you can remove the regular binding, and spawn a toggle binding

#

(Or just add/remove the component)

raven arch
#

Hi new user of this crate. It's pretty exciting, so far quick to setup. But I am having some trouble with some action binding and specifically my left stick on my PS5 dual sense controller (I have tested it work fully)

(
    Action::<crate::controls::Move>::new(),
    DeadZone{kind: DeadZoneKind::Axial, lower_threshold: 0.2, upper_threshold: 1.},
    Scale::new(Vec3::new(1., 1., 1.)),
    Bindings::spawn(Axial::right_stick()),
),
(
    Action::<crate::controls::Move>::new(),
    DeadZone{kind: DeadZoneKind::Axial, lower_threshold: 0.2, upper_threshold: 1.},
    Scale::new(Vec3::new(1., 1., 1.)),
    Bindings::spawn(Axial::left_stick()), // Bidirectional::new(KeyCode::ArrowLeft, KeyCode::ArrowRight),)
),

here I have defined both left and right sticks to the same action (for debug) but the left stick never writes a value to the X axis. The right will do this correctly though. I could very easily be doing something incorrectly but the right working left not working is a pretty strange 🧩
Any ideas what the issue might be?

sharp warren
raven arch
sharp warren
raven arch
raven arch
# sharp warren It's basic_action_management

I have 2 contexts like the docs [on foot, in car] the "in car" context only uses

(
    Action::<controls::Steer>::new(),
    // Bindings::spawn(Axial::left_stick()),
    bindings![
        GamepadAxis::LeftStickX,
    ],
),

could this be related? (obviously this is the intention of contexts) I have attempted to test using the left full stick also (did not change anything)

raven arch
sharp warren
#

I never tried something like that.

#

Just put both sticks to the Bindings::spawn

raven arch
raven arch
# sharp warren Just put both sticks to the `Bindings::spawn`

So in my case I have a [steer] car (X axis only) and [movement] player InputAction which I bind on each entity. This #1297361733886677036 message code was simply testing the sticks. I really only use the Axial::left_stick() part.
Sorry but I'm not sure what you mean by "Just put both sticks to the Bindings::spawn"

sharp warren
raven arch
raven arch
# raven arch So with having 2 different contexts [in car, on foot] I shouldn't have actions b...

this is the car when spawned

  LinearVelocity::default(),
  AngularVelocity::default(),
  controls::DrivingContext,
  actions!(
      controls::DrivingContext[
          (
              Action::<controls::Throttle>::new(),
              bindings![
                  GamepadButton::RightTrigger2,
              ],
          ),
          (
              Action::<controls::ThrottleReverse>::new(),
              bindings![
                  GamepadButton::LeftTrigger2,
              ],
          ),
          (
              Action::<controls::Steer>::new(),
              // Bindings::spawn(Axial::left_stick()),
              bindings![
                  GamepadAxis::LeftStickX,
              ],
          ),
          (
              Action::<controls::Interact>::new(),
              bindings![KeyCode::Space, GamepadButton::South],
          ),
      ]
  ),

and here is the player when spawned

  Transform::from_translation(spawn_pos),
  RigidBody::Dynamic,
  Collider::capsule(0.4, 0.8),
  LockedAxes::ROTATION_LOCKED, // Keep upright
  Player,
  Health::default(),
  crate::controls::OnFootContext,
  actions!(
      crate::controls::OnFootContext[
          (
              Action::<crate::controls::Move>::new(),
              DeadZone{kind: DeadZoneKind::Axial, lower_threshold: 0.2, upper_threshold: 1.},
              Scale::new(Vec3::new(1., 1., 1.)),
              Bindings::spawn(Axial::left_stick()),
          ),
// removed debug Axial::right_stick() binding
          (
              Action::<crate::controls::Interact>::new(),
              bindings![KeyCode::Space, GamepadButton::South],
          ),
      ]
  ),
sharp warren
raven arch
raven arch
sharp warren
raven arch
sharp warren
#

So you have 2 contexts active at the same time?

raven arch
#

It looks like it. I have only used .add_input_context in the build and I use a state to switch between [on foot] and [in car] ... but those are bevy states, I'm not sure how I switch a context off.

raven umbra
#

I am trying to figure out how to create a chord for pressing the jump button while holding down ⬇️ on either the keyboard, dpad, or gamepad left stick. Is there a way I can use my existing Move command (Vec2) as the chord and check if the y value is less than one?

Or if I create a new action, I don't know how to specifically set the gamepad y axis negative to fire it. I know how to set the down arrow and down on the dpad.

sharp warren
sharp warren
raven arch
raven umbra
inner sky
#

@sharp warren just wanted to say that BEI is allowing me to have a lovely tiny API surface for my KCC:

use bevy::prelude::*;
use bevy_enhanced_input::prelude::*;
use bevy_ahoy::prelude::*;

#[derive(Component)]
struct PlayerInput;

fn spawn_player(mut commands: Commands) {
    // Spawn the player entity
    let player = commands
        .spawn((
            // The character controller configuration
            CharacterController::default(),
            Transform::from_xyz(0.0, 20.0, 0.0),
            // Configure inputs
            PlayerInput,
            actions!(PlayerInput[
                (
                    Action::<Movement>::new(),
                    DeadZone::default(),
                    Bindings::spawn((
                        Cardinal::wasd_keys(),
                        Axial::left_stick()
                    ))
                ),
                (
                    Action::<Jump>::new(),
                    bindings![KeyCode::Space,  GamepadButton::South],
                ),
                (
                    Action::<Crouch>::new(),
                    bindings![KeyCode::ControlLeft, GamepadButton::LeftTrigger],
                ),
                (
                    Action::<RotateCamera>::new(),
                    Scale::splat(0.04),
                    Bindings::spawn((
                        Spawn(Binding::mouse_motion()),
                        Axial::right_stick()
                    ))
                ),
            ]),
        ))
        .id();

    // Spawn the camera
    commands.spawn((
        Camera3d::default(),
        // Enable the optional builtin camera controller
        CharacterControllerCameraOf(player),
    ));
}
#

That's already it. No glue, no managing inputs, no fussing around with schedules, nothing

#

I simply export Movement, Jump, Crouch, and RotateCamera from the library and the user sets up a binding. Done.

#

Great job with the API design of BEI, this is really powerful πŸ˜„

sharp warren
sharp warren
sharp warren
#

For your use case, I'd probably do this ^

Making a custom condition would be overkill.

We could also extend Chord to assign a desired value that triggers the chord, but it's a bit ugly πŸ€”
I think a separate action fits better.

frozen spear
#

if i wanted a wasd-keys-like Cardinal input but with key-overriding behavior (e.g. im holding A going left, and then press down D without letting go A, it should override A and start going right. If i then let go D it should start going left again. if i hold A, press D, then let go A and press it down again, it should start going left again) how would i go about doing that?

sharp warren
frozen spear
#

if i hold D and then press A, it doesnt change direction

#

want it to resolve ties by taking the most recent input direction per axis

#

maybe a third accumulation is needed

sharp warren
frozen spear
#

we could also make it always resolve this way in MaxAbs if the absolute values are the same

#

all my directions are unit length

#

i think this resolution strategy would be preferred if the absolute values are the same

frozen spear
#

the newest one

#

yeah

#

would this just be making this < a <=

Accumulation::MaxAbs => {
    let mut value = self.value.as_axis3d().to_array();
    let other_value = other.value.as_axis3d().to_array();
    for (axis, other_axis) in value.iter_mut().zip(other_value) {
        if axis.abs() < other_axis.abs() {
            *axis = other_axis;
        }
    }
    value.into()
}
#

testing rn

#

nope

#

that just inverts the direction that dominates

#

okay so these things arent ordered by recency

#

hmmm

#

how do we recovery recency?

sharp warren
#

What is your use case, btw? πŸ€”

frozen spear
#

character control

sharp warren
#

For things like movement accumulation usually common

sharp warren
frozen spear
#

oh, uhhh

#

its a lot of things together

sharp warren
#

Do you think accumulation wouldn't work for it?

frozen spear
#

fps-y

#

well it would but i want this specific feel to it

#

cus itd be snappier

#

if the key you press always immediately does the direction then it feels better

#

rather than waiting for the other key to be unpressed or having a couple no movement frames inbetween

sharp warren
# frozen spear cus itd be snappier

It's quite unusual. In all games I've played it's accumulation πŸ˜…
Both modern games (like Marvel Rivals) and old (like Quake 2).

But you can still get this behavior by adding a custom input modifier.

frozen spear
#

oh yeah ive never played a game that does this

#

its just something i want in mine

sharp warren
sharp warren
frozen spear
#

thats unfortunate

#

cus gamepad gets awkward then

sharp warren
#

Yeah... I'm also open extending the crate, but I'm not entirely sure how something like this can be integrated

#

But I'd suggest to try different directions and see how it feels.

frozen spear
#

do you support key chord bindings? stuff like how vs code lets you change document highlighting by pressing ctrl + k and then m in quick succession

sharp warren
frozen spear
#

can you have a different action for ctrl+k than k+ctrl

#

or is it the same

sharp warren
#

Wait, no, when you want a sequence, you need Combo

inner sky
#

@sharp warren is there a way to bind something only to mousewheel down?

#

and not up

inner sky
inner sky
#

this seems so still trigger on both up and down hmm

sharp warren
inner sky
#
2025-11-29T12:24:54.517506Z DEBUG bevy_enhanced_input::action::fns: triggering `STARTED` for `Jump` (`252v0`) for context `241v0`
2025-11-29T12:24:54.517525Z DEBUG bevy_enhanced_input::action::fns: triggering `FIRED` for `Jump` (`252v0`) for context `241v0`
2025-11-29T12:24:54.523512Z DEBUG bevy_enhanced_input::action::fns: triggering `COMPLETED` for `Jump` (`252v0`) for context `241v0`
2025-11-29T12:24:55.984066Z DEBUG bevy_enhanced_input::action::fns: triggering `STARTED` for `Jump` (`252v0`) for context `241v0`
2025-11-29T12:24:55.984081Z DEBUG bevy_enhanced_input::action::fns: triggering `FIRED` for `Jump` (`252v0`) for context `241v0`
2025-11-29T12:24:55.990436Z DEBUG bevy_enhanced_input::action::fns: triggering `COMPLETED` for `Jump` (`252v0`) for context `241v0`

I assume you meant to debug print it manually, as this doesn't show the transform

#
2025-11-29T12:27:32.308240Z  INFO bevy_ahoy::input: jump=On { event: Fired { value: true, state: Fired, fired_secs: 0.0, elapsed_secs: 0.0 }, trigger: EntityTrigger, _marker: PhantomData<()> }
2025-11-29T12:27:33.695994Z  INFO bevy_ahoy::input: jump=On { event: Fired { value: true, state: Fired, fired_secs: 0.0, elapsed_secs: 0.0 }, trigger: EntityTrigger, _marker: PhantomData<()> }
#

well, not much to transform, given that it's a bool output

#

I see, that's probably the issue

#

#[action_output(Vec2)] fixes this

#

#[action_output(f32)] does not FWIW

sharp warren
inner sky
#

sec

#

I'll revert to bool

sharp warren
#

Yeah, I'd expect it to be a bool.

inner sky
#

it seems like it prints stuff every frame

#

Oh wait, I can panic! on the input

sharp warren
#

Yeah, just redirect into a file

sharp warren
inner sky
sharp warren
#

Transform it into X value first

#

I.e. swizzle it

#

Also looks like you have 2 jumps?

inner sky
sharp warren
inner sky
# sharp warren Also looks like you have 2 jumps?

yeah I wasn't sure how to bind it other than this:

(
    Action::<Jump>::new(),
    Press::default(),
    bindings![KeyCode::Space,  GamepadButton::South],
),
(
    Action::<Jump>::new(),
    Clamp::pos(),
    bindings![Binding::mouse_wheel()]
),
inner sky
inner sky
#

same result :/

sharp warren
#

Your action is bool. When the binding gets evaluated, it converts into the action value. It's too late to apply Clamp at this point.

inner sky
#

yesssss

#
(
    Action::<Jump>::new(),
    Press::default(),
    bindings![
        KeyCode::Space,
        GamepadButton::South,
        (Binding::mouse_wheel(), SwizzleAxis::YXZ, Clamp::pos()),
    ],
),
#

this here does everything I need πŸ™‚

sharp warren
#

Yeah, that's what I'd do πŸ™‚

inner sky
#

(except run rustfmt on it bavy )

#

thx for the help!

sharp warren
#

So you basically transform positive wheel into a button

#

You're welcome πŸ™‚

inner sky
inner sky
#

Do you have some advice on how to provide API for default bindings?

#

without it being too magical

#

e.g. required components would maybe work, but that would not allow users to manage their input actions however they want

#

I could provide a function like default_input_actions, but that would that just use a default input context that is already preregistered?

#

sounds a bit magic

#

but having the user provide their own input context in a generic could also be weird

sharp warren
#

But this might limit the customization because all modifiers and conditions will be hardcoded.

#

So it depends on how opinionated your controller will be

torn spade
#

hi @inner sky, or whomever. this is a fairly basic question and my intent is to understand BEI better. in your bevy_ahoy crate, the right axis for a controller is bound to camera controls. in the minimal example, using a controller (ive tried a few controllers but curerntly using a DS4) to look around results in reversed up/down pitching. the BEI example for character control appends ".with(Negate::x())" to the right stick bind. in that example, and when modifying the bind in your minimal setup for ahoy with a negate, this results with reversed left/right, yaw and a proper up/down. to me this implies that both axes are being reversed for some reason, instead of just one.

is this intentional? what am i not understanding about this? my goal is to bind the controls so that pushing upwards on the analog stick looks up, and down looks down, etc

inner sky
torn spade
#

something else i should mention is that your splat scaling works fine for mouse but is way too slow for the controller stick, so i suppose youd do something like .with(splat(.4)) on the right stick

inner sky
#

I'll run it later with a controller and debug the things you mention πŸ™‚

torn spade
#

and thank you very much for the crate. having an example like this to work and learn from is much needed. i was struggling a lot trying to understand physics/movement/system ordering and ahoy has so far been invaluable in filling in blank spots for me

inner sky
#

it will help you much more than reading through the source code, I believe

torn spade
#

yes, ive been reading this. great resource

novel fossil
inner sky
#

@frozen spear in case you get this working, I'd be interested in making it the default for Ahoy πŸ‘€

frozen spear
#

i would like BEI to support it out of the box

#

i think the way to get BEI to support this is to have bind processing be ordered not by declaration order but by pressing order

#

then it gets pretty trivial. idk if that breaks anything though, @sharp warren would know better

sharp warren
sharp warren
mystic torrent
frozen spear
#

it wouldnt change the order in which the input is consumed

#

just the order in which its processed internal

#

BEI has no concept of rotation or moving, only axes afaik

#

@sharp warren i thought a bit more about it, and i think whats actually desired here is timestamps on the input somehow

#

cus that can be used to combine it unambiguously

#

and also be used for other things

sharp warren
frozen spear
#

like knowing exactly when within a frame a button was pressed

sharp warren
frozen spear
#

i think that until we have real timestamps (idk when this happens) it makes sense to have fake internal timestamps of some kind no?

#

like, we could do something as stupid as just reading system time when we see a key press and saving it next to it, with the expectation to switch to real timestamps eventually

#

for wasd ordering that would be enough

sharp warren
#

@frozen spear have you tried manually making this logic by splitting movement into X and Y axes and adding custom logic to implement the desired behavior?

frozen spear
#

no

#

i dont really wanna do that

sharp warren
frozen spear
#

cus its more complicated than it should be, and will make gamepad support painful

#

also im doing other things lol

#

ill probably end up doing that if theres no other way and i really need it

#

im not the only one who wants this though

sharp warren
#

Feel free to ignore the advice, but I'd suggest to quickly hack it together and see how it feels.
Because there might be a good reason why other games don't implement input like that.

frozen spear
#

i already know how it feels because ive used controllers that behave like this

sharp warren
frozen spear
#

software

#

although there are keyboards that do this

#

theres keyboards specifically designed for this, which cancel out A when D is pressed and vice versa, same for W and S

sharp warren
sharp warren
sharp warren
frozen spear
#

i have some C# code that does this that i wrote ages ago somewhere for my game

#

idk where it is now though

exotic dome
#

Razer called this feature snap tap, and it has been very controversial in cs2, afaik it's mostly banned in competitive/pro play these days

frozen spear
#

yeah

#

i think whether its fair or not doesnt matter if you're a gamedev putting it in base game though

#

thats just a feature then and everyone has it. if anything it levels the playing field

exotic dome
#

Yeah I agree, it doesn't have to be unfair, but in CS2 specifically it's seen as a kind of cheat/aid because counter strafing is a core part of the game. If it's part of the game then it's fine.

frozen spear
#

and yeah the hardware impls of this go to even more ridiculous lengths, like tuning the key depth activation threshold to make it even more immediately responsive

inner sky
sharp warren
inner sky
#

(With the obvious caveat that this is discretized to the frame rate)

sharp warren
#

Otherwise it's just the evaluation order

inner sky
#

To be clear, I mean reading the elapsed time in the BEI systems that run in PreUpdate by default

novel fossil
# sharp warren Do you know any game that implements this behavior? Could you also link a script...

Looks like it doesn't work in CS2 anymore but there are configs for other games like TF2 or L4D2. It's called a null movement config. Example: https://github.com/ChadyG/TF2-configs/blob/master/null_movement.cfg

GitHub

general and class specific configs for TF2. Contribute to ChadyG/TF2-configs development by creating an account on GitHub.

sharp warren
frozen spear
#

oooooh yay so its possible?

#

we can make it pick the newest by using the elapsed time in an action modifier?

sharp warren
outer wyvern
inner sky
#

@sharp warren I have a biiiiiiig actions! block right now, and would like to introduce a chord. The examples I could find for chords don't use the actions! macro, I assume due to syntactic limitations. But I would be very sad if I had to replace my entire actions! block haha.
How do I add an action with a chord after the actions! block? I suppose something like with_related::<Action>?

mystic torrent
sharp warren
inner sky
#

New question: what would be your advice for how to record input?

#

I would like to record the inputs I execute in a certain time frame (eg start recording when pressing F3, stop when pressing F3 again) and then play it back as a unit test

#

Or I guess end-to-end test more like

#

Is there some prior art? Or do I basically just write the input timestamp into a text file when the observer for an action is triggered?

#

And then mock those inputs in a test run, dividing the timestamp by some speedup factor

sharp warren
inner sky
#

I'll probably do that crate over the weekend since I kinda need it

#

If you want, I can donate it to simgine after πŸ™‚

sharp warren
#

But having the mention in the readme might be useful πŸ™‚

inner sky
#

Maybe it could work via the events StartRecording and EndRecording and write everything into an Asset

sharp warren
#

Makes sense

inner sky
#

(Events are triggered on a specific entity holding actions)

sharp warren
#

Yep, sounds convenient.

inner sky
#

Could also add IgnoreRecording markers to actions in case someone needs that

#

Then in my KCC, I can conveniently expose that as a StartRecording and StopRecording action instead of an event

#

Since the whole API is action based

sharp warren
#

I like the described API a lot!

inner sky
# sharp warren I like the described API a lot!

Btw I've had even more people asking "can you make the input for this movement support this weird thing" and my answer is still always "yes, it’s BEI, you can bind or mock the action however you want"

#

And I implemented input buffering and coyote time downstream since it was really trivial

#

Well, trivial for me in any case since I input buffer to the next fixed timeframe anyways

#

So I can just as well buffer that input for say 150 ms on top

inner sky
inner sky
#

@sharp warren hope I'm not pinging you too much πŸ˜„ I'm wondering if BEI or Bevy in general has a way to translate keybindings from KeyCode to Key. It seems like BEI's example keybinding menu only shows KeyCodes, but I would like to have the UI display the Key instead

#

I know you can get the Key once an input has already been made, but what about the preconfigured inputs?

#

I.e. if I ship a game so that by default it uses the physical W A S D KeyCodes, how would I get my keybinding UI to display that as D A S H, which are the corresponding Keys on my keyboard layout?

sharp warren
inner sky
#

(for keyboard ofc)

#

for gamepads, idk, I have not thought about that at all πŸ˜„

sharp warren
inner sky
#

but I wasn't able to find it

#

not that it doesn't exist, I'm not terribly familiar with the API

inner sky
sharp warren
#

Damn, looks like not much we could do

inner sky
#

that's really disappointing for UX :/

#

maybe there's some other crate I can use downstream to read the keyboard layout and do some weird conversion that way

sharp warren
#

Since it's for KeyCode, I feel like it belongs more to bevy_input.

inner sky
#

@sharp warren we just ran into something weird. This setup here made Only Tac ever fire, and never Crane

#

is that intentional?

#

Don't get me wrong, the setup is wrong anyways, it should be this:

#

and the second screenshot behaves exactly as expected

#

but our debugging was a bit hindered by the fact that the code in the first screenshot apparently "consumes" the space input, never triggering Crane

sharp warren
inner sky
#

but Press does not consume?

#

that seems a bit odd hmm

sharp warren
inner sky
sharp warren
#

Ah, I see.

inner sky
#

we have like 3 things bound to space haha

sharp warren
#

First action will trigger on one frame and second action on the second

inner sky
#

take a look at this:

sharp warren
#

Actions consume inputs only if they actually do something.

#

Press is basically a single press

inner sky
#

OOOOO

#

AAAA

#

so we are introducing a one frame lag!

sharp warren
#

Yep!

inner sky
sharp warren
inner sky
#

how do I do that when using actions!?

sharp warren
#

The position doesn't matter

#

Just looks nicer logically

inner sky
#

hmm that looks like something I personally want everywhere in my app

#

is there a global setting for disabling consumption?

inner sky
frozen spear
#

wild

sharp warren
sharp warren
inner sky
heady mauve
# inner sky like so?

Do you need them to be separate actions? If how the action behaves depends on the situation which can be determined in advance then having them as unique actions in different contexts might work better. If the result of the action depends on things that may occur after the input is done then it might be better to check for that within the action itself and not have separate versions.

To give an example of the former, if you are touching a wall you might be able to create a WallTouching context that would override the Jump action with a Mantle instead. If however, you are allowing the character to Jump into a Mantle if they hit a low enough wall, then that might be better done within the action itself.

inner sky
#

e.g. some want mantle to be on a different key

#

some want to have jumps on release

#

some don't want to bind cranes at all

#

(already got all those requests)

#

so it seems easiest to just toss the actions at the user and let them decide how to bind them

heady mauve
#

Could you have different contexts for the different play styles?

#

Of course, it may not be an issue for your game.

#

I've had problems in a game where they had some things set to not consume inputs while allowing for user defined controls which lead to two different actions which shouldn't have occurred together to happen.

inner sky
#

The code I posted above is just the one used in an example πŸ™‚

inner sky
tawdry heart
#

Hello everyone. I'm new to online dialogue, so bear with me.

My goal is to allow an action to deactivate its own context upon being fired. This could be useful for a general SwichContextTo<C> action, which could activate the C context and deactivate the action's current context. (This could be useful for folks who want to transition between mutually exclusive, "state-like" input contexts.)

To do this, the action entity must know which component on its context entity is the correct ContextActivity to deactivate.

Although this can be achieved in the aforementioned use case by adding an additional generic parameter to the action (resulting in SwitchContext<C1, C2>), this would require the user to add a new observer for each (C1, C2) combination. It would be more ergonomic to use SwitchToContext<C>, especially if the observer for this action were added as soon as the context C was registered (e.g. in the InputContextAppExt::add_input_context_to method or a similar custom method).

To make an action entity aware of its corresponding ContextActivity component without introducing extra generics on the action, the action entity could automatically include an ActivityId component that wraps the ContextActivity's ComponentId. This would be as simple as adding the following line in InputContextAppExt::add_input_context_to method (or in a similar, custom extension method):

let _ = self.try_register_required_components_with::<ActionOf<C>, ActivityId>(|| ActivityId(activity_id));

Here comes my problem: I am not sure how to deactivate ContextActivity using its ComponentId. If deactivation were to occur simply removing ContextActivity::<C>, I could use something like:

commands.entity(context).remove_by_id(activity_id);
#

However, deactivation instead occurs by inserting ContextActivity::<C>::INACTIVE, which limits me to the unsafe EntityCommands::insert_by_id method, and I don't know how I would make this specifically insert the ContextActivity::<C>::INACTIVE variant.

Any help or course correction would be welcome.

sharp warren
tawdry heart
# sharp warren The entity could have multiple contexts attached to it. So I don't think you can...

To clarify, which of these are you saying?

  1. the action entity can have multiple context components connected to it, or
  2. the context entity can have multiple context components connected to it

In the case of #1, I assumed that each action entity can have no more than one context (because action entities automatically despawn when a single context entity despawns). If this assumption is wrong, then I unquestionably agree with you.

In the case of #2, context entities can have multiple context components. But since an entity can only have 1 of each component, and since each ComponentId is unique in a given world, wouldn't ContextActivity::<A> and ContextActivity::<B> have different ComponentIds (and therefore, different ActivityId(ComponentId) components for their "children" actions to "inherit")?

sharp warren
sharp warren
#

I use this trick inside the BEI itself, so you look at the internals for an example

tawdry heart
limber turtle
#

hiya is it possible to handle trackpad scroll?

sharp warren
limber turtle
#

PanGesture I think?

sharp warren
sharp warren
#

Updated to the latest RC, enjoy.

oak monolith
#

Hi there! I have a use case that I'm not sure how to cover and maybe you could help me out. I have a Hold action that, once fired (and while fired) consumes some resource (picture a stamina bar). I would like to cancel/complete the action once said resource is depleted and the user is still holding down the button so they have to release the button, press and hold again if they want to fire the action again. Any ideas?

sharp warren
oak monolith
sharp warren
oak monolith
#

I see what you mean, I thought you were referring to some InputAction specific logic that I was unaware of. The problem I'm having with that is the observer that listens to the Fire event can check what you say, but if we add a bar regeneration system into it, whenever the condition gets checked again, the input is still Fired but the bar now has some value other than zero and the cycle continues. So I was looking for a way to potentially tell the input library that the action got released, but not necessarily the key/button associated to it. I hope this makes sense. πŸ€”

#

I just found out BlockBy exists, I wonder if that could help at all... But now I see this is an action that blocks another, I don't think this covers my use case.

sharp warren
oak monolith
#

Thank you!

#

I will try some stuff out and let you know how it goes

heady mauve
# oak monolith I see what you mean, I thought you were referring to some InputAction specific l...

I'm not sure, but you might be able to use some sort of layering of contexts so that upon the first input it adds a context layer if all the conditions are valid that uses that context to add the action and the removal of that context layer is done outside of the action (like when it reaches zero).

Something like an Activate action that adds or activates a Consumption context layer on top with a Consume action (that uses the same binding as Activate) where either releasing the binding, or external to it in a system when the bar is drained, the Consumption context is removed, or possibly inactivated. But the Activate action would need to have its conditions met before it could enter the Consumption context so you could check in that observer if there was regeneration going on or whatever that might prevent the activation.

oak monolith
oak monolith
#

I ended up using a boolean state wrapped in a Resource, the observer handling the Fire mutates it and then the other systems read this resource as necessary. For now, it works! Thanks a lot for the ideas, I think it was a "writer's block" situation and I needed a different perspective πŸ˜…

spice knot
#

I want to remove the ability to move on the diagonals.
Is there an easy built in way to do this or do I have to manually filter the input to only be on the x or y axis at one time?

sharp warren
inner sky
#

@sharp warren got a weird one for you after the holidays: It looks like Negate::x() negates both X and Y on my controller hmm

#

the issue with this is that right now, moving the stick up looks down (like when controlling a airplane). Using Negate::x() makes moving the stick up look up, at the cost of making the stick to left look to the right

#

meanwhile, Negate::y() (which is what sounds more correct in this case to my mind) doesn't seem to do anything at all for some reason

#

Unless I'm doing something wrong here

frozen spear
#
impl<X, Y, T: Clone> WithBundle<T> for Axial<X, Y> {
    type Output = Axial<(X, T), (Y, T)>;

    fn with(self, bundle: T) -> Self::Output {
        Axial {
            x: (self.x, bundle.clone()),
            y: (self.y, bundle),
        }
    }
}
#

from bei

#

.with is decomposing axial into two 1d axes

#

and putting the same bundle on both

#

negate x will negate both because both are x

#

negate y wont do anything

inner sky
#

But I get it now, thanks!

#

Any idea how we could work around it? hmm

frozen spear
#

im not really sure

inner sky
#

There’s probably a way to turn this into a tuple I reckon

frozen spear
#

like the obvious answer is to have a separate method for it, but that leaves the footgun there

#

i think its interesting that Axial is kinda completely generic

#

like the fact that you can just use it with pretty much whatever for X or Y

#

Axial { x: Camera::default(), y: Collider::cuboid(x,y,z) }.with(Transform::IDENTITY) is valid code i believe

#

lol yeah using this as the bindings block of an action is valid

Bindings::spawn((
    Cardinal::wasd_keys(),
    Axial {
        x: Camera::default(),
        y: Collider::cuboid(0.0, 0.0, 0.0),
    }
    .with(Transform::IDENTITY)
))
#

i dont know what that means

#

i think the first step is to constrain it such that it cant be used incorrectly, and only then add api surface to allow configuration in correct intuitive ways

inner sky
inner sky
frozen spear
#

remove Negate from the one you dont want negated

#

although im not really sure cus the docs say

/// Inverts value per axis.
///
/// By default, all axes are inverted.
#

not sure how that would make sense tbh

sharp warren
sharp warren
#

Wait, I think I know a good solution to it.

sharp warren
inner sky
sharp warren
inner sky
sharp warren
# inner sky Yep

Okay, let's consider this as a bug, so I can draft a new patch release after you test it.

sharp warren
sharp warren
sharp warren
outer wyvern
sharp warren
inner sky
#

Will try to remember to check tomorrow

#

but it very much looks like it will fix it πŸ™‚

viscid hazel
#

Hi, how can i mock action input?
I read docs, but i have multiple entities with same actions (player, NPCs, etc..):

fn mock_jump(mut commands: Commands, jump: Single<Entity, With<Action<Jump>>>) {
    commands.entity(*jump).insert(ActionMock::once(ActionState::Fired, true));
}

so this jump query can find only one random entity, but i need mock input for specific entity.

p.s. i need this because in bevy_ahoy BEI is only one way to pass orders to char controller.

viscid hazel
#

I figured it out, I'll leave the solution for those who will do something similar:

sysparam for simlicity:

#[derive(SystemParam)]
pub struct SupPawnMovementInput<'w, 's> {
    actions_movement: Query<'w, 's, &'static Actions<ICPlayerMovement>>,
    action_jump: Query<'w, 's, &'static Action<Jump>>,
    commands: Commands<'w, 's>,
}

impl<'w, 's> SupPawnMovementInput<'w, 's> {
    pub fn jump(&mut self, pawn: Entity) -> Result<(), BevyError> {
        let movement_actions = self.actions_movement.get(pawn)?;

        for action_entity in movement_actions {
            let Ok(_) = self.action_jump.get(action_entity) else {
                continue;
            };

            self.commands
                .entity(action_entity)
                .insert(ActionMock::once(ActionState::Fired, true));
        }

        Ok(())
    }
}

system that apply "jump" to some character:

pub fn on_player_jump(
    // .. magic
    mut movement_ctl: SupPawnMovementInput,
) -> Result<(), BevyError> {
    let player: Entity = {..some query code..}
    movement_ctl.jump(player)?;

    Ok(())
}

p.s. this is not best solution, because we need loop over all input actions, but i dont now how do better.

sharp warren
weary maple
#

if and when this gets merged
https://github.com/bevyengine/bevy/issues/17647

itl be even easier since you could just do

fn on_player_jump(mut commands: Commands, action: Single<Entity, (With<Action<Jump>>, RelatedTo<ActionOf<Player>, With<Player>>)>) {
    commands.entity(*action).insert(ActionMock::once(ActionState::Fired, true));
}

assuming I understood the proposal right of course LUL

native pendant
#

any example on moviment with "drag" controls on mobile? i.e., touching the left side of the screen and dragging will create Vec2 movement input from the difference of the initial position and the current location of the touch

sharp warren
# native pendant any example on moviment with "drag" controls on mobile? i.e., touching the left ...

We don't expose touch controls yet :(
But it's very easy to implement.
Just add another variant here: https://github.com/simgine/bevy_enhanced_input/blob/356a439d083c2da4d88ccde8153b4bb66164823a/src/binding.rs#L43
And read it from Bevy here: https://github.com/simgine/bevy_enhanced_input/blob/356a439d083c2da4d88ccde8153b4bb66164823a/src/context/input_reader.rs#L73
If you're interested, I'll be happy to accept a PR. It just requires actually playing with the API to expose actually convenient way to bind touch controls.

GitHub

Input manager for Bevy. Contribute to simgine/bevy_enhanced_input development by creating an account on GitHub.

GitHub

Input manager for Bevy. Contribute to simgine/bevy_enhanced_input development by creating an account on GitHub.

native pendant
#

Has anyone ever complained about having Gizmos on a On<Fire<...>> not working?

sharp warren
native pendant
#

It is an ActionMock with ActionState::Fired

#

Span manual

gleaming falcon
#

is there an example of using picking together with BEI for shortcuts?

native pendant
#

E.g., you have 2 sprites right clicking one does one action on it, right clicking on the other does the action on the other, left clicking on one does a different action on it, etc, is that it?

sharp warren
sharp warren
ionic sundial
#

One thing that just tripped me up in BEI is Chord vs Combo. In VS Code, a chord is where I press, say, cmd+k, then wait a bit, then press something else and VS Code picks up the action (see screenshot).

However, in the all_conditions.rs example, the Chord action only fires if you press 8 and 9 at the same time. This tripped me up because of the difference to VS Code.

I don't really mind, because I suspect BEI is using UE terms, but just thought I'd share in case anyone else comes across this.

sharp warren
ionic sundial
#

@sharp warren I've made a Toggle condition. Here's the gist: https://gist.github.com/mgi388/da68cb3e8fe42ee54326f9bb381a1966. Happy to PR this, or someone else to do it, but I'm still proving that this does what I want for my own use case (and tidying up an example), so haven't done it yet.

One question I had is regarding BEI users and whether they should prefer to mutate a condition (like Toggle), or if they should mutate the ActionState. To elaborate: When an action is toggled on, the BEI user may eventually wants to programmatically toggle the action off. There are two options:

  1. They could query &mut Toggle and set t.toggled = false, or;
  2. They could set the action entity's ActionState to ActionState::None (which is what Toggle's evaluate function ultimately does underneath when the toggled member is false.

cc @inner sky because you wanted Toggle too.

edgy shuttle
#

that's what I've assumed the name comes from

sharp warren
frozen spear
#

the notes of a chord dont necessarily have to be played simultaneously (ex. strumming) or even overlapping (ex. arpeggio) for it to be a chord technically

#

anyways it would be funny to take the music analogy further and call vs code's notion of chords "melodies"

sharp warren
# ionic sundial <@243426730851696640> I've made a `Toggle` condition. Here's the gist: https://g...

So yeah, feel free to PR. The snippet looks good!
But a quick note - I prefer to avoid commenting asserts. Just my preference πŸ™‚ 2 reasons:

  1. You can write a panicking message. So this:
// Initially off
assert_eq!(
    condition.evaluate(&actions, &time, 0.0.into()),
    ActionState::None
);

Can be written like this:

assert_eq!(
    condition.evaluate(&actions, &time, 0.0.into()),
    ActionState::None,
    "Should be off initially"
);
  1. In most cases it's obvious what's happening. Like in the example above. So I most cases I omit messages entirely. There is no need to duplicate Rust in English πŸ™‚
ionic sundial
outer wyvern
fast jasper
# sharp warren Yeah, it’s named after the corresponding modifiers in UE. But we don’t have to s...

IMO chord generally means "a bunch of things at once". This is true for music, emacs, etc. whereas a "combo" is a series actions in succession (like a fighting game).

I think vscode is an outlier here, and it has been documented in their issue tracker as being weird multiple times, with the ultimate reasoning being "we're going to continue stretching the meaning of the word": https://github.com/microsoft/vscode/issues/155556

emacs chord reference: https://www.emacswiki.org/emacs/Chord#chord

ionic sundial
sharp warren
#

Interesting, thanks for the clarification!

@ionic sundial should we close the issue?

ionic sundial
ionic sundial
#

@inner sky a while back you asked about NPCs and input mocking things. #1297361733886677036 message

Is your thinking there that if an NPC can jump, then you also give it BEI components, and notably a Jump action. But you drive the state of that action not from bindings but from an ActionMock? So if you have some AI script thing that says the NPC is meant to jump now, you'd handle that part of the script by adding an ActionMock to the NPC's action entity?

And either your generic Jump handlers/systems would kick in to make the NPC jump (just as the player jumps), or, if NPCs had a different behavior for jumping maybe there are some slightly different jump handling systems for those.

I think it's an interesting approach that I hadn't really thought of. I tend to think of my BEI components and actions as existing on a "Player" entity, of which there's generally only one (ignore multiplayer things).

My use case is an RTS, and I currently have actions on a "Player" entity, and it represents what you can do to the selected unit only. But I'm thinking whether it makes sense for every unit to have all these actions as well, and to drive the enemy units using mocked values (coming from AI scripts, etc.).

spice knot
#

I'm getting an error when trying to use a re-exported version of this crate.

I'm trying to define an InputAction:

use my_crate::enhanced_input::prelude::*;

#[derive(InputAction)]
#[action_output(bool)]
pub struct HoldBreath;

issue is the macro tries to reference ::bevy_enhanced_input::prelude::InputAction which doesn't work because InputAction now lies under ::my_crate::enhanced_input::prelude::InputAction, can't the macro just refer to InputAction directly as it should be inscope if one uses the macro?

sharp warren
spice knot
#

Is there any way to get around this error other than changing the reference then?

sharp warren
#

I don't think it's possible. You need to depend on the crate directly.

spice knot
#

Or what if the reference was changed to bevy_enhanced_input::prelude::InputAction instead, this way it would generally always target the crate but in my case I could write

Use my_crate::enhanced_input as bevy_enhanced_input
sharp warren
spice knot
#

I want to define the version of an external crate in the crate I use it in and then export all external crates for use in crates that depend on it so I have one place that defines the external crates version for all dependencies

sharp warren
sharp warren
spice knot
#

Workspace dependencies feel so clunky but I guess I have no choice

#

(because what if I want to reuse crates in a different project or publish them independent of their workspace)

sharp warren
frozen spear
#

whats the status on upstreaming?

sharp warren
frozen spear
#

cool

#

so its ready

sharp warren
#

We think so, yeah

sharp warren
#
GitHub

I think it&#39;s a much better default.
Otherwise when you change your speed, things like camera controls will also work faster, which is not what user would expect. Or when you pause your game...

GitHub

Evaluate last-spawned contexts first. Modifiers and conditions use insertion order, but this behavior is less convenient for contexts.
For example, when spawning multiple dialogs with a CloseDialog...

outer wyvern
zinc cliff
#

I'm a bit confused on how you could bind both mouse_motion and Axial::left_stick() to the same action

#

Like I'm trying to do:

(
    Action::<CameraRotate>::new(),
    Bindings::spawn((
        Binding::mouse_motion(),
        Axial::right_stick(),
    ))
),
#

Oh never mind, as soon as I find an example with it...

#
(
    Action::<Rotate>::new(),
    Bindings::spawn((
        // Bevy requires single entities to be wrapped in `Spawn`.
        // You can attach modifiers to individual bindings as well.
        Spawn((Binding::mouse_motion(), Scale::splat(0.1), Negate::all())),
        Axial::right_stick().with((Scale::splat(2.0), Negate::x())),
    )),
),
sharp warren
#

Will be simpler with BSN πŸ™‚
Spawning hierarchies in Bevy is not super convenient right now.

floral vector
#

Just switched my control scheme over and am still trying to familiarize myself. How do I distinguish between a button push and a button hold?

#

I know it's probably in conditions.

sharp warren
ionic sundial
#

@sharp warren Any chance you've come across this?

.../index.crates.io-1949cf8c6b5b557f/bevy_enhanced_input-0.20.1/src/condition/fns.rs:48:50:
called `Result::unwrap()` on an `Err` value: QueryDoesNotMatch(593v0, ArchetypeId(876))
stack backtrace: <snip>

Link to BEI source code

I don't have a repro, and I couldn't find an existing issue or bugfix in newer versions.

This is happening in 0.20 after upgrading from 0.18 to 0.20 as part of my project's Bevy 0.16 -> Bevy 0.17 upgrade.

Not 100% sure, but I think it's happening when I do a state transition (in this case I tried to end my battle which transitions to a different state).

Don't think it's useful, but link to the full stack trace: https://gist.github.com/mgi388/27afa2a24d78e6f2cbc271f69c2877a6

sharp warren
ionic sundial
# sharp warren This should never happen πŸ€” If a condition is removed, it should've been previos...

I do lots of DespawnOnExit BTW, including on my BEI player entities.

app.add_systems(OnEnter(Battle), setup_battle_player);
app.add_systems(OnExit(Battle), teardown_battle_player);

// ...

fn teardown_battle_player(mut commands: Commands, player: Query<Entity, With<BattlePlayer>>) {
    let Ok(player_entity) = player.single() else {
        error!("Battle player not found"); return;
    };

    commands
        .entity(player_entity)
        .remove_with_requires::<BattlePlayer>() // necessary to fully remove the context
        .despawn_related::<Actions<BattlePlayer>>();
}

I checked the end of the stack trace which I missed when uploading, but it has:

Encountered a panic when applying buffers for system `engine_battle_player::teardown_battle_player`!
Encountered a panic in system `Pipe(bevy_state::state::transitions::last_transition<engine_battle_state::Battle>, bevy_state::state::transitions::run_exit<engine_battle_state::Battle>)`!

So I wonder if the remove_with_requires and/or despawn_related possibly combined with DespawnOnExit is creating some bad state in the ECS/BEI.

sharp warren
sharp warren
#

Drafted a patch release with a new feature: automatic state activation/deactivation based on states.

ionic sundial
#

@sharp warren while continuing my upgrade to Bevy 0.17 I have another weird bug. Again, I don't have a repro sorry, but thought I'd see if you have any ideas off the top of your head. Sometimes when I load my game my context actions appear to be missing. By that I mean: There is an entry in the Actions<C>.entities Vec but there's no actual entity there. Further, sometimes some of the actions are there, but not all of them. Some egui inspector screenshots should help illustrate.

Look at my GlobalPlayer context's Actions (first screenshot). 7 entries, but no entity data. The GlobalPlayer actions are spawned like this and there's no dynamic disabling or removal of entities on my side, and recall that sometimes they're there.

If we look at another of my contexts that exists at the same time, DeploymentPlayer, we see that there's one of the actions there, but the others are just empty entries (second screenshot). The DeploymentPlayer actions are spawned like this. Now, these actions do have some DisabledOnEnter/EnabledOnExit components so I can disable them depending on the state, but I don't think this is related because a) GlobalPlayer doesn't do this and b) RepositionToCursorOrderAction looks the same as all the others, and from the second screenshot you can see this action shows and c) I also disabled these side effects of these components and it still happens.

Again, sorry I don't have a repro yet and I doubt you can do much, but thought I'd ask if anything stands out to you. I don't see any panics or error logs either. Just sometimes it works, sometimes they appear broken. If the entities themselves looked fine I'd probably think system ambiguity, but because of the broken actions it looks like something else. This is [email protected] and [email protected].

sharp warren
ionic sundial
#

It definitely is strange. Very likely could be user error of course. It’s just suspicious that 0.16 was fine.

#

I’m not using the macro in the snippets above. I could try switching to the macro just in case that is it.

sharp warren
ionic sundial
#

Yeah I figured too.

sharp warren
#

Can you try updating to 0.18?

ionic sundial
#

That said, it’s fine. Just thought I would check if you had any obvious clue why it could be broken. My next steps is to start to break it down into minimal repro, or I might see if I can update the remaining 0.18 deps myself.

sharp warren
#

You can try updating the deps to 0.18 or see if they were already updated by someone and use these branches.

ionic sundial
#

Yep I’ve got a tracking issue of all outstanding deps. There’s just a few like bevy_mod_scripting that might be hard for me to do so I’ve typically just waited.

sharp warren
ionic sundial
# sharp warren I wonder why entity might not exist at this point πŸ€” On each action and binding...

This still happens in Bevy 0.18 BTW:

.../.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_enhanced_input-0.22.2/src/condition/fns.rs:48:53:
called `Result::unwrap()` on an `Err` value: QueryDoesNotMatch(611v0, ArchetypeId(795))

If I change this in my teardown function:

commands
    .entity(player_entity)
    .remove_with_requires::<BattlePlayer>() // necessary to fully remove the context
    .despawn_related::<Actions<BattlePlayer>>();

to this:

commands
    .entity(player_entity)
    .remove_with_requires::<BattlePlayer>(); // necessary to fully remove the context
// .despawn_related::<Actions<BattlePlayer>>();

(i.e., remove the despawn_related call)

It doesn't crash. And for completeness, removing remove_with_requires, but keeping despawn_related also crashes.

Still striving to get a repro for you but wanted to share an update re still not working in Bevy 0.18 (have only just upgraded / in the middle of it).

ionic sundial
sharp warren
ionic sundial
# ionic sundial This still happens in Bevy 0.18 BTW: ``` .../.cargo/registry/src/index.crates....

@sharp warren I believe I have a repro for this one: https://github.com/simgine/bevy_enhanced_input/pull/279 See PR description for more info.

GitHub

…71v0, ArchetypeId(21))

Run example: cargo run --example context_layering
Press X
Press Z
Watch it crash

The example has been changed to disable some actions when you press X. Then when you press...

sharp warren
#

Thanks, I'll fix it today after work!

ionic sundial
inner sky
#

though because BEI / Bevy have no good input buffering story, I need some intermediate representation anyways (to keep the input around until the next fixed update)

#

so as a consequence in my API, you can still opt to not use mocks at all and directly modify that cache

ionic sundial
inner sky
#

I'm also curious to see how this scales in the long run!

ionic sundial
sharp warren
inner sky
#

@sharp warren is there a cleaner way to mock a specific action?

fn set_controller_velocity(
    mut agent_query: Query<(&Agent, &Actions<NpcInputContext>)>,
    mut action_mocks: Query<&mut ActionMock>,
    desired_velocity_query: Query<&LandmassAgentDesiredVelocity>,
    global_movements: Query<(), With<Action<GlobalMovement>>>,
) {
    for (agent, actions) in &mut agent_query {
        for action in actions {
            let Ok(mut mock) = action_mocks.get_mut(action) else {
                error!("Failed to get action mock");
                continue;
            };
            if global_movements.contains(action) {
              // use mock
            }
        }
    }
}
#

if no, would you mind if I upstreamed an analogue to seedling's get_effect?

#

Maybe something like

fn set_controller_velocity(
    mut agent_query: Query<(&Agent, &Actions<NpcInputContext>)>,
    mut action_mocks: Query<&mut ActionMock>,
) {
    for (agent, actions) in &mut agent_query {
        let mut mock = action_mocks.get_mock::<GlobalMovement>(actions).unwrap();
        // use mock
    }
}

sharp warren
#

I would really prefer to have a better generic solution built into Bevy, but I don't mind adding a trait extension in the meantime πŸ™‚

inner sky
#

that bit me many times too

#

oh, though in this case it doesn't matter

inner sky
#

iter_many_mut cannot be chained into filter

sharp warren
inner sky
#
fn set_controller_velocity(
    mut agent_query: Query<(&Agent, &Actions<NpcInputContext>)>,
    mut action_mocks: Query<(Entity, &mut ActionMock)>,
    desired_velocity_query: Query<&LandmassAgentDesiredVelocity>,
    global_movements: Query<(), With<Action<GlobalMovement>>>,
) {
    for (agent, actions) in &mut agent_query {
        let mock = action_mocks
            .iter_many_mut(actions)
            .fetch_next()
            .filter(|(e, _)| global_movements.contains(*e))
            .map(|(_, mock)| mock)
            .unwrap();
    }
}
#

this ugly duckling works

sharp warren
inner sky
#

you're right

#

bleh

sharp warren
#

So yeah, if you want eronomically get action X for context Y, we need either better Bevy support for it, or do something custom.
I heard we're getting hierarchy queries in the next release, though πŸ€”

inner sky
#

This?

fn set_controller_velocity(
    mut agent_query: Query<(&Agent, &Actions<NpcInputContext>)>,
    mut global_movement_mocks: Query<&mut ActionMock, With<Action<GlobalMovement>>>,
    desired_velocity_query: Query<&LandmassAgentDesiredVelocity>,
) {
    for (agent, actions) in &mut agent_query {
        let mock = global_movement_mocks.iter_many_mut(actions).fetch_next().unwrap();
    }
}
#

Pinging @brittle swallow because you also care about this

inner sky
inner sky
#
fn set_controller_velocity(
    mut agent_query: Query<(&Agent, &Actions<NpcInputContext>)>,
    mut action_mocks: Query<&mut ActionMock, With<Action<GlobalMovement>>>,
    desired_velocity_query: Query<&LandmassAgentDesiredVelocity>,
) {
    for (agent, actions) in &mut agent_query {
        let mut iter = action_mocks.iter_many_mut(actions);
        let mut mock = iter.fetch_next().unwrap();
    }
}
#

oof this is suboptimal

#

not your fault ofc

#

that's just missing ergonomics in the ECS

inner sky
#

Oh, while on the topic: how do you feel about implementing Default for ActionMock?

sharp warren
sharp warren
inner sky
inner sky
#

is same PR alright?

#

"improve mock ergonomics"

sharp warren
# inner sky could you elaborate?

Instead of adding an extension trait, we can create a special event that targets context and specifies the action we want to mock.

inner sky
sharp warren
inner sky
#

that would have lovely semantics

sharp warren
#

Yeah, easier to write. You just add Commands to your system.

inner sky
#

like this?

sharp warren
inner sky
#

apparently you feel good about adding that πŸ˜„

inner sky
#

that could be a generic

sharp warren
inner sky
#

heck

#

you're right

sharp warren
#

It needs to be dynamic.

inner sky
#

would that work? hmm

#

it's surely ugly

sharp warren
#

Just make the constructor generic

inner sky
sharp warren
#

I.e. you pass the type into constructor and it stores the TypeId internally.

inner sky
#

If you don't mind currying, this would even work: (sec)

sharp warren
#

You will need to convert TypeId into ComponentId via type registry

inner sky
#
commands.entity(context).trigger(
    Mock::new::<GlobalMovement>(ActionMock::once(...))
);
sharp warren
#

And later find an entity in Actions that stores type

inner sky
#

where Mock::new actually returns an Fn

sharp warren
#

Wait, Actions is generic πŸ€”

inner sky
#

you know what, I'll do the extension trait for now

#

I bet there's a neat design here that can be dug up

#

but we can still switch to it later

sharp warren
#

Wait, I think I have an idea.
We can have a custom command. It can be fully generic.

inner sky
sharp warren
#

commands.mock

inner sky
#

ooooooh

inner sky
sharp warren
#

It's easy to implement. We just need to create a struct MockCommand and implement Command trait for it. The struct will be generic over action and context.
And finally you add an extension trait for Commands to do mock.

#

Yeah, something like this.

#

But you need to write both context and action type.

inner sky
#

oh yep

#
commands
  .entity(context)
  .mock::<NpcInputContext, GlobalMovement>(ActionMock::once(...));
sharp warren
#

For example you can take a look at something like AddChild in Bevy

#

It's also a custom command.

inner sky
#

yeah I know about those, I was thinking in the wrong direction earlier πŸ˜„

#

alright I can do that

#

brb coding it up

#

@sharp warren this would require : Reflect for the involved input action and input context

#

which is fine by me

#

just mentioning

#

or well, at least Any

sharp warren
inner sky
#

or hmm

sharp warren
#

You needed it for event because we can't observe such event without registration

inner sky
#

if it's generic, I don't need a dynamic query at all

#

you're right

sharp warren
#

But it's not the case for command. If your struct is generic, you don't need dynamic queries

inner sky
sharp warren
#

Also, maybe do mock_once command as well?

sharp warren
#

Instead of this

.mock::<TestContext, Test>(ActionMock::once(ActionState::Fired, true));

do this:

.mock_once::<TestContext, Test>(ActionState::Fired, true);
#

And .mock can look like the default constructor. To avoid typing ActionMock

inner sky
#

Now why is it not actually mocking anything hmm

#

the places with ? are all returning Ok

#

Oooh

#

It has a one frame delay like this

#

One update to queue the command, one for applying the mock

#

Hrmm

#

Got any wisdom?

sharp warren
#

Ah, of course. It's because you using commands on the world. It's fine.

sharp warren
#

Maybe we need to implement this extension trait for both world and commands. I did similar thing in Replicon.

inner sky
sharp warren
#

On second though, it's not that useful on the world πŸ€”

inner sky
#

i.e. when calling it in a system

sharp warren
#

No, with the world impl it will work immediately.

#

If you won't use commands() πŸ˜…

inner sky
#

oh, no

sharp warren
#

Hm, no sure tbh

inner sky
#

no it wouldn't

#

since the ActionMock will be handled the next frame by BEI

#

all good

#

then I'm happy with it πŸ™‚

#

my doc example is not quite right, gonna fix that

#

also, IIRC we agreed that consuming inputs should be false by default, right?

sharp warren
#

I don't remember agreeing on this πŸ€”

inner sky
sharp warren
#

I think it will be less confusing for users πŸ€”

inner sky
#

so I right now have to tell consumers "please make sure to set that to false"

sharp warren
#

Yeah, not great.

inner sky
#

I can do a second PR for that

sharp warren
#

Okay, let's do this!

#

A tiny question on the current PR. Do we need to panic there?

inner sky
#

using a Result there would be the wrong semantic

#

ideally this would be rewritten so that the type system knows this fact

#

but idk how without having a multiple borrow

#

(assuming you mean the .expect())

sharp warren
#

Ah, right!
I'd only suggest to change the messages a little:

  • Include actual type to make them more clear.
  • Start messages with a non-capital letter (both panics and errors). That's how it's done in the crate and that's what Rust std lib does.
sharp warren
#

Check other places in the crate for usage examples

inner sky
#

but alright for the others πŸ™‚

inner sky
sharp warren
#

Can't use ` inside code fences, weird πŸ˜…

inner sky
#

done

#

PR should be good now check_accept

sharp warren
#

Maybe move ActionMock and the extension trait into a separate mock module under action?

sharp warren
sharp warren
inner sky
inner sky
#

aight, fixed those

#

nothing dramatic

#

PR ready too πŸ™‚

sharp warren
#

Great, I'll take a look later today πŸ™‚

sharp warren
#

Reviewed, thanks!
Once merged, I'll draft a new release.

inner sky
#

@sharp warren I've addressed everything except this:

I'm looking at the Bevy code for EntityCommand and looks like they do it differently nowadays. The idea is to create an extension trait for EntityWorldMut first with mock and mock_once. And then create a function that returns a closure that simply calls this method. And finally create an extension trait for Commands that queues this function. This way you have a convenience for both Commands and direct world access.

#

could you point me to the code in the Bevy codebase you're referencing?

#

I fail to understand what exactly you mean

sharp warren
# inner sky could you point me to the code in the Bevy codebase you're referencing?

Sure!
Take a look at this module:
https://github.com/bevyengine/bevy/blob/5925b1925841149a6df99beda0c4d831c53d4556/crates/bevy_ecs/src/system/commands/entity_command.rs#L112
Their entity commands are just plain functions.

So instead of having a struct, you just implement your extension trait for ⁨EntityWorldMut⁩ that does the thing directly.
And just create a function that returns ⁨EntityCommand⁩.
And finally implement the extension trait on ⁨Commands⁩. This way you have both: command and entity API.

Feel free to ask questions if anything is unclear.

GitHub

A refreshingly simple data-driven game engine built in Rust - bevyengine/bevy

inner sky
#

will implement that later

sharp warren
inner sky
#

I'll be going to IKEA now, so feel free to implement it if you happen to have time πŸ™‚

#

otherwise I'll do it when I return

sharp warren
#

I'm also need to go rn πŸ˜›

inner sky
#

hehe

outer wyvern
sharp warren
#

Got it, sorry for bothering!

ionic sundial
#

@sharp warren we're missing these register_types (first screenshot) (they're not automatic because of generic type parameters). But to do that we need to add the + TypePath constraint (second screenshot). Which means deriving TypePath on contexts (third screenshot).

Would it be OK to PR this change like this? It would force everyone to add TypePath to their context components. But because the BEI crate doesn't yet have a conditional bevy_reflect feature (or reflect, whatever the name), it would be a bigger change to introduce that feature at the same time.

I guess it's probably going to be better to first introduce a reflect feature before making this change, isn't it...

Use case: self.register_type::<Actions<C>>();, for example, lets me look at and expand the actions in my inspector.

sharp warren
ionic sundial
sharp warren
ionic sundial
# sharp warren I don't know, I think it's convenient to have reflect enabled to see things like...

Yeah I’ll open an issue. I personally don’t think it’s invasive to have to derive reflect or typepath on my components. All of my components derive reflect as a matter of course anyway (I need to do this so I can use an inspector) and you have to derive typepath on asset loaders to make them work as well, so it’s no big deal IMHO to have to do this.

And yeah a second trait method is also an option (and is what I am currently doing in user land).

ionic sundial
sharp warren
inner sky
sharp warren
# inner sky done <a:check_accept:979825485532004372>

Thank you!

I need your opinion πŸ™‚
If we return ⁨⁨Err⁩⁩ from ⁨⁨Command⁩⁩, the app will panic, right?
Do you think it's desirable? This doesn't sound like a critical error to me, maybe just log a warning if there is no such action?
I found panics in gamedev quite annoying for development πŸ˜… A warning would allow me to inspect the entity in the inspector. Once we get editor, this may become even more desirable.
Another advantage is that we'll able to have a single extension trait for both ⁨⁨EntityWorldMut⁩⁩ and ⁨⁨EntityCommands⁩⁩.

inner sky
#

so the general advice right now is to expose as many things as possible as ⁨Result⁩s so that the error handler has the power to handle them accordingly

#

and since panicking is crap, most people right now should switch the default error handler from panic to log imo

#

but that's an on ongoing ECS design thing

#

so IMO we should definitely return a ⁨Result⁩ there

#

any panics resulting from that are not a BEI fault, but a default error handler fault

#

last few times this was discussed, the outcome was "don't assume ⁨Err⁩ == panic for your designs"

inner sky
#

but its panickyness was already changed a few times

#

and it's very very likely that when we have error levels, it will go back to warnings or being ignored again

sharp warren
#

Yeah, I'd expect it to ⁨Result⁩, tbh

#

Very error-prone right now

inner sky
#

so in order to not have it switch yet again, we decided to wait with that

sharp warren
#

Makes sense πŸ˜…

sharp warren
#

@inner sky Thank you a lot! Love this API.

Pushed a few minor changes from my side πŸ™‚

sharp warren
#

Once merged, I'll draft a new release

thick kiln
#

let me check; so the PR is not needed?

sharp warren
outer wyvern
#

@sharp warren I've given you a couple of reviews now πŸ™‚

sharp warren
#

Awesome, thanks!

sharp warren
#

But we need to mention it in the quick start guide.

ionic sundial
ionic sundial
#

@sharp warren @inner sky I started using the new mock_once but I've come across an issue I'd like to get your thoughts on: After usingmock_once, bevy_enhanced_input unconditionally sets enabled = false after expiry, breaking AI-controlled actions.

Use case: I'm using ActionMock for AI-controlled units. When spawning actions, I use ActionMock::new(ActionState::None, false, MockSpan::Manual) to keep the mock enabled (so AI can drive the action). When a unit is player-selected, I set enabled = false so real input bindings work. When deselected, I restore enabled = true for AI control.

The problem: When I call mock_once() to trigger an AI action, after the mock span expires, the code in context.rs sets mock.enabled = false. This makes the AI lose control. The action returns to "player-controlled" state instead of staying in "AI-controlled/mocked" state.

Any ideas? Could ActionMock support restoring to a configurable enabled state after expiry (rather than always false)? For example, a restore_enabled: bool field, or treating MockSpan::Manual differently since it already implies manual lifecycle control?

sharp warren
ionic sundial
# sharp warren I'd expect AI to regain the control after user. I.e. give a different value.

Yeah I guess it can be controlled in user-land. I'm doing this at the moment. Not sure if this will scale / will reveal any future design issues, but this seems to work for now:

app.add_observer(restore_action_mock_after_complete::<CancelOrderAction>);

fn restore_action_mock_after_complete<A: InputAction>(
    complete: On<Complete<A>>,
    mut commands: Commands,
    unit_query: Query<(), Without<UnitSelected>>,
) {
    // If the unit is not selected (i.e., AI-controlled), restore the action
    // mock. If we don't do this, the action mock remains disabled and the AI
    // ends up responding to bindings meant for player-controlled units.
    if unit_query.get(complete.context).is_ok() {
        commands.entity(complete.action).insert(ActionMock::new(
            ActionState::None,
            false,
            MockSpan::Manual,
        ));
        trace!("Restored AI mock for action");
    }
}
sharp warren
ionic sundial
# sharp warren I'm not sure if I get it. Why you reset the mock?

The units in the game all have an action context, and of course a set of action entities related to it. Now, by default I add an ActionMock to all of those actions (with enabled=true). This is so that the non-player controlled units do not respond to bindings/input.

When the player selects a unit, I change that entity/unit's actions and set the ActionMock.enabled=false. This is so that the selected unit can now respond to player input/bindings.

Now, to test some really simple "AI logic", I added a system that does this every 1s:

// Get a random non-player controlled unit.
//
// Call "mock_once" to simulate AI logic where the unit is canceling its current order.
commands
  .entity(unit_entity)
  .mock_once::UnitInputContext, CancelOrderAction>(ActionState::Fired, true);

This works as expected. However, once BEI is done executing this mock, and it has expired, BEI sets the ActionMock.enabled=false (per the context.rs link I shared above).

But now refer back to the start of this message: "I add an ActionMock to all of those actions (with enabled=true)". I need the AI units to have their ActionMock.enabled=true so that they don't respond to player input/bindings, but BEI just set it to false!

Is that a bit clearer?

sharp warren
ionic sundial
# sharp warren Ah, I get it now! Yes, right now I'd probably reset it like this. I wouldn't exp...

I could imagine a BEI API like:

commands
  .entity(unit_entity)
  .mock_once::UnitInputContext, CancelOrderAction>(ActionState::Fired, true)
  .and_then(OnActionMockExpired::SetEnabled);

(this is just pseudo-code, no idea how the API should actually look here, e.g., it could just be another arg to mock and mock_once)

I think part of the reason there is a tension here is that mock_once and mock are sending off non-zero values for the action mocks (and with enabled: true to indicate the mock should now be in effect). But I'm trying to have the default state of the actions with an action mock that also has enabled: true, but with zero values for the action mock.

Said another way, it feels wrong to me that BEI unconditionally sets enabled=false once the mock has expired, because using the mock as an "always on" (i.e., with enabled: true), but sometimes with non-zero values seems like a valid thing to do.

Happy to be proved that this is the wrong thinking though.

sharp warren
ionic sundial
#

I think and_then is a bit too opinionated
Yeah it was just pseudocode to illustrate callers trying to say "restore ActionMock.enabled"

sharp warren
#

Might be more convenient to attach your logic this way.

ionic sundial
ionic sundial
sharp warren
ionic sundial
#

I am also happy to keep thinking about this and seeing if my design should be reworked though.

ionic sundial
#

@inner sky I wonder if we could have try_ versions of entity commands mock and mock_once (by using queue_silenced I believe).

Why? It's easy to experience the panic entity 462v0 has no Actions<MyInputContext> when you're spawning and despawning contexts (e.g., when using context layering) intertwined with mocking.

mock and mock_once probably also need to have their docs updated to mention they can panic.

inner sky
ionic sundial
#

Oh lol not this again.

inner sky
#

I really hate it

ionic sundial
#

Right, so if my error handler warned and didn't panic, I'd have the error warned, right?

inner sky
#

Bingo

#

Set the error handler to log and you're golden

ionic sundial
#

But does my comment still stand insofar as "well, I can't do anything about this warning, the context no longer exists, so if we had try_ versions I'd use call that to indicate I don't care about it failing"?

inner sky
inner sky
#

we could do the exact same thing for try_mock πŸ™‚

ionic sundial
#

Yup. I'll make an issue to track it, don't have time rn to make a PR for it, but will try and get to it later.

inner sky
#

actually, we could override the error handler for mock as well

#

so that it prints a warning

ionic sundial
ionic sundial
# inner sky so that it prints a warning

Ah so for my case even though I have the panic error handler I would now get the warn instead. That would be a better default state I think, but I'd still use the try_ ones to avoid warning log spam.

inner sky
#

honestly, the issue here is that error handling is a bit messy in Bevy

#

but let's at least be consistent

#

I'll whip up a PR rn

#

I happen to have a bit of time

ionic sundial
#

Amazing thanks, if you're free go for it.

#

Yeah I have an overall average experience with Bevy error handling (not great, not the worst), but I don't personally have any great ideas or suggestions to make it better.

Lmao the amount of times I can't work out why a system isn't running and I've yet again forgotten that it was skipped due to a Single query no longer matching.

But that's OT.

inner sky
acoustic marsh
acoustic marsh
#

Actually I guess it should normally? Hmm

Edit: Oh looks like it has flip flopped a few times. That explains it

sharp warren
#

@ionic sundial @inner sky the patch is up πŸ™‚

ionic sundial
#

I wonder if we could improve the docs for ActionState Ongoing and Fired. When you're trying to understand whether to use ongoing or fired, it's not really clear from the enum variant docs what the actual difference is. To me they read exactly the same. Edit: And I still don't actually know what the difference is.

Ongoing: "For example, with the [Hold] condition, this state is set while the key is held down, until the required duration is met."
Fired: "For example, with the [Down] condition, this state remains active as long as the key is held down."

Context: In my Toggle implementation I am trying to understand if I'd use Ongoing or Fired (I'm trying to solve a bug in my game and checking how my Toggle implementation works).

outer wyvern
#

Explicitly contrasting them would be a good strategy

heady mauve
# ionic sundial I wonder if we could improve the docs for `ActionState` `Ongoing` and `Fired`. W...

The documentation you quoted also has these portions that were prior to what you quoted:
ActionState::Ongoing: "Condition has started triggering, but has not yet finished."
ActionState::Fired: "The condition has been met."

Ongoing is used when a condition has some but not all its requirements that need to be met before the condition is active, it is in process though. Fired is when the condition has all its requirements met, so it is active.

In the case of [Hold], the input requirement is met but the necessary time might not have passed so it is set to ActionState::Ongoing. On the other hand, [Down] just requires the input is active and will keep as ActionState::Fired as long as that is the case.

ionic sundial
# heady mauve The documentation you quoted also has these portions that were prior to what you...

Ah right I think that clears it up a bit, thanks.

I honestly didn't know that Ongoing meant "condition has not been met" πŸ˜… .

I thought it was the opposite: That the condition has been met. For example, if I say I am having an ongoing argument with someone, the argument has started and is still happening. So I thought this meant the condition was started and is still happening.

English is hard.

heady mauve
# ionic sundial Ah right I think that clears it up a bit, thanks. I honestly didn't know that `...

Perhaps ActionState might be better named ConditionState since it may help someone consider that the Condition can be Ongoing even if the Action isn't active. I think Press is an example of using Ongoing to prevent further firings until the input has been released and then pressed again. In that case, the ActionState for the Condition goes to Fired and then switches to Ongoing to keep track of the input state.

#

I was mistaken. It is Tap that uses Ongoing.

sharp warren
#

@ionic sundial by any chance you could open a PR to clarify the doc since you understand it properly now?

heady mauve
#

@ionic sundial Could you include your thoughts on the issue as well?

summer patrol
#

i'm currently investigating if its possible to allow binding OpenXR input through this (from a thirdparty crate), but i can't find a way to add any custom binding source? is this currently supported?

#

it would suck if i still had to recommend that users replace their entire input handling system with something else, just because they want to support XR, especially if this is upstreamed into bevy

ionic sundial
#

@inner sky it occurs to me with your thinking here: I wonder if another way to improve the chances of upstreaming BEI and show it's quality is for those new Bevy camera controllers to be upgraded to BEI outside of tree. It's not my call, but even if they lived in BEI so they were also treated as first class citizens, they'd then more easily slot back in tree if BEI was upstreamed. They could just as easily be another 3rd party crate ofc.

sharp warren
sharp warren
summer patrol
sharp warren
sharp warren
ionic sundial
sharp warren
ionic sundial
# sharp warren Another good use case would be shortcuts for buttons. And shortcuts for picking ...

Not sure if this is what you mean regarding picking: But yeah I’d love to see something UI + BEI combined. For example how to make sure clicks on UI elements swallow/consume input so that actions like clicking on your 3d world don’t also fire. I wondered if BEI could somehow provide a UiInputContext with a high priority or something.

Just thinking out loud, can probably do all this in user land easily.

heady mauve
#

Here's the function that actually sent the click:

pub fn activate_ui_selection(
    _: Trigger<Fired<UiActivate>>,
    input_focus: Res<InputFocus>,
    mut commands: Commands,
) {
    if let Some(focused_entity) = input_focus.0 {
        commands.trigger_targets(
            Pointer::<Click> {
                target: focused_entity,
                pointer_id: PointerId::Mouse,
                pointer_location: Location {
                    target: NormalizedRenderTarget::Image(
                        bevy::render::camera::ImageRenderTarget {
                            handle: Handle::default(),
                            scale_factor: FloatOrd(1.0),
                        },
                    ),
                    position: Vec2::ZERO,
                },
                event: Click {
                    button: PointerButton::Primary,
                    hit: HitData {
                        camera: Entity::PLACEHOLDER,
                        depth: 0.0,
                        position: None,
                        normal: None,
                    },
                    duration: Duration::from_secs_f32(0.1),
                },
            },
            focused_entity,
        );
    }
}

BEI allowed me to separate inputs from actions so I could navigate the UI with keyboard and/or gamepad relatively simply. Probably need a little update for newer versions of both Bevy and BEI.

sharp warren
sharp warren
heady mauve
ionic sundial
inner sky
sharp warren
#

Yeah, I really liked it!

sharp warren
ionic sundial
sharp warren
#

Asked in #reflection-dev message

graceful tapir
ionic sundial
graceful tapir
#

observers are only called when their triggers are all on right?

sharp warren
round portal
#

do we really need a whole Action for a modifier here ? I feel it can be simplified to just a bindings entity

heady mauve
#

I do know how I can replicate that with Actions in BEI, but could you explain your solution?