#bevy_enhanced_input

1 messages Β· Page 2 of 1

heady mauve
#

It could also BlockBy the Chain action.

sharp warren
heady mauve
#

On a Combo sequence, it could use a BlockBy condition that is associated with the above InvalidInput action, right? That way if for some reason the developer didn't want to block on all inputs, but only actions they could choose not to do BlockBy.

heady mauve
#

Then the fact that Combo doesn't work on all inputs by default is a feature, not a bug.

sharp warren
#

Agree πŸ˜… It's just most of the time you want to block it on other inputs.

#

But I think Input::Any is a good solution.

#

Should solve the limitation

zinc cliff
#

is there any way to add input buffering?

#

I guess an input condition

sharp warren
zinc cliff
#

Bit confused for how the Vec2/Vec3 outputs work and how to set it up for a Vec3

#

I'm assuming this controls it:

impl<I: IntoBindings> IntoBindings for Cardinal<I> {
    fn into_bindings(self) -> impl Iterator<Item = InputBinding> {
        // Y
        let north = self
            .north
            .into_bindings()
            .map(|binding| binding.with_modifiers(SwizzleAxis::YXZ));

        // -X
        let west = self
            .west
            .into_bindings()
            .map(|binding| binding.with_modifiers(Negate::all()));

        // -Y
        let south = self
            .south
            .into_bindings()
            .map(|binding| binding.with_modifiers((Negate::all(), SwizzleAxis::YXZ)));

        // X
        let east = self.east.into_bindings();

        north.chain(east).chain(south).chain(west)
    }
}
#

but why does it Negate::all()?

zinc cliff
#

Ok I think I see... scalar value turns into Vec3::new(scalar, 0.0, 0.0), then you swizzle it to the position you want it to be by putting x in that spot, so for a y position is YXZ or ZXY, for a z position its ZYX or YZX, negate just flips it for that input and then you fold it all together at the end with the Accumulation::Cumulative stuff

sharp warren
sharp warren
zinc cliff
#

ya I got it after reading through some more and understanding how it works

#

I just wanted a Cardinal + up and down

#

threw it together a little while ago and it works πŸ™‚

sharp warren
# zinc cliff Ok I think I see... scalar value turns into `Vec3::new(scalar, 0.0, 0.0)`, then ...

Not exactly... Let's break it down for the Y axis as an example:

  1. You have a button that outputs a bool.
  2. We apply SwizzleAxis::YXZ, which turns it into a Vec2 with:
    • The X axis equal to zero
    • The Y axis equal to the output value
      (Swizzle does the minimal possible dimension expansion.)
  3. Negate also does the minimal possible dimension expansion, so it just negates the first two axes. Since the X axis is 0, it does nothing and the Y axis gets negated.
sharp warren
sharp warren
zinc cliff
#

I actually wanted to go up another dimension so an output of Vec3

sharp warren
zinc cliff
#

I could throw in a PR

sharp warren
zinc cliff
#

It just looks like Cardinal but 2 added fields for up/down

#

Tbh I just dont know what to even name it lol

#

SixDOF is what I threw in right now

sharp warren
sharp warren
zinc cliff
sharp warren
#

Makes sense

#

Feel free to PR

#

Maybe Alice will think of a better name.

zinc cliff
#

Is there a way to only get raw mouse motion?

#

I suppose this might be a bevy input thing too, but I want to set my cursor to the center of the screen when I grab it, but this gives a mouse motion delta that I don't really want because I'm assuming its a current - previous position

#

yeah I think this is an upstream problem, this isn't correct: "This represents raw, unfiltered physical motion." for MouseMotion

native bison
#

Might want to use rawinput or OS equivalent for that

#

Or just start ignoring cursor events right before centering cursor

zinc cliff
#

I'll just put something in input-dev I suppose, since the docs are just incorrect here and do some sort of ignoring

#

Or I guess winit's docs are also incorrect here?

#

either that or this is a platform specific issue

inner sky
zinc cliff
#

MacOS for the issue above

orchid burrow
#

Hi! Previously in leafwing-manager I had my actions group as enums such as:

pub enum WorldSpeedAction {
    TogglePause,
    RealTime,
    Fast,
    Faster,
    Fastest,
}

What's the idiomatic way to convert this to Bevy Enhanced Input?
Should each enum entry be its own InputAction, and should I then group them by InputContext?

sharp warren
#

Hi!

Should each enum entry be its own InputAction, and should I then group them by InputContext?
Correct, instead of the enum you will have distinct types.

orchid burrow
#

Is there a way to sort of group them together in some fashion to make it clear that they represent one group of actions?

#

(like in the above example, only one of those is going to ever be set at any one time)

#

My intuitive thought originally was that I should make action macro output = the enum, but I'm not sure if that's an intended use case for the output thing, and if a user can create their own output types (it wasn't explicitly mentioned in the docs I believe)

#

And thanks for your answer btw! :)

sharp warren
sharp warren
orchid burrow
#

Alrighty, fair enough! :) Thanks very much

sharp warren
#

You are welcome :)

heady mauve
orchid burrow
#

That hurts my Rusty mind too much 😭 I'll group them in some other way instead hehe

sharp warren
#

Same. Trigger<WorldSpeedActionFast> looks horrible πŸ˜…

#

Just pick a descriptive name, like FastSpeed, etc or something.

orchid burrow
#

The only "problem" is that I had all input actions defined in one file which was nice and neat, now I'll probably put them closer to the module where they're used which IS fine definitely, just need to get used to it

sharp warren
#

It's also a good approach

orchid burrow
#

I suppose just having an inner mod would be fine for grouping them as well

sharp warren
#

I usually use separate contexts for separate things

#

For example, my object placing input placed in one context. UI operations in another. All of them are defined in the place they actually used

orchid burrow
#

Gotcha, good to know! I'll consider that as an option as well

sharp warren
#

So if I have a module with some actions, I usually define actions and context for them in the same module

zinc cliff
#

I'm not sure input context's priority is working for me

#

I'm on 0.10 though, I don't know if this was changed/fixed in 0.11

#

I insert a FlyingCamera input context with a priority of 10 that uses wasd, my Player input context has a priority of 0 that also uses wasd, but when I insert a Actions<FlyingCamera> the inputs continue to go to the Player input context

#

at first I thought maybe the priorities are flipped, 0 being higher and 100 being lower, but that also didn't work

#

do the inputs need to be on the same entity for priority to work?

#

seems not

#

ok nvm I think I was just silly and ran into a 1 frame bug where i removed/inserted the actions and it probably didn't get rebound properly

#

doing a insert_if_new instead of insert fixed the issue

#

either that or an Add vs Insert kind of thing?

#

or wait not entirely...?

#

idk something about removing/re-inserting the Actions messes up the priority I think

zinc cliff
#

I have no idea lol

orchid burrow
#

Just to put this out there, this crate is definitely more verbose for defining the input actions themselves. I wonder if there could be a macro for defining a bunch of inputs at once?

Leafwing:

#[derive(Actionlike, PartialEq, Eq, Hash, Clone, Copy, Debug, Reflect)]
pub enum WorldSpeedAction {
    TogglePause,
    RealTime,
    Fast,
    Faster,
    Fastest,
}

Becomes:

#[derive(InputAction, Clone, Copy, Debug, Reflect)]
    #[input_action(output = bool)]
    pub struct TogglePause;
    
    #[derive(InputAction, Clone, Copy, Debug, Reflect)]
    #[input_action(output = bool)]
    pub struct RealTime;
    
    #[derive(InputAction, Clone, Copy, Debug, Reflect)]
    #[input_action(output = bool)]
    pub struct Fast;
    
    #[derive(InputAction, Clone, Copy, Debug, Reflect)]
    #[input_action(output = bool)]
    pub struct Faster;
    
    #[derive(InputAction, Clone, Copy, Debug, Reflect)]
    #[input_action(output = bool)]
    pub struct Fastest;
#

This is the last I'll bring this up! Just wanted to say it once now that I have them set up correctly :D

#

It's a super minor nitpick of course so feel absolutely free to ignore

orchid burrow
#

Ah, but I also have to handle each of these separately in separate systems, even though the functionality is the same but just with different numbers.

I used to have one system for changing the world speed, but now I think I have to make a separate trigger observer system for each key command, meaning having to duplicate the system params for each simple system.

Just thinking out loud here!

#

So basically:

pub fn set_speed_realtime(
    _trigger: Trigger<Fired<WorldSpeedAction::RealTime>>,
    mut virtual_time: ResMut<Time<Virtual>>,
) {
    virtual_time.set_relative_speed(1.0);
    println!("Speed changed to 1.0");
}

pub fn set_speed_fast(
    _trigger: Trigger<Fired<WorldSpeedAction::Fast>>,
    mut virtual_time: ResMut<Time<Virtual>>,
) {
    virtual_time.set_relative_speed(2.0);
    println!("Speed changed to 2.0");
}

... etc, for each of these.

Honestly not a huge deal, but just bringing this kind of use case to your attention in case it wasn't :)

#

I'll reiterate that intuitively I figured I could just make my own action output type that is an enum of one of these actions, just like how you can have vec2 mapped into what is actually multiple different inputs, by the default vec2 handler

sharp warren
# zinc cliff I have no idea lol

We have tests for this behavior.

Try enable logging via RUST_LOG=bevy_enhanced_input cargo run .... You should see when the context activates and which contexts are registered.

sharp warren
orchid burrow
#

Durp! I was literally just reading that part in the docs πŸ˜„
My apologies

sharp warren
#

No problem!

orchid burrow
#

Okay one more question, given:

#[derive(InputAction, PartialEq, Eq, Hash, Clone, Copy, Debug, Reflect)]
#[input_action(output = f32)]
pub struct CameraZoomAction;

Combined with:
actions.bind::<CameraZoomAction>().to(MouseWheel { mod_keys: Default::default() });

Shouldn't the following trigger when scrolling the mouse wheel:
trigger: Trigger<Fired<CameraZoomAction>>,

I have double checked that the context exists in an entity, was registered, and the observer was added.

#

(I have camera panning using the same context etc, and it works fine)

zinc cliff
#

ya very confused whats going on here

#
2025-04-26T10:10:40.499558Z TRACE bevy_enhanced_input::action_instances: updating input context `arch::character::input::PlayerInput` on `38v1`
2025-04-26T10:10:40.499563Z TRACE bevy_enhanced_input::action_binding: updating action `arch::character::input::Move`
2025-04-26T10:10:40.499637Z TRACE bevy_enhanced_input::action_instances: updating input context `arch::camera::flying::FlyingCamera` on `39v1`
2025-04-26T10:10:40.499714Z TRACE bevy_enhanced_input::action_binding: updating action `arch::camera::flying::CameraMove`
#

PlayerInput has a priority of 0, FlyingCamera has one of 10 here

#

but PlayerInput is being processed first

#

I can't reproduce it outside of what I have so I don't know whats happening here

#

If I just swap it so FlyingCamera is spawned before PlayerInput it now does FlyingCamera before

#

and then the priority also doesn't matter the other way around, if I set PlayerInput priority to 1000 or 0, the FlyingCamera context runs first

#

I'll investigate it some more tomorrow, probably just some random thing I set somewhere that broke things

zinc cliff
#

ok there was a bug in 0.10 that was resolved in 0.11

#

the issue doesn't occur in 0.11, only previous ones

#
use bevy::{input::InputPlugin, prelude::*};
use bevy_enhanced_input::prelude::*;

#[test]
fn priority_consumption() {
    let mut app = App::new();
    app.add_plugins((MinimalPlugins, InputPlugin, EnhancedInputPlugin))
        .add_plugins(plugin)
        .finish();

    app.update();

    app.world_mut()
        .resource_mut::<ButtonInput<KeyCode>>()
        .press(KeyCode::KeyO);

    app.update();
}

fn plugin(app: &mut App) {
    app.add_input_context::<Context1>()
        .add_input_context::<Context2>();

    app.add_observer(bind_context1)
        .add_observer(bind_context2)
        .add_observer(move1)
        .add_observer(move2);

    app.add_systems(Startup, spawn_input_consumption);
}

pub fn bind_context1(
    trigger: Trigger<Binding<Context1>>,
    mut context: Query<&mut Actions<Context1>>,
) {
    let Ok(mut actions) = context.get_mut(trigger.target()) else {
        return;
    };

    actions.bind::<Move1>().to(KeyCode::KeyO);
}

pub fn bind_context2(
    trigger: Trigger<Binding<Context2>>,
    mut context: Query<&mut Actions<Context2>>,
) {
    let Ok(mut actions) = context.get_mut(trigger.target()) else {
        return;
    };

    actions.bind::<Move2>().to(KeyCode::KeyO);
}

pub fn move1(trigger: Trigger<Fired<Move1>>) {
    println!("!!! MOVE 1 TRIGGERED !!!");
}

pub fn move2(trigger: Trigger<Fired<Move2>>) {
    println!("!!! MOVE 2 TRIGGERED !!!");
}

pub fn spawn_input_consumption(mut commands: Commands) {
    let context1 = commands.spawn((Actions::<Context1>::default(),)).id();
    let context2 = commands.spawn((Actions::<Context2>::default(),)).id();
}

#[derive(InputContext)]
#[input_context(priority = 1)]
pub struct Context1;

#[derive(InputAction, Debug)]
#[input_action(output = bool)]
pub struct Move1;

#[derive(InputContext)]
#[input_context(priority = 2)]
pub struct Context2;

#[derive(InputAction, Debug)]
#[input_action(output = bool)]
pub struct Move2;

This works in 0.11, not 0.10

orchid burrow
#

I figured out my scroll wheel issue, the action had to have Vec2 as output, not f32.
So one just uses the y of the Vec2 for "regular" mouse wheel scrolling πŸ‘

sharp warren
orchid burrow
#

Yup that's why I figured to even try it! But yeah, would be nice to have a mention in the docs! :)

sharp warren
#

But glad that there is no issue in the latest version.

sharp warren
native bison
#

Just have to share a horrible issue I was having haha, I deleted my local bevy_enhanced_input and my project stopped compiling. I was depending on my local copy because I wanted some fixes that were on master, but I couldn't remember which version I was depending on. It took me forever to figure it out because it turns out ActionsMarker was never really a thing I think? It was introduced and renamed between two tagged versions πŸ˜‚

atomic rose
#

Hey! I am new to bevy_enhanced_input, really like how the API looks. just one issue, its not working xD. probably me not understanding it, but shouldn't the following work? the plugins are added to my app, I can see that the Actions<OnFoot> component gets added to the player on the client, but it doesnt trigger the binding, and thus no input events happen...

lethal juniper
atomic rose
#

ah thanks

lethal juniper
#

I have made that mistake like four times in the last week. I cannot remember that I need to add the input context. So it's at the top of my mind.

atomic rose
#

haha yeah

#

glad it's an easy fix

outer wyvern
#

Maybe linting?

#

Or inventory-style automatic registration

sharp warren
#

I think it won't be compatible with WASM πŸ€”

#

This is why reflection is not automatic.

lethal juniper
outer wyvern
lethal juniper
#

It would be nice to not have to worry about those registration steps any more though...

#

Save me personally a lot of recompiles 😭

zinc cliff
#

does it make sense to have a run condition that triggers ActionState::Fired and ActionState::Ongoing while held?

#

or am I dumb and just missed the condition for this

#

Release is similar but just in reverse

sharp warren
zinc cliff
#

name wise or?

#

tbh this is what I expected the default to be for key presses, or Press

frozen spear
#

esaeleR

sharp warren
zinc cliff
sharp warren
zinc cliff
#

Nop, is ok though, the usecase I had was a jump for my character:

  • Fired would add a velocity upwards to the character
  • Ongoing would add a slow falloff to the velocity
  • None would add a fast falloff for the velocity
#

so when you hold space you go higher/stay in the air longer, letting going would ground you faster

#

I managed to make this work by just adding a "did i jump last frame" and just using Fired but I probably could've made a RunCondition for it too

#

PressAndHold might be a good name for this condition

heady mauve
zinc cliff
#

I guess using Started<Jump> for initial and then Fired<Jump> for falloff would work too

#

I'm not a huge fan of using the observer triggers because I'd like to order them but I guess they are just events so I could poll it

#

I think I'm just misunderstanding the Ongoing term, it sounds like something that fired and is continuing to be fired to me

#

but Hold uses Ongoing for "Meeting conditions and will be fired if continued to meet it"

heady mauve
#

If you look at ActionEvents in the documents it provides a table of what the various state transitions output for events.

heady mauve
#

If you use observers, then I think using Press to listen for a Started event trigger to add velocity upwards, then Fired for the slow falloff (which will have one activation when it triggers with Started) and then when it transitions back to None it will output a Completed event to do the fast falloff.

For polling, if you wanted to, you could create a new InputCondition like your PressAndRelease that outputs Fired like Press then transition to Ongoing which would output Ongoing events until it transitions to None. Then you could see which states you're looking for. However, if you still use observers it wouldn't necessarily be too different than the first case you'd just watch for different events: Started, Ongoing, and Canceled if the condition outputs the above states.

I guess it depends on which makes more sense to you and whether there would be a performance impact. Polling for the states may not be as responsive.

#

ActionState::Ongoing can be used to indicate an input condition is currently receiving input that matches but it is still in process and hasn't met the condition fully to fire. That's what Hold does.

#

Tap does the opposite. It uses ActionState::Ongoing to check whether the input completes in a short enough time otherwise it will cancel as a condition.

sharp warren
sharp warren
brittle wolf
#

This was probably already discussed.
In 3d game moving player usually means moving in xz axis. Currently input_action(output= vec3) maps to xy. This means that 3d games will have to manually replace y with z and additionally flip the sign of z? Maybe if user specifies output = vec3 in input_action we should do this mapping for a user automatically? Or add one more parameter to input_action that will allow to specify how to map? Or add one more modifier that will do this conversion? Currently it can be done with two modifiers Swizzle::XZY and Negate::z() (this works but it seems it requires a couple of operations and can be made faster).

sharp warren
gleaming falcon
inner sky
sharp warren
#

Yes, it's called ToWorldSpace

inner sky
sharp warren
#
Input space to World space conversion
Auto-converts axes within the Input Action Value into world space    allowing the result to be directly plugged into functions that take world space values.
E.g. For a 2D input axis up/down is mapped to world X (forward), whilst axis left/right is mapped to world Y (right).
#

Unreal have a different coordinate system, but you get the gist :)

inner sky
sharp warren
#

I would appretiate a PR, currently busy with different stuff...
Or I will take a look into it later myself.

inner sky
sharp warren
#

Have a good night!
But if you do it tomorrow, it would be also great πŸ™‚

sharp warren
#

Thinking about it more, not sure if it's a good idea πŸ€”
Action outputs shouldn't necessarily correspond to Transform.

When Bevy gets a proper character controller, you'll likely use Vec2 for direction control rather than Vec3. We just lack a built-in controller currently 😒

That said, I'm open to discussion if you disagree. Perhaps we should open an issue πŸ™‚

brittle wolf
#

If Bevy used Blender coord system this would not be issue at all.

sharp warren
#

Yeah, not a big fan of the Bevy choice...

brittle wolf
#

2d side scrolling games and game menus in some types of games probably will not encounter this issue.

#

only 3d and topdown 2d.

sharp warren
#

It's not really an "issue".
Transform.translation != movement direction.

#

The only reason UE implements ToWorldSpace modifier is just because in blueprints the conversion involves unnecessary boilerplate.

sharp warren
brittle wolf
inner sky
#

Not sure where I stand on this. I see your point, definitely. But I also see the reality that currently all 3D consumers need to write boilerplate for something that should be trivial. But then again, it's fair to say that that should be the business of the character controller and not the input manager. Then again again, Tnua's approach is to just have a generic desired_forward that is allowed to have a Y offset. Not sure who in this case is supposed to bring in the API glue

fast jasper
#

fwiw I would expect a vec3 from an input to be a 3d input device, like a space mouse

heady mauve
#

Even in the case of a space mouse, or 3 axes joystick, it often is better to let the user set the axes individually. For a flight or space sim, one person might prefer roll on what might be considered the X-axis while another might prefer yaw on that axis.

gleaming falcon
inner sky
ebon tinsel
# inner sky Not sure where I stand on this. I see your point, definitely. But I also see the...

I don't think the character controller should be the one responsible for mapping from input space to world space. The character controller is not just for player character - it's also for AI controlled characters, which don't have a camera (which is required for said mapping) and are easier to code in world space. Even if said AI is not NPC but a pathfinder that makes the character go to where the player clicks - it'd still want to work in world space.

Not saying it's the input manager's job either - thought this feature does make more sense there because input manager is always for player input. I really don't think it's that bad to put it in the pipe between the input manager and the character controller.

heady mauve
#

I've been playing around with UI and bevy_enhanced_input, and I don't know whether I'm doing something wrong, whether observers can't handle multiple generics, or if it is something with bevy_enhanced_input. Here is the signature of my binding function:

fn bind_volume<C: InputContext, Down: InputAction, Up: InputAction>(
    trigger: Trigger<Binding<C>>,
    mut actions: Query<&mut Actions<C>>,
)

And here is how I'm trying to call it:

    app.add_observer(bind_volume::<MainContext, MainDown, MainUp>);
    app.add_observer(bind_volume::<MusicContext, MusicDown, MusicUp>);
    app.add_observer(bind_volume::<EffectContext, EffectDown, EffectUp>);

I have used multiple generics successfully in other functions, so that's why I'm confused.

The only other possibility is that since I am trying to apply the bindings to an entity node within the UI hierarchy instead of on the root entity, which has a different InputContext applied to it, there is some issue. I have checked and made sure that the action is getting applied to the entity. I thought that different entities can have different contexts applied to them so multiplayer is possible.

#

Oh...wait...I think I might have solved it. I forgot to add the input contexts.

#

Yup, works now. Such a simple thing that is easy to miss.

sharp warren
#

If anyone figure this out without breaking WASM - let me know!

inner sky
tulip jolt
#

Hi! Is the input serializable?

#

I have a problem where saving the input for demos is basically impossible, because I cannot serialize the input.

#

I tried writing a wrapper around it, and then serializing that, but because TypeId is also not serializable, nor is the action_map public in Action struct I cannot do it.

sharp warren
fringe rover
#

I'm trying to do different things on click and shift+click but can't quite get it to work, the click only action seems to always run.
This is what I have at the moment:

actions
    .bind::<PlaceBuilding>()
    .to(MouseButton::Left)
    .with_conditions(BlockBy::<PlaceBlueprintRepeating>::default());

actions
    .bind::<PlaceBuildingRepeating>()
    .to(MouseButton::Left.with_mod_keys(ModKeys::SHIFT));

PlaceBuilding seems to always trigger even if the Shift key is held. If I remove the PlaceBuilding binding things work as expected.
Sorry if I am missing something obvious

sharp warren
fringe rover
#

Thank you, that worked!
I hadn't considered the fact that the order may be important πŸ˜…

sharp warren
#

We considering automating it πŸ™‚

inner sky
sharp warren
#

But I want to wait for the decision to be made on the Bevy side first.

inner sky
tulip jolt
#

The list of all the inputs - from my knowledge - is the action_map

sharp warren
#

But I didn't add an iterator over the list πŸ€”
If you need it - feel free to PR this function.

zinc cliff
#

should translate a Vec2 wasd into worldspace (well, character object space really)

mint parrot
#

Hello! Probably missing something, but I couldn't find any information on how to "manually" set the value of an action (in particular looking to set an Aim action based on the mouse position in a top-down shooter style game). Is that supported?

sharp warren
mint parrot
#

Ah I see, thanks. So individually triggering Fired<Aim> events and so on as needed for now

sharp warren
#

Yep!
I planning to add a nicer mocking system by directly setting data on Actions component.

unkempt ferry
#

Hi everyone, I want to trigger an InputAction only when the mouse is moved while holding down the middle mouse button. Should I use a Chord for this?

unkempt ferry
unkempt ferry
#

I want to control the movement of the camera along the XZ axis using mouse movement. I used the SwizzleAxis::XZY modifier, but in the final output Vec3, only the x-axis value is correct β€” both the y and z values are always 0. As I understand it, only the y-axis should be 0, while the x and z axes should have valid values. Did I misunderstand something?

sharp warren
unkempt ferry
sharp warren
unkempt ferry
sharp warren
#

@unkempt ferry Could you provide a minimal reproducible example?

sharp warren
# unkempt ferry

Ah, you're applying SwizzleAxis::XZY to the input! I put it at the action level, which is why I couldn't reproduce it. I'd suggest applying it at the action level when possible - the formatting looks much cleaner:

action
    .bind::<Move>()
    .to(Input::mouse_motion())
    .with_modifiers(SwizzleAxis::XZY)
    .with_conditions(Chord::<MouseRight>::default());

However, this was indeed a bug - I've pushed a fix: https://github.com/projectharmonia/bevy_enhanced_input/pull/104

Also, for your specific use case, I wouldn't use swizzle at all. My personal recommendation would be to provide the expected output directly from actions. For example, since you're moving in 2D, I would expect your Move action to output a Vec2 rather than a Vec3 with Y always equal to 0.

unkempt ferry
sharp warren
#

Ah, got it πŸ™‚

inner sky
#

@sharp warren do you think BEI could have some QOL for input accumulation until a fixed update? Or is that firmly in the domain of character controllers?

sharp warren
inner sky
sharp warren
inner sky
#

Or are you saying that moving triggers into fixed updates will already do that?

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

Makes sense, I will think about it πŸ™‚

inner sky
compact flume
robust salmon
# compact flume πŸ‘‹ Spent a bit of time integrating BEI into the default 2d template, so if anyon...

Nice! I just did this yesterday for our team's project too, and wanted to compare with yours - they're very similar! One thing I noticed is you're manually mapping each arrow key; there's a nice preset for things like controller sticks, WASD, and arrow keys built in to BEI you can use: https://github.com/4D4XFUN/bevy-jam-6/pull/3/files#diff-00cee8468905bf18e2e930266cd1aaefcd00ddf851a26bfd431ab5e4cfb631c9R28

robust salmon
# compact flume πŸ‘‹ Spent a bit of time integrating BEI into the default 2d template, so if anyon...

Also had a question for you - I originally did my trigger on Fired like you but I noticed the duck wouldn't stop moving after I stopped giving it input. So I had to add another trigger on Completed (here: https://github.com/4D4XFUN/bevy-jam-6/blob/4a3fc889da6e05824b762185b894f3174e92382a/src/demo/player.rs#L90 )

I'd love to avoid this and only have one trigger, any idea what the difference is between our implementations? At a glance I don't see anything but I'm probably missing it

heady mauve
robust salmon
crude summit
#

How can I get a percentage of how long key was held during FixedUpdate tick?

#

E.g. Update tick rate is ~200, FixedUpdate is 50
The plugin registered that user held key for 3 Update ticks
How I can query ~0.75 value in some FixedUpdate system?

sharp warren
empty whale
#

alright call me stupid but for the life of me i cant figure it out, the goal is to print to the console when holding down two button ie holding w and shift to run/sprint, but i cant seem to get it to work```rust
actions
.bind::<RunChord1>()
.to(RunChord1::KEY)
.with_conditions(BlockBy::<Run>::events_only()); // Don't trigger the action when the chord is active.
actions
.bind::<RunChord2>()
.to(RunChord2::KEY)
.with_conditions(BlockBy::<Run>::events_only());
actions
.bind::<Run>()
.with_conditions((Chord::<RunChord1>::default(), Chord::<RunChord2>::default()));

#

😭 know what i am stupid i didnt realize shift was a mod key

#

still would be useful to know when combining inputs

cinder grail
#

Q: How to represent boolean 4-way motion? Let's say something like pacman or gauntlet, where the player can only move at a fixed speed in each of 4 directions.

#

In particular, I don't want the player speed to be determined by the input bindings

sharp warren
#

What about diagonal movement?

#

Ah, no diagonal movement, right?

cinder grail
#

Diagonal is allowed, x and y movement are independent

sharp warren
#

Then you don't want it to be boolean

#

Otherwise diagonal speed will be higher

cinder grail
#

Diagonal speed should be higher in this case

#

It's a tactic πŸ™‚

#

So for example we could have:

struct Movement {
    left: bool,
    right: bool,
    up: bool,
    down: bool,
}
sharp warren
#

In this case, I'd either provide separate 4 boolean actions.

#

Or just use Vec2, you will need to convert it into floats anyway.

#

And x=1.0 will be right, x=-1.0 will be left. And y is up and down.

cinder grail
#

It just seems odd to convert bool to float to bool to float.

sharp warren
#

Your movement isn't in booleans, it's in float.

cinder grail
#

That is bool (key state), float (action value), bool (fixed speed), float (player position)

sharp warren
#

Why fixed speed is bool?

cinder grail
#

It's comparing the action output to a threshold value I guess

sharp warren
#

Not sure if I get you πŸ€”

cinder grail
#

OK so if we are using WASD or arrow keys there's only one natural speed. But for a joystick we want to disable proportionality

sharp warren
#

I see it like this:

  1. Game device: bool or float.
  2. Action value: Vec2
  3. Action speed: Vec2, you multiple value axes by it.
  4. Position: Vec3, you add values from axes.
cinder grail
#

For this type of game, we don't want to allow the player to move slowly

sharp warren
#

I'd suggest to describe what do you want first and we could suggest how could you achieve it with BEI

cinder grail
#

I understand how to do it, I was just wondering if there was a simpler way. I had originally envisioned something like Cardinal<bool>.

#

Anyway, it sounds like using 4 separate bools is probably the way to go.

sharp warren
#

I'm just not sure what you're trying to achieve.
But using four separate bools for movement is quite unusual - you most likely want a Vec2 with some modifiers πŸ€”

cinder grail
#

What I am envisioning is old-school arcade games, where you had a non-proportional joystick

#

Pacman, defender, galaxian, and so on

#

You can only move at one speed, that's part of the challenge

sharp warren
#

But pacman doesn't have a diagonal movement πŸ€”

cinder grail
#

It's an outlier in that sense

sharp warren
#

I played galaxian on NES, but I do you move faster with diagonals? πŸ€”

cinder grail
#

Well, I knew a guy who was a master at Descent and he always flew diagonally to get the extra speed

sharp warren
#

The diagonal movement doesn't matter to the approach - I was just curious.

cinder grail
#

Admittedly that's about 10 years later

#

Also, for a side-scroller, the speed in the vertical and horizontal directions often have nothing to do with each other. Or at least, the max speed is different in each axis.

sharp warren
#

I would do something like this:

actions.bind::<Move>().to(Cardinal::wasd_keys()).with_modifiers((SnapDirections, Scale::splat(SPEED_FACTOR)));

Where Move is a Vec2 and SnapDirections is a custom modifier that snaps values to -1.0 or 1.0 if any axis is non-zero. It's very easy to write custom modifiers. But I think this one even might need to be upstreamed.

And on trigger you just add values to your Transform.

cinder grail
#

actually it's weirder than that

sharp warren
#

Makes sense, you can use new then. Or write a custom modifier if it's dynamic.

cinder grail
#

in this particular case, y movement is a fixed speed, x movement is fixed thrust

#

but that's just a question of how you apply the values

glad bough
#

Not to be that guy, I'm genuinely curious. But what are the advantages of this crate over leafwing input manager?

fast jasper
# glad bough Not to be that guy, I'm genuinely curious. But what are the advantages of this c...

from a crate development perspective, I basically think of bevy_enhanced_input as leafwing 2 (because of https://bsky.app/profile/did:plc:fjg6pzaigjmfpsfnbyp6m5oc/post/3lkjjtvfglt2d )

A while back, I decide to swallow my #bevy perfectionism and collaborate with Shatur on getting bevy_enhanced_input into a good enough state to upstream. One of my big requirements was that the examples needed to be rewritten to focus on clear user stories.

glad bough
#

Gotcha

#

Also big fan πŸ™‚

outer wyvern
#

And this is a better foundation for building out more complex forms of input handling that map to things like "hold for 3 seconds"

daring kettle
#

hey, not sure if this should go here but im having an issue with my gamepad inputs not being picked up, i have my Move action bound to wasd, dpad and left_stick but only wasd seems to work
ive had a friend try on linux and it seems to work flawlessly but not on macos

actions
  .bind::<Move>()
  .to((
    Cardinal::wasd_keys(),
    Cardinal::dpad_buttons(),
    Axial::left_stick(),
  ))
  .with_modifiers((
    DeadZone::default(),
    SmoothNudge::default(),
    Scale::splat(4.0),
  ));

bevy is definitely picking up the controller though i see it in the logs

#

i tried on both 0.11 and 0.12

#

ive also tried the examples but same result

sharp warren
daring kettle
sharp warren
#

Try to reproduce with raw input. I suspect that the Bevy input just can't see it.

daring kettle
#

yeah i dont think bevy_input can see it at all, it is definitely picking up on the gamepad being present though

#

21v1 is pro controller
56v280 is xbox one controller

sharp warren
#

You probably need to report it upstream. Maybe even not to Bevy, just directly to gilrs.

outer wyvern
#

And there's a high chance it's actually a driver bug T_T

crude summit
crude summit
#

I see

    elapsed_secs: f32,
    fired_secs: f32,

in Action, but they are get zeroed when the key is released

#

It would be very convenient to have a total_secs that is incremented on ActionState::Fired and only reset in FixedPostUpdate (or manually)

sharp warren
sick hearth
#

is this crate better than leafwing-input-manager ?

sharp warren
#

It's a new iteration of it.

sick hearth
#

I already have working code using leafwing-input-manager, should I upgrade to this crate?

sharp warren
#

Depends πŸ™‚
If LWIM already satisfies all your needs, then you can keep using it.
But Alice and I have been focusing on BEI - it takes a completely different approach, and there are plans to upstream it once it matures.

sick hearth
#

alright thanks for your feedback

crude summit
sharp warren
crude summit
#

But what if I pull them in FixedUpdate which run with 32hz?

#

To make a more responsive input I need to compute integral over time for inputs for the last FixedUpdate tick

sharp warren
crude summit
crude summit
sharp warren
#

But I will get less precise resolution πŸ€”

sharp warren
crude summit
#

Not sure if I get you.
What I actually want is to read the WASD input inside a low frequency FixedUpdate at the highest possible resolution.
If a user has managed to press and release a key twice since the last FixedUpdate, then I want the total time the key was held down.

fickle latch
#

I'm working on a roguelike, and my movement is tied to the numpad. I'm trying to figure out how to bind the actions, but I'm not sure how to tackle the diagonals.

actions.bind::<Move>().to((
    // KeyCode::Numpad1.with_modifiers((SwizzleAxis::YXZ, Negate::all())),
    KeyCode::Numpad2.with_modifiers((SwizzleAxis::YXZ, Negate::all())),
    // KeyCode::Numpad3.with_modifiers((SwizzleAxis::YXZ, Negate::all())),
    KeyCode::Numpad4.with_modifiers(Negate::all()),
    KeyCode::Numpad6,
    // KeyCode::Numpad7.with_modifiers((SwizzleAxis::YXZ, Negate::all())),
    KeyCode::Numpad8.with_modifiers(SwizzleAxis::YXZ),
    // KeyCode::Numpad9.with_modifiers((SwizzleAxis::YXZ, Negate::all())),
));

KeyCode::NumpadX.with_modifiers() sounds like it's expecting CTRL ALT SHIFT like we are still describing which button to trigger the action with, not modifying the action being returned.

sharp warren
# crude summit Not sure if I get you. What I actually want is to read the WASD input inside a l...

Ah, I see πŸ€”
Right now, you can’t reliably get this time unless you evaluate your context in PreUpdate and accumulate the time manually - which is definitely not ideal.

There’s also another related issue: https://github.com/projectharmonia/bevy_enhanced_input/issues/106.

I initially tried to design the API to be unified, with no special logic for schedules - you define the schedule, and it just works due to the plugin’s architecture. But it looks like we'll need special logic after all to provide a smooth experience.

I’ll think about the design. Maybe instead of making it abstract over schedules, we could add a special toggle that alters the logic.
If you have ideas about the API, feel free to comment on the issue or open another one!

sharp warren
# fickle latch I'm working on a roguelike, and my movement is tied to the numpad. I'm trying to...

Right now you need a custom modifier that assigns the action value to both axes. They are very easy to write.

But we might want to add things like SwizzleAxis::XXX or SwizzleAxis::YYY for it. And maybe even provide a built-in preset πŸ€” It's quite common for roguelikes.
PR would be welcome. Or I will take a look into it tomorrow πŸ™‚

KeyCode::NumpadX.with_modifiers() sounds like it's expecting CTRL ALT SHIFT
It's because modifiers can be assigned to both actions and keys to modify their output. That's also how it's named in Unreal.

fickle latch
#

I just switched to a list of bool inputs.. I figured I'd at least mention the awkwardness to the modifier stuff as a new user.
I've used UE3 for maybe a month when looking at different engines back in the day. I don't think you need to adhere to UE names, but it's not my project :p.

crude summit
crude summit
#

Also about abstractions, every abstraction has its own cost.
It is difficult to inspect the source that contains generic schedules or callbacks.
You can't use goto defenition on them, and even goto references don't help much.

sharp warren
# fickle latch I just switched to a list of bool inputs.. I figured I'd at least mention the aw...

List of bools also works, but if you want to support input with stick, you probably want a single action. Shattered Pixel Dungeon have good gamepad support, for example.
I will add a modifier and a preset today. You will be able to just copy them without waiting for a release since modifiers and presets can be defined by users.

awkwardness to the modifier stuff as a new user.
If you have any ideas - feel free to suggest a better name, we are open to it.
I just tried to say that the current naming is technically correct - modifiers assigned to inputs modify their output. But I can see how it could be confused with the keyboard modifiers.

sharp warren
fickle latch
fickle latch
#

I followed the context switching example, I have PlayerActions InventoryActions contexts. Everything seems to work fine except, if I open the inventory and push ESC it not only closes my inventory but also quits the game as both actions are tied to the ESC key. PlayerActions holds the quit action, and InventoryActions holds the close inventory action.
I even explicitly state for the close inventory action to consume the input...

#[derive(Debug, InputAction)]
#[input_action(output = bool, consume_input = true)]
pub struct InventoryClose;

pub fn close_inventory(
    trigger: Trigger<Fired<InventoryClose>>,
    mut commands: Commands,
    [...]
) {
    commands
        .entity(trigger.target())
        .remove::<Actions<InventoryActions>>()
        .insert(Actions::<PlayerActions>::default());
    [...]
}

pub fn open_inventory(
    trigger: Trigger<Fired<OpenInventory>>,
    mut commands: Commands,
    [...]
) {
    commands
        .entity(trigger.target())
        .remove::<Actions<PlayerActions>>()
        .insert(Actions::<InventoryActions>::default());
    [...]
}
heady mauve
#

It's possible that it is firing for multiple frames, have you tried making it require_reset = true in the input action?

#[derive(Debug, InputAction)]
#[input_action(output = bool, consume_input = true, require_reset = true)]
pub struct InventoryClose;
fickle latch
#

that seems to have done it

#

Why do my Trigger<Fired<XXX>> events pop more than once in the same context? It sounds like Trigger<Ongoing<XXX>> should fire after the initial kind of like input.just_pressed vs input.pressed

#

Do I just wrap all my triggers in a trigger.fired_secs check to make sure it's 0?

heady mauve
#

If you look at ActionEvents it shows what state transitions exist and what events are fired. Every frame if the current ActionState is ActionState::Fired and the new one is also ActionState::Fired it will send a Fired event. Ongoing is more to let you know if some InputCondition is processing the input but isn't fully activated, like Hold for example.

#

If you have something like:

action::bind<DoOnce>()
    .to(KeyCode::Escape)
    .with_conditions(Press::new(0.5));

Then it should only activate once per press because the condition Press is limiting the events.

fickle latch
#

Press::new(0.0) never fired and I had to use f32::EPSILON Is this intended?

Is there some sort of condition which replicates the old console programs such as DOS by responding to 1 press, but if you held it long enough it would start repeating? I assume not.

heady mauve
#

Pulse allows for repeated firing, so you might want to look at that input condition to get what you want. Did you try Press with any other amount than f32::EPSILON? I have mine set to what I put above Press::new(0.5) and it fires for me. I haven't looked specifically at that code to determine why 0.0 might not work.

fickle latch
#

actually... now that I think about it... it probably needs a threshold for gamepad values.

#

it's probably not a time value

heady mauve
#

Yes, it is an activation threshold.

fickle latch
#

probably after deadzone is applied to gamepads, so f32::EPSILON is probably fine... I'm not really looking at gamepad support anyways.

fickle latch
#

This will at least get me going! thanks for the help!

sharp warren
fickle latch
#

wait... I don't have to spam .with_condition(Press) all over? πŸ˜„

sharp warren
#

No πŸ˜… I probably need to add references to those 2 events from Fired and clarify.

sharp warren
zinc cliff
#

Found a weird edge case of wanting a JustPress/Press that swaps between 2 contexts

#

but the JustPress doesn't really work because the new context is using a different JustPress

zinc cliff
#

wonder if a "JustBound" thing would work

sharp warren
zinc cliff
#

o

#

@sharp warren tysm!

sharp warren
# crude summit Not sure if I get you. What I actually want is to read the WASD input inside a l...

I think this approach should solve your issue as well as missing inputs: https://github.com/projectharmonia/bevy_enhanced_input/issues/106#issuecomment-2953138701
You will need to use triggers to get preciese timings and store in some components (that's what you usually do for other things, such as position). Then inside FixedUpdate you evaluate the logic.

GitHub

Let's assume we are running two variable frames followed by a fixed update frame. If we are using the observer workflow, inputs are being read in PreUpdate by default. This means our schedule l...

crude summit
sharp warren
crude summit
sharp warren
crude summit
#

What if we remove the schedule configuration and always run in PreUpdate? (from your github)
Forget what I wrote about the cost of abstractions.
I just realized that when I will do rollback networking, Update/PreUpdate will not be the most frequent schedule.
The most frequent will be the resimulation ticks, so I would like to register the inputs here.
So abstract schedules is actually a good idea

sharp warren
sharp warren
#

Ah, you rollback some specific schedule πŸ€”

#

But how can I keep the schedule abstraction and avoid missing inputs πŸ€”

crude summit
#

Resumulation is like runnig FixedUpdate for physics several times for past ticks, and it happens inside single FixedUpdate tick

sharp warren
#

Yes, I understand. But I wonder how it works in other crates then.

#

For example, LWIM evaluates in PreUpdate

#

Curious how lightyear deals with rollback for it

crude summit
sharp warren
#

And another issue with this approach, as you mentioned, is that you lose precision.

crude summit
sharp warren
crude summit
#

why?

sharp warren
#

We need a special logic to run in PreUpdate to collect the ring buffer of action states

sharp warren
crude summit
#

We need just 2 schedules: one to populate the ring buffer, and one to clear

crude summit
#

Instead of using ring buffer we can also use some user callback to accumulate the inputs

sharp warren
sharp warren
#

I previosly though we were talking about actions state buffering. This is doable, but the API will be weird...

crude summit
sharp warren
#

@cursive gorge quick question - you replay inputs during rollback in your game, right?
I’m wondering how that works with LWIM πŸ€”

cursive gorge
#

Yea, I load whichever input was stored for that tick when it is replayed

sharp warren
#

Ah, right, LWIM don't have triggers.

cursive gorge
#

Idk how it would interact with LWIM, I've never actually tried using it directly instead of accumulating input into my own input struct first (the server doesn't even know that LWIM exists)

crude summit
#

Something like a SmoothNudge, but cleared in FixedPostUpdate

sharp warren
#

But you probably need a way to clean it somehow πŸ€”

sharp warren
# crude summit I just realized that this can probably already be implemented using a custom inp...

@crude summit Don't get me wrong, I want to provie you with a nice API, I just want to design it properly, so I collecting the information. Let's sum up the requirements:

  1. You need to replay inputs. On the latest master I implemented input mocking. You can use it to set inputs before running the rollback. But you need to put the schedule evaluation to your rollback schedule.
  2. You also need to evaluate during the fixed schedule run. So you need to put the schedule evaluation here as well.
  3. You don't want to lose the precision and miss inputs. So evaluating in your rollback and fixed schedules is not enough.
#
  1. Modifiers might not be flexible enough. We need to provide more information, such as which schedule are you running right now.
crude summit
#
  1. Those structures populated from your library inside FixedPreUpdate
sharp warren
crude summit
sharp warren
#

I like this design!

Okay, let's think what is reasonable to provide from BEI side:

  1. We might want to remove the Schedule configuration from the context. If you configure it for your schedule, you can miss inputs. We might always want to evaluate PreUpdate because of it.
  2. When fixed schedule runs more then 1 time, we additionally evaluate the state in FixedPreUpdate for each additional fixed run to properly trigger events and deal with Completed and Finished.
  3. You will also need to run the evaluation inside your rollback system. Let's export the evaluation system label, so you can additionally schedule it.
    Does it makes sense?
crude summit
#

Yes, it looks good

sharp warren
#

Okay, I'll start working on it. I will ping you when the PR will be ready, so you can check the API in more details. Maybe I'll need to make more adjustments after that.

crude summit
sharp warren
thick kiln
sharp warren
# thick kiln leafwing stores separate values for the update and fixedupdate schedules, and lo...

Yeah, it's a nice implementation!

But we're currently planning a different approach: splitting contexts into two categories: fixed and non-fixed.

  • Non-fixed contexts will evaluate only in PreUpdate.
  • Fixed contexts will also evaluate in PreUpdate to avoid missing inputs, and additionally in FixedPreUpdate for each extra run of the fixed schedule (i.e., when it runs more than once). Users will also be able to schedule additional evaluations in rollback schedules.

Evaluation is the process where we read inputs, perform state transitions, and fire events. Due to the state- and trigger-based design, we need to run this evaluation to ensure things behave as expected.

thick kiln
#

Sounds good, I think that would work!

limpid lake
#

Hey, I was wondering if there is a best practice way to create "cooldowns" for certain inputs, so they could only happen every x seconds or so

#

Also is there a way to bind the same keycode to multiple InputActions, for example:

actions
        .bind::<Toss>()
        .to(settings.keyboard.toss.with_conditions(Press::default()));

    actions
        .bind::<TossAll>()
        .to(settings.keyboard.toss.with_conditions(HoldAndRelease::new(0.3)));
heady mauve
heady mauve
heady mauve
limpid lake
heady mauve
limpid lake
# heady mauve If you currently want to make a cooldown, you might want to try code similar to ...

This is amazing thank you!
Currently implemented this with a component that contains the last elapsed time that a dash has happened

/// Apply movement when `Dash` action considered fired.
fn apply_dash(
    _trigger: Trigger<Fired<Dash>>,
    time: Res<Time>,
    player_controller: Single<(&mut LastDash, &mut LinearVelocity), With<Player>>,
) {
    // Read transform from the context entity.
    let (mut last_dash, mut linear_velocity) = player_controller.into_inner();

    let current_time = time.elapsed_secs().adjust_precision(); // Get current time

    // Check if enough time has passed since the last dash
    if current_time - last_dash.0 >= DASH_COOLDOWN {
        linear_velocity.0 *= DASH_SPEED;
        // Update the last dash time
        last_dash.0 = current_time;
    }
}
#

But I think this is not that clean hahahha

limpid lake
#

hmmmm so basically listen to all events and implement the logic inside the observer function that will check the conditions?

heady mauve
#

If you do something like this:

fn toss(_trigger: Trigger<Canceled<TossAll>>) {
    // toss one item only
}

then it should only fire if TossAll gets cancelled (basically a short press)

limpid lake
#

Ahhh that looks great let me check this quickly and update

heady mauve
#

You might also be able to set up one of the actions with a BlockBy condition, but I haven't played with that enough to know what way to do it.

limpid lake
#

Nice!! it works, thanks @heady mauve

glad bough
#

How can I capture the mouse scroll using this crate? The docs don't make it very clear, since I did the following

#[derive(Debug, InputAction)]
#[input_action(output = f32)]
pub struct Zoom;

actions.bind::<Zoom>().to(Input::mouse_wheel());

But doesn't seem to even trigger the event

#

Wait, I tried

#[derive(Debug, InputAction)]
#[input_action(output = Vec2]
pub struct Zoom;
#

Seems to have worked

heady mauve
#

mouse_wheel() outputs a Vec2 because there can be both vertical and horizontal scrolling.

sharp warren
sharp warren
inner sky
cinder grail
sharp warren
#

At least that's how it works in all game engines I know πŸ€”

#

It's because you don't need to remap things Increment and Decrement.

#

Sometimes you want to active a button using a shortcut. But it can be done by triggering a button click from an action πŸ€”

heady mauve
# cinder grail <@243426730851696640> I wrote up a long discussion w/r/t integrating the core wi...

I've created a generalized version of the stepper widget from bevy_new_2d. My widget creates observers on Increment and Decrement that send a StepperAction enumerated event that use the entity id of the root node of the stepper so that the user of the widget only needs one observer to respond to the various possible actions.

I also am using BEI and attempting to make UI navigation work with a gamepad, and I've done exactly as @sharp warren says, the Action actually causes a button click on the Increment or Decrement.

cinder grail
#

This isn't the sort of thing that you would use in the game's main mode. Those modes don't normally have widgets or input focus.

#

But lots of game settings menus do have widgets and focus. Same goes for inventory screens, skill tree dialogs, anything where the number of actions is greater than the number of controller buttons.

#

Take a look at the inventory screen for Witcher 3, or the construction dialogs in No Man's Sky as an example.

sharp warren
sharp warren
#

At least that's how it's usually done

#

So I would see it as 2 separate things.
I don't think you need bindable game actions for UI navigation πŸ€”

outer wyvern
heady mauve
#

I don't really see anything wrong with using BEI because that is what I've tried to do. I have basic navigation Actions that determine movement through the NavigationMap and also have Actions that activate focused elements or return.

outer wyvern
#

It's important for accessibility

heady mauve
#

It seemed simple to set up an InputContext that is just associated with UI when I need it.

sharp warren
sharp warren
#

Although, it might be good to reuse some of the logic πŸ€”

inner sky
#

It seems logical to me that a user may want to change the UI navigation bindings for accessibility reasons, and BEI fits this paradigm nicely already. Why have two APIs for input binding abstractions when BEI can do it all?
But I'm pretty much an outsider to these things, take this with a grain of salt πŸ˜„

outer wyvern
#

I feel quite strongly that no user inputs should ever be hardcoded in a production game

heady mauve
sharp warren
#

You are right, this sounds reasonable.

Will think about how we can do this in reply in the issue tomorrow! (it's late night for me)

cinder grail
#

@outer wyvern @sharp warren This conversation has actually been very useful.

#

I've come up with something simple that I think will work:

/// Event which can be triggered on a slider to modify the value (using the `on_change` callback).
/// This can be used to control the slider via gamepad buttons or other inputs. The value will be
/// clamped when the event is processed.
#[derive(Event)]
pub enum SetSliderValue {
    /// Set the slider value to a specific value.
    Absolute(f32),
    /// Add a delta to the slider value.
    Relative(f32),
}
#

You can use either bevy_input events or BEI, doesn't matter. Main thing is that the action dispatches one of these puppies to the active focus element.

#

So Increment sends a SetSliderValue::Relative(step) to the slider. Decrement sends a negative step.

#

You can also increment on first pressed, or continuous increment while held down (naturally you'd want to use a smaller step size for the latter).

inner sky
sharp warren
sharp warren
# sharp warren I like this design! Okay, let's think what is reasonable to provide from BEI si...

@crude summit This breaks timing information 😒
When I update in PreUpdate, the delta reflects the time since the last frame.
But in the fixed schedule, the delta is different.
So having one evaluation in PreUpdate and another in FixedPreUpdate during overstep ends up mixing two different deltas - which isn't correct.

In LWIM, they handle it by swapping states (see https://github.com/Leafwing-Studios/leafwing-input-manager/pull/522),
but that can also lead to missed inputs - same issue we’re facing now
(see https://github.com/projectharmonia/bevy_enhanced_input/issues/106 for an example).

I need to think about this more...

crude summit
# sharp warren <@333939300703928324> This breaks timing information 😒 When I update in `PreU...

Since our last conversation, I realized that evaluating inputs in Fixed(Pre)Update is fundamentally incorrect.
To keep the fps high(>100) and make it smooth, I need to prevent renderer stalls.
So I need to keep the execution time of FixedUpdate(and the whole Main) below 10ms.
Either by optimizing physics or pipelining it over multiple frames, or running it completely separate and async from the Main schedule.
At this point, with <10ms steady update, it doesn't make sense for me to capture inputs with higher resolution.
And I also want to play animations for some inputs immediately, so I need observers anyway.

What I would suggest to BEI regarding FixedUpdate is to encourage people to create their own simple abstraction between input and physics.
This way also simplifies networking and physics rollback if needed.

sharp warren
# crude summit Since our last conversation, I realized that evaluating inputs in Fixed(Pre)Upda...

Yeah, I’m also thinking about evaluating inputs only in PreUpdate and letting users swap the state and evaluate manually for rollbacks.
But it also means that Actions::events will return things like STARTED and COMPLETED bits for all fixed runs in a single frame (if set), which might be confusing. On the other hand, users can use observers for that - it’s even more optimal πŸ€”

crude summit
crude summit
# sharp warren Not sure if I get you.

If I want to register just_pressed in FixedUpdate, I will register it in PreUpdate and count in some struct how much times I pressed a key.
Then in FixedUpdate I will read and decrement that value in the struct.

sharp warren
#

*observer for Completed/Started

crude summit
#

I also will use observers for it. I mean that you cannot just read the input in FixedUpdate, you need to consume it, to signal the next FixedUpdate that it was already consumed.

sharp warren
thick kiln
#

I'm not sure I fully followed, but I think some kind of fixed update handling should be part of BEI. It's hard for users to get this right

sharp warren
# thick kiln I'm not sure I fully followed, but I think some kind of fixed update handling sh...

I tried it, but it looks like this breaks things.
I let users specify in which schedule each context should be evaluated, and if you set it to FixedPreUpdate or something similar, it works as expected: triggers fire for each schedule run, and only on the first one the COMPLETED or STARTED bits are set.
But it turns out this results in missing inputs. For example, if you have an input that is pressed and then released between fixed update runs, you miss it in the fixed schedule. See this issue for more details.

To solve this, we need to always evaluate in PreUpdate to capture all inputs. But this means we don't handle the fixed schedule.
BEI provides an observer-first API that works correctly for the fixed schedule already, just by design: the observer triggers each frame, but you don’t run any simulation inside it - you just update the value/state/timing in a component, and the fixed schedule simulates it.
However, timing and COMPLETED/STARTED bits using the pull-based API (where you query the Actions component inside a system and read data from it) might be unexpected. But users can just use push-based API (observers) instead.

I tried something like [this](#1297361733886677036 message), but it only fixes the state and completely breaks timing.

I don't see a perfect solution, but updating in PreUpdate might be a lesser evil. What do you think?
@cursive gorge Curious about your opinion as well.

cursive gorge
#

Afaik the best we can currently do is gather inputs in PreUpdate, then apply them in FixedPreUpdate. Until we can actually get accurate timestamps it'll always be kind of a compromise though. In some cases having everything fire in PreUpdate could already be fine if you modify and update your own input types with it, though I doubt that's an ideal "out-of-the-box" experience for any sort of networking integration

sharp warren
# cursive gorge Afaik the best we can currently do is gather inputs in PreUpdate, then apply the...

gather inputs in PreUpdate, then apply them in FixedPreUpdate
I considered this as well, but there are a few problems with this approach.

Here is how actions evaluated in pseudo-code:

for input in bindings {
    let value = input_reader.value(input);
    // Apply modifiers and calculate the state based on conditions and take the most "significant" state.
}

But If I accumulate things like this:

  1. User press button A.
  2. User releases button B.
  3. User presses button A.

The form is similar to events, but I need them to be in a map form to calculate action states. I guess I could have a layered map for each missed frame, but due to the missing timestamps I wouldn’t be able to tell which frames are "safe" to merge with each other.

And another issue is timing. I provide information on how long an action was triggered, and some modifiers/conditions also use the delta. I can't use the delta from Time<Fixed> - it's incorrect. I can take collect the actual time when I read the input, but then what timing do I use if I have 2 evaluations of the fixed schedule? If I use the fixed delta for the second run, it will result in a mixture of fixed and non-fixed deltas, which I think isn’t correct πŸ€”

I doubt that's an ideal "out-of-the-box" experience
Agree. Keeping the current behavior is still an option too. The behavior is similar to LWIM. But missing inputs are a problem for both crates.

#

fine if you modify and update your own input types with it
Why this would be needed? πŸ€”
If you have a Jump action, can't you observe for it and update the value inside your character controller that is evaluated during the fixed schedule?
Networking is similar - observe for state changes and encode them into a message.

sharp warren
#

Looking into Godot implementation and I think I have an idea of how to get the this work without the mentioned issues πŸ€”

cursive gorge
sharp warren
#

Makes sense.

I working on the implementation that is similar to the current solution, but without the missing inputs problem. But it requires a small refactor first.

compact flume
#

Are there any widely accepted patterns for cooldowns for inputs? I've got BEI working with a UI with Bevy input focus, but currently the inputs fire way too fast. Perhaps theres a smarter way to listen for the events event: Trigger<Fired<UIMovement>>, or perhaps I can build a Local<Timer> into my on_input observer? Curious to see how others recommend doing it.

EDIT: In the end I landed on creating a resource for the cooldown timer, and a seperate system that updates the timer. Not sure why I resisted doing that in the first place, but it feels like maybe the correct thing to do

sharp warren
compact flume
#

I think pulse sounds like what I want, but I'm not entirely clear on how its intended to be used (which is likely user error πŸ˜‚ )

#

Ah because Pulse is an inputCondition and not an inputAction

#

Magical it works! Thanks @sharp warren

sharp warren
#

Lifehack for BEI patterns: search for any tutorials for enhanced input for Unreal Engine, they all applicable here.

compact island
#

ha, I was playing with UI navigation using BEI and have found an interesting bug or feature πŸ˜†
when both my xbox controllers are connected axis input is read(converted) from only one of them
brief investigation narrowed down to these lines in input_reader.rs:

GamepadDevice::Any => self.gamepads.iter().find_map(|gamepad| {
                        gamepad.get_unclamped(axis).filter(|&value| value != 0.0)
                    }),

only first value is read from first controller
sticks are not ideal and there are residue values(like 0.003)
quick solution for me was:

GamepadDevice::Any => {
    let sum = self
    .gamepads
    .iter()
    .filter_map(|gamepad| {
        gamepad.get_unclamped(axis).filter(|&value| value != 0.0)
    })
    .sum::<f32>();

    if sum == 0.0 { None } else { Some(sum) }
}
#

so, my question is what should be expected behaviour here?
as mentioned for GamepadDevice::Any it is supposed to be a sum

heady mauve
#

If you are controlling a cursor or something in the UI where multiple controllers can access the UI at the same time then it probably would be best for each cursor to be controlled independently. If that isn't required, then it probably should restrict which gamepad has authority over the UI so setting to a single gamepad.

compact island
#

in my case Any and sum is exactly what I needed

heady mauve
#

Can you explain your usecase though? Because if you have two gamepads that provide opposite input they will cancel each other out.

compact island
#

I will do it a bit later

#

btw dpad buttons cancel each other

compact island
#

so usecase is simple - two players have to be able to navigate menu from their respective gamepad
if they will be doing opposite input - it's a part of a game )

sharp warren
sharp warren
heady mauve
#

@compact island If you haven't yet, you might want to create an issue and a PR if possible.

compact island
#

ok, I'll open an issue and link a PR to it

round portal
#

I finally gave bevy_enhanced_input a shot. Maaan that's a lot more complicated that leafwing enum. Had to rethink everything. But I love observer aproach

But some stuff is still not clear. Is there a default binding to move cursor with gamepad right stick?..

mossy plover
#

Is it possible to reuse a single function for event handling with bevy_enhanced_input?
I want to detect RedirectFreelookCamera or RedirectFreelookCameraSmooth.

 pub(crate) fn redirect_freelook_camera(trigger:Trigger<Fired<RedirectFreelookCamera>>, mut camera_q:Query<&mut Transform, (With<Primary>, With<Camera>)) {
   /* ... */
 }
 
 /* ... { */
 moveset.bind::<RedirectFreelookCameraSmooth>().to((
   KeyCode::ArrowRight.with_modifiers(Negate::all()),
   KeyCode::ArrowLeft
 )).with_conditions((Press::new(0.2), Hold::new(0.75))).with_modifiers(SmoothNudge::default());
 
 moveset.bind::<RedirectFreelookCamera>().to((
   KeyCode::ArrowRight.with_modifiers(Negate::all()),
   KeyCode::ArrowLeft
 )).with_conditions(Press::new(0.01)).with_modifiers(Clamp::new(Vec3::splat(-0.5), Vec3::splat(0.5))

I know I could probably make a one-shot system called rotate_camera which takes some sort ov In parameter and is called from the other two; however, I think it would just be simpler if I could, somehow, just use the one function, redirect_freelook_camera, to handle both tasks, as they are essentially the same but with different conditions and modifiers.

sharp warren
sharp warren
mossy plover
#

Huh...

#

Why didn't I think ov that.

#

I know it's somewhat pointless, since only I will see the code, since it is marked with pub(crate), but would you say it's a good idea to have a marker trait as a bound to express that only camera redirections are allowed?

sharp warren
mossy plover
#

Would you necessarily discourage it?

sharp warren
round portal
#

At which moment does Trigger<Binding<Context>> is fired?.. I'd imagine on startup when bevy app is aware of existing keyboard/gamepad connected, right?

#

Another question. I want to experiment with split screen, that means to use code from local_multiplayer example. But I also want to have a few Contexts, for example for keybindings in character manipulation and main menu. Seems silly to create enum with First/Second for menu as well, any tips here?

outer wyvern
#

@sharp warren πŸ₯² Thanks Github semi-outage

heady mauve
#

However, you currently do have to register the Context.

round portal
#

But IIUC I do not need to register every single action, right?

heady mauve
sharp warren
heady mauve
sharp warren
outer wyvern
sharp warren
#

Ah, got it πŸ™‚

sharp warren
round portal
# heady mauve Are you wondering how to use the menu with multiple gamepads while still allowin...

Basically. Let me explain what I was doing before on the menu example:
when user presses Escape> I invoke menu modal from which you can go to settings or title screen. When this happens I was doing some contextual changes. like toggle cursor, pause the physics. IIUC I have to switch contexts for that by deleting Ations::<Gameplay> and inserting Actions::<Menu>, I get that.
But if I want to support multiple gamepads, do I also need for Menu context to be an enum with First/Second like in local_multiplayer example?

sharp warren
round portal
#

oh, ok, so if I do not actually need for players to have different key bindings, I do not even need an enum? nice

mossy plover
#

Are InputModifiers applied in the order they are specified when they are registered?
The documentation doesn't say.

I.e. if I have:

moveset.bind::<RedirectFreelookCameraSmooth>().to(/* ... */).with_conditions(/* ... */)
  .with_modifiers((SmoothNudge::default(), Clamp::new(Vec3::splat(-0.5), Vec3::splat(0.5)), Multiplier(1.314)));

is Clamp going to come before Multiplier?
If not (normally), is there a way to make it ordered?

#

(Multiplier is a custom type... and it's what it says on the tin.)

round portal
#

Hmm, so, I have movement and dash function, and I want dash to be directed to the same vector movement is. But is there a way to obtain the movement input from dash observer or do I need to track the movement vector manually?

round portal
#

Another question - I have this button to invoke menu on escape and trying limit hwo much it is fired like in context_switch example.

 actions
        .bind::<Back>()
        .to((KeyCode::Escape, GamepadButton::C))
        .with_modifiers(Pulse::new(0.2))

but I get

error[E0277]: the trait bound `bevy_enhanced_input::prelude::Pulse: IntoModifiers` is not satisfied
   --> crates/models/src/input.rs:112:25
112 |         .with_modifiers(Pulse::new(0.2));
    |          -------------- ^^^^^^^^^^^^^^^ unsatisfied trait bound
    |          required by a bound introduced by this call
    |
    = help: the trait `bevy_enhanced_input::input_modifier::InputModifier` is not implemented for `bevy_enhanced_input::prelude::Pulse`

sharp warren
sharp warren
sharp warren
round portal
#

Oh, thx

I'm using it to invoke the menu modal in the gameplay πŸ™‚
And it's firing so fast the menu is toggling like crazy πŸ˜„

#

I think Fired event should only be fired once per button press by default. I thought the Ongoing is the event you'd want for long press actions

sharp warren
#

I highly suggest to read the quick start guide first πŸ™‚

round portal
#

I did :*)
Might've missed it

sharp warren
round portal
#

I oriented to examples mostly and there was no usage of Started, might be a good idea to add it.

I have one interesting behavior:
So I have an entity to which I insert Actions::<GameplayCtx>::defaut(), which IIUC triggers rebind, but then I crash with:
QueryDoesNotMatch(140v2#8589934732, ArchetypeId(142)) on query in the binding, which is basically the same as in any of the examples. Likely due to the fact that this entity loads mesh and lots of other things which I guess is not fast enough to be ready before rebind event fires.

sharp warren
round portal
#

as I said - mostly same query from examples

#[derive(InputContext, Component, Clone, Copy)]
pub struct GameplayCtx;

fn bind_gameplay(
    trigger: Trigger<Binding<GameplayCtx>>,
    mut players: Query<(&GameplayCtx, &mut Actions<GameplayCtx>)>,
) {
    let (&_id, mut actions) = players
        .get_mut(trigger.target())
        .expect("Failed to query gameplay context actions");

sharp warren
round portal
#

indeed. hulk meme warranted

sharp warren
#

@crude summit @thick kiln @cursive gorge I wrote up the results of my API investigation regarding missing inputs here.
TL;DR: I'm currently inclined to keep the current approach, which works for networking out of the box. @thick kiln mentioned he's already working on an implementation for Lightyear.

crude summit
#

Yeah, timestamps from the driver would be ideal

round portal
#

Ok, migration almost complete. I really like the observers

I'm trying to marry bevy_enhanced_input with tnua controller now. Anyone has experience with that?
tnua requires feeding the basis every frame which kinda goes against observer approach so I have to compromise somewhere

Let's say I query Actions component. How do i get input for any given action? Oh, I see value() method, nvm

sharp warren
#

But pull-based API (querying for Actions) is also fine.

#

On the latest master it's even fully typed.

round portal
#

yeah, Fired would not work, because tnua takes care about idle animations as well
I tried to pull the value with this setup

#[derive(Debug, InputAction)]
#[input_action(output = Vec2)]
pub struct Navigate;
...

// bind
    actions
        .bind::<Navigate>()
        .to((Cardinal::wasd_keys(), Axial::left_stick()))
        .with_modifiers((
            DeadZone::default(), // Apply non-uniform normalization to ensure consistent speed, otherwise diagonal movement will be faster.
            SmoothNudge::default(), // Make movement smooth and independent of the framerate. To only make it framerate-independent, use `DeltaScale`.
            Scale::splat(0.3), // Additionally multiply by a constant to achieve the desired speed.
        ));
...
// system on Update
    actions: Query<&Actions<GameplayCtx>>,
) -> Result {
    let input_value = actions.single()?.value::<Navigate>()?.as_axis2d();
    info!("navigate: {input_value:?}");

Am I doing something weird? because I'm getting (0.0,0.0) on WASD with Cardinal wasd bound

sharp warren
round portal
#

nope, observers work, but polling action value through query yield nothing, tried wit hbool action as well

sharp warren
#

Show us the logs via RUST_LOG=bevy_enhanced_input=debug cargo run ...

heady mauve
#

When are you running the system?

round portal
#

on update

logs seem to show that the action is actually registered

2025-06-19T20:09:16.860564Z DEBUG bevy_enhanced_input::action_map: triggering `Fired { value: Vec2(0.0, 1.0), state: Fired, fired_secs: 0.7635166, elapsed_secs: 0.7635166 }` for `models::input::NavigateModal` for `61v1`
2025-06-19T20:09:16.866919Z DEBUG bevy_enhanced_input::action_map: triggering `Fired { value: Vec2(-1.0, 1.0), state: Fired, fired_secs: 0.7697411, elapsed_secs: 0.7697411 }` for `models::input::NavigateModal` for `61v1

#

hmm, is there something wrong in how I query it? or bind...

heady mauve
#

Is there a reason you are doing a Query instead of a Single?

round portal
#

not really, just how I used to do it, but I use single_mut which does pretty much the same
Upd: I tried it - changed nothing

heady mauve
#

See if you are getting anything directly under the Fired state.

#

Or just an info saying it saw that the action was fired to begin with.

round portal
#

hmm, somehow same

heady mauve
#

So the info doesn't fire?

round portal
#

nope, but debug still fires, I'm confused

heady mauve
#

Can you show your updated system?

round portal
#
fn movement(
...
    actions: Single<&Actions<GameplayCtx>>,
) -> Result {
    let actions = actions.into_inner();
    info!("before");
    if actions.state::<Navigate>()? == ActionState::Fired {
        let input_value = actions.value::<Navigate>()?.as_axis2d();
        info!("navigate: {input_value:?}");
        ...tnua stuff
    }

    Ok(())
}

I see

2025-06-19T20:25:15.062428Z  INFO game::player::control: before
2025-06-19T20:25:15.079098Z  INFO game::player::control: before
2025-06-19T20:25:15.096574Z  INFO game::player::control: before
heady mauve
#

So do you have any info outputting prior to, or after, the if statement? If not, could you add something to make sure that the system is getting to that point? If any of the queries in the system have no members, then I don't think the system will fire at all.

round portal
#

Oh, I tried that, it is definitely printing stuff outside the conditioned part, system is running(i updated the code earlier)
what I do not understand, is why on debug I see the action firing - but when I query it - I see default values

heady mauve
#

You could possibly try doing:

    info!("ActionState: {:?}", actions.state::<Navigate>()?);

before the if to see if it is returning a value or if it is returning None.

round portal
#

it is indeed None even if I slam the wasd aggressively

2025-06-19T20:37:34.425131Z  INFO game::player::control: ActionState: None
#

oh well, I'm going to try to debug it tomorrow

heady mauve
#

Do you have any info in the bind observer? If not, might want to add one.

round portal
#

gimme a sec, I don;t remember what it was giving in observer

#

oh, nothing

#

for all I see observer does not fire, but debug logs are still there

round portal
#

the bind observer is definitely fired, game would crash otherwise
upd: just checked with log

heady mauve
#

Ok

sharp warren
thick kiln
#

Would it be possible to make some of the types Reflect? (so that they can be debugged via the inspector)
ActionState, ActionValue, etc.

round portal
sharp warren
sharp warren
crude summit
sharp warren
thick kiln
sharp warren
crude summit
thick kiln
#

i think both are useful but in my case i need get_mut since the message I sent over the network contains a Vec<NetId> and i need to find the corresponding action

sharp warren
sharp warren
thick kiln
#

I have another slight issue; in my tests I use the UntypedAction.state from Actions to build the message i'm going to send.
But actually if the action is mocked, i should use the mocked action.
I could add methods similar to https://github.com/projectharmonia/bevy_enhanced_input/blob/master/src/input_context/actions.rs#L162
like
pub value_by_id(&self, type_id: TypeId)
which returns the mocked value if it exists, or fallsback to the real value

But I think it might be better to store the mocked values directly on the UntypedActions, no?

GitHub

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

sharp warren
#

I think you don't want to send the mocked value πŸ€”

#

When you mock something, it happens on the next frame. It's a delayed operation

#

I think you need to send the current value.

thick kiln
#

oh it's delayed?

#

ah i see it mocks the InputReader

thick kiln
#

@sharp warren I think i have it working, we can merge the PR above

sharp warren
thick kiln
#

yep!

sharp warren
#

Once Alice approves, I'll merge and draft a patch release since all changes are non-breaking

thick kiln
#

oh sorry

sharp warren
#

No problem πŸ™‚

thick kiln
#

Ah wait i need something else

#

when the server receives the InputMessage with the type_id of the action

#

it needs to bind the InputAction on the Action

#

because the server doesn't anything bound yet

sharp warren
#

This way you should be able to bind actions to the context by storing pointers that perform the registration.

thick kiln
#

but i don't want coupling between actions and context, no?
I was just registering all the InputAction types to assign them an ID, but i wasn't registering them to a specific context

#

hm maybe i can make it work

#

do you know if there could be a way to register a function pointer that would work for any InputContext? i don't think so

#

Basically I have a Actions<C>, i receive an net_id that corresponds to an InputAction (A), and i need to call Actions<C>::bind::<A>()

sharp warren
sharp warren
#

So users will have to register contexts and actions on app.

thick kiln
#

what i meant is that InputActions and Contexts should be independent, right? I don't want to register an InputAction for a specific Context

sharp warren
#

Another idea. What if you run the bind function on the server, but just discard all the mappings? Just to make it work automatically.

sharp warren
#

The PR is merged, let me know when you done, I will just draft a patch

thick kiln
sharp warren
thick kiln
#

Every Actions<C> adds an action using a similar function, no? So maybe there could be under the hood a type erased UntypedActions that could bind the action, and I could store a fn pointer for that function. I'll investigate if that is possible

sharp warren
thick kiln
#

Then instead of registering a type-erased bind for each C and A (num_c * num_a), I would be able to only register a type-erased bind function for each InputAction A.
When i receive a new message with A, I can just call the type erased bind which will just call get_or_create_binding_untyped<A>(&mut actions.bindings, &mut actions.action_map) so i only need to register num_a functions

sharp warren
sharp warren
#

Ah, right, even knowing the C, we can't store a function pointer for a generic context, you need concrete types.

thick kiln
#

I have the Actions<C>, but i just need to call something like bind_untyped(action_net_id, &mut actions.action_map, &mut actions.bindings) and that bind_untyped doesn't depend on C

sharp warren
thick kiln
#

I was trying to make it work with a

pub struct UntypedActionsView<'a> {
    gamepad: &'a mut GamepadDevice,
    bindings: &'a mut Vec<ActionBinding>,
    action_map: &'a mut TypeIdMap<UntypedAction>,
    sort_required: &'a mut bool,
}

impl<C: InputContext> Actions<C> {
    fn get_or_create_binding<A: InputAction>(&mut self) -> &mut ActionBinding {
        let mut untyped_actions = UntypedActions {
            gamepad: &mut self.gamepad,
            bindings: &mut self.bindings,
            action_map: &mut self.action_map,
            sort_required: &mut self.sort_required,
        };
        untyped_actions.get_or_create_binding::<A>()
    }
}

but i get getting errors like cannot return value referencing local variable `untyped_actions` so i switched to passing action_map and binding directly

sharp warren
thick kiln
#

yea, just trying to get a full working example, i think i'm close

thick kiln
#

I have another issue: I have some systemSets such as client:BufferClientInputs (take a snapshot of Actions and put it in the buffer) and server::UpdateActions (read the snapshot contained in the input-message sent by the client, and use that to update the buffer on the server)

I need:

  • client::BufferClientInputs.after(EnhancedInputSystem) on the client, because the input-reader + actions update needs to have happened before i take a snapshot
  • server::UpdateActions.before(EnhancedInputSystem) on the server, because I need to update the Actions using the snapshot before triggering events on the server
  • client::BufferClientInputs.before(server::UpdateActions) in host-server mode (the client needs to set the correct snapshot to the Buffer before the server pops the snapshot to update the Actions)

this gives my a cycle.

I would need more flexibility in the schedule by splitting EnhancedInputSystem into:

  • Update (update the bindings from the InputReader and use the new values to update the UntypedActions)
  • Trigger (emit any triggers from the state)

Then i could do Update -> client::BufferClientInputs -> server::UpdateActions -> Trigger

sharp warren
thick kiln
#

There could be some input_delay, so client actions are stored in the buffer with N tick of delay

sharp warren
#

The changes looks good

thick kiln
#

I'm almost there; i see logs
```2025-06-22T18:31:15.474798Z DEBUG bevy_enhanced_input::input_context::input_action: triggering Completed { value: Vec2(0.0, 1.0), state: Fired, fired_secs: 0.03125, elapsed_secs: 0.03125 } for bevy_enhanced_inputs::protocol::Movement for `121v1````
on the server, but for some reason it doesn't trigger my observer

fn movement(
    trigger: Trigger<Fired<Movement>>,
    mut position_query: Query<
        &mut PlayerPosition,
        // if we run in host-server mode, we don't want to apply this system to the local client's entities
        // because they are already moved by the client plugin
        (Without<Confirmed>, Without<Predicted>),
    >,
) {
    info!("TRIGGER!");
    if let Ok(position) = position_query.get_mut(trigger.target()) {
        shared::shared_movement_behaviour(position, trigger.value);
    }
}
#

hm, it's because on the server i only get the Completed trigger, not Fired

sharp warren
thick kiln
#

no

#
2025-06-22T18:31:15.578020Z TRACE lightyear_inputs::server: action state after update. Input Buffer: InputBuffer<"lightyear_inputs_bei::input_message::ActionsSnapshot<bevy_enhanced_inputs::protocol::Player>">:
} }
Tick(448): ActionsSnapshot { state: ActionsMessage { input_actions: [InputActionMessage { net_id: 0, state: Fired, value: Axis2D(Vec2(0.0, 1.0)), elapsed_secs: Some(0.15625), fired_secs: Some(0.15625) }] } }
Tick(449): ActionsSnapshot { state: ActionsMessage { input_actions: [InputActionMessage { net_id: 0, state: Fired, value: Axis2D(Vec2(0.0, 1.0)), elapsed_secs: Some(0.171875), fired_secs: Some(0.171875) }] } }
Tick(450): ActionsSnapshot { state: ActionsMessage { input_actions: [InputActionMessage { net_id: 0, state: Fired, value: Axis2D(Vec2(1.0, 1.0)), elapsed_secs: Some(0.1875), fired_secs: Some(0.1875) }] } }
Tick(451): ActionsSnapshot { state: ActionsMessage { input_actions: [InputActionMessage { net_id: 0, state: Fired, value: Axis2D(Vec2(1.0, 0.0)), elapsed_secs: Some(0.203125), fired_secs: Some(0.203125) }] } }
Tick(452): ActionsSnapshot { state: ActionsMessage { input_actions: [InputActionMessage { net_id: 0, state: Fired, value: Axis2D(Vec2(1.0, 0.0)), elapsed_secs: Some(0.21875), fired_secs: Some(0.21875) }] } }
 tick=Tick(446) entity=121v1#4294967417
2025-06-22T18:31:15.578038Z TRACE bevy_enhanced_input::input_context: updating input context `bevy_enhanced_inputs::protocol::Player` on `121v1`
2025-06-22T18:31:15.578040Z DEBUG bevy_enhanced_input::input_context::input_action: triggering `Completed { value: Vec2(0.0, 1.0), state: Fired, fired_secs: 0.125, elapsed_secs: 0.125 }` for `bevy_enhanced_inputs::protocol::Movement` for `121v1`
#

my input buffer gets the Fired state though

#

maybe something is resetting the server-state to None between every tick

#

maybe this? TRACE bevy_enhanced_input::input_context::action_binding: updating bevy_enhanced_inputs::protocol::Movement from input

sharp warren
#

Hmmm... Completed is a transition from Triggered to None. So yeah, I think you do an extra update somehwere.

thick kiln
#

I think it might be that the server calls update_from_reader

#

which sets the state to None?

#

how could i disable updating from reader on the server

sharp warren
#

I'd expect you to set the initial state and then mock the next one.

thick kiln
#

i was updating the state directly from the snapshot; it seemed easier because i use the same function update_state_from_snapshot() for the server and the client. On the client, i need to update the state directly (and not mock) because i need to reset the elapsed/fired durations

sharp warren
thick kiln
#

my understanding was that on the binding, i would create a new ActionBinding where inputs: Vec<InputBinding>, is empty, so the reader would be unused and the server wouldn't update the state from the inputs at all

sharp warren
sharp warren
#

I'd expect you on each tick to do both: set the initial state for this tick and then mock the next value

thick kiln
#

on rollbacks?

sharp warren
#

Yeah

thick kiln
#

I guess it would need a bigger reorganization of my code.
Right now i'm under the assumption that:

  • each tick has an ActionState (for BEI, Actions<C>) and I store the relevant data in a snapshot (ActionState/ActionValue)
  • for that tick, I fetch the correct ActionState from the buffer and update the current ActionState using the snapshot (UntypedAction.state = snapshot.state)
  • for example when i Rollback to tick 5, I replace the existing Actions<C> by setting the state/value/durations to the ones from the snapshot at tick 5.

You're saying that instead my snapshot should include something like

struct Snapshot {
   current_state: ActionState,
   current_value: ActionState,
   elapsed_durations: ...,
   next_state: ActionState,
   next_value: ActionValue,
}

?

sharp warren
thick kiln
#

Or i guess i could be using two consecutive snapshots.
So:

  • server receives snapshot for tick T and stores it in buffer. When tick T arrives, server just mocks the state/value from tick T that was recorded in the buffer
  • client rollbacks to tick T. Client restores the state/value/durations from the snapshot of tick T, and when we resimulate tick T+1, we mock the state/value from tick T+1
sharp warren
sharp warren
#

@thick kiln ah, I think instead of T and T+1 it should be T-1 and just T. Sorry for confusing.

thick kiln
#

also one minor thing; on the server I have to always call bind_untyped for each InputAction, to check if i need to bind an action. This runs in O(n), but we could switch to an IndexMap to make it O(1) while keeping iterating fast

thick kiln
#

Hm i think the current design is not compatible with how I do input_delay. https://github.com/cBournhonesque/lightyear/blob/cb/lightyear-bei/lightyear_inputs/src/client.rs#L174
The way things work for me is: (tick = 10, delay = 2)

  • tick 10:
    • FixedPreUpdate: read inputs from InputReader -> take snapshot of ActionState and store in Buffer for tick 12 -> read the tick 10 snapshot from the InputBuffer and update the ActionState using that.
    • FixedUpdate: users can interact with the ActionState from tick 10
    • FIxedPostUpdate: read the tick 12 snapshot from the InputBuffer and use that to restore the ActionState to tick 12

This works because in LWIM, the ActionState contains the entire state of the actions for a given tick. I can just restore from a snapshot by replacing the ActionState with the snapshot from that tick.
In BEI, State/Value are not enough to compute the full values because UntypedActions.events are computed using the state transition.

So instead it would be something like:

  • tick 10:
    • FixedPreUpdate: BEI::Update = read inputs from InputReader -> take snapshot of ActionState (State/Value + durations) and store in InputBuffer for tick 12 -> read the tick 10 snapshot from the InputBuffer and call UntypedAction.mock() -> call ActionBinding.update() again to recompute the tick 10 state/value/events -> BEI::Trigger
    • FixedUpdate: users can interact with the Actions<C> from tick 10
    • FixedPostUpdate: read the tick 12 snapshot from the InputBuffer and call UntypedAction.mock() + reset the durations -> call ActionBinding.update() again to restore the correct ActionEvents

Even if update() was made public so that I could call it multiple times, I don't think it would work because the events are computed from the previous_state to the current_state.

#

I guess what I could do is

  • tick 10:
    • FixedPreUpdate: read inputs from InputReader -> take snapshot of ActionState (State/Value + durations) and store in InputBuffer for tick 12 -> reset the State/Value/durations to the tick 9 snapshot and call UntypedAction.mock() using the state/value from tick 10 -> call ActionBinding.update() again to recompute the tick 10 state/value/events -> Trigger
    • FixedUpdate: users can interact with the Actions<C> from tick 10
    • FixedPostUpdate: reset the State/Value/durations to the tick 11 snapshot and call UntypedAction.mock() using the state/value from tick 12 -> call ActionBinding.update() again to restore the events.

An alternative while I get all of this figured out would be to also send the events/elapsed_duration/fired_duration through the network, that way the snapshot i get for a given tick is a self-contained representation of the Actions<C> for a given tick, and i can freely swap/restore from snapshots

thick kiln
#

Ok, when storing the ActionEvents and durations in the snapshot, i have something that works!
I will merge what i have for now; switching to a version that uses the mocks will be more involved

sharp warren
# thick kiln Ok, when storing the `ActionEvents` and durations in the snapshot, i have someth...

Yes! After reading this, I think mocking is just not the right tool for this.

So here is what I think:

  1. Split update and trigger as you already did in your PR. This way you can read the input and quickly swap the entire state before any triggers.
  2. On headless server we don't need update at all. Probably could just disable this system entirely by attaching a run condition to the label. It's public, so it's possible to do outside of the crate.
  3. Maybe we could run the binding observer on the server as well? And just ignore the mappings. This way you don't need type-erase actions.
    What do you think?
thick kiln
#

Ok things definitely work with https://github.com/projectharmonia/bevy_enhanced_input/pull/128
I agree with 1 and 2!
For 3 i'm not sure it would work; the bindings might be updated purely on the client-side no? For example if they are changing the bindings in a settings menu. I think it's safer to have the server update the bindings upon receipt

GitHub

On the receiver (server), i need to be able to bind an action to a context in an untyped manner. This change allows me to register a function pointer for each InputAction, instead of for each Input...

sharp warren
sharp warren
# thick kiln Ok things definitely work with https://github.com/projectharmonia/bevy_enhanced_...

Reading the PR, you mentioned that you network ActionEvents. Can you just network ActionState and compute the events from the previous state using https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/input_context/events/struct.ActionEvents.html#method.new ?

thick kiln
#

I'd prefer not to for now, for the reasons I outlined above. In the case of input-delay, I would need to fetch the previous state from the InputBuffer, as the current Actions::state() might not be the correct one.
In the future I might do a PR where I compute things using the previous_state + mock, in which case I won't need to network the durations or the ActionEvents anymore, but I would prefer to have a working version with the current design if it's ok with you

sharp warren
thick kiln
#

I think that's ok for now; LWIM networking was even more expensive since everything was stored multiple times! https://github.com/Leafwing-Studios/leafwing-input-manager/blob/main/src/action_state/action_data.rs#L247
I'm super happy to have something working right now, I'll work on optimization in a later phase.

But yeah not sending durations/events will be great for bandwidth, especially because State/Value is extremely compressible, i'm sure that in a lot of cases the State of tick T is equal to the state of tick T-1

GitHub

A straightforward stateful input manager for the Bevy game engine. - Leafwing-Studios/leafwing-input-manager

sharp warren
#

I mean in the future, not now πŸ™‚

sharp warren
# thick kiln Ok things definitely work with https://github.com/projectharmonia/bevy_enhanced_...

Looking into the PR β€” it's good, but I’m not satisfied with the type erasure. I know it was my suggestion; I'm just looking at it now and don't like it πŸ˜… It forces us to point to UntypedActions in our docs, while users interact with Actions<C>.
Let me think about this a bit more. I'm going to sleep now. I'll take another look tomorrow after work with a fresh mind and maybe I will be able to suggest something nicer. If you will have other ideas - let me know as well πŸ™‚

thick kiln
thick kiln
#

Maybe the simplest is to just have a special unsafe method that returns &mut bindings and &mut action_map which I can use for the type-erasure

sharp warren
#

I have a crazy idea. What if we turn all actions into children entities for an entity with input context component? Everything (modifiers, conditions, bindings, values, etc.) will be regular components.

Advantages:

  1. No type erasure for Actions<C> needed to bind actions because actions are just entities. Should make it simpler for @thick kiln.
  2. Binding process could be done by loading a Bevy scene, which automatically solves https://github.com/projectharmonia/bevy_enhanced_input/issues/101.
  3. I remember @cursive gorge mentioned that if actions were components, it would simplify integration with her input queue plugin. Is it implemented on top of replication with custom functions? πŸ€”

Disadvantages:

  1. Pull-based API will work with ActionValue enum instead of the defined output of the action.
  2. I have to rewrite a lot of code πŸ˜…
cursive gorge
#

The input queue is just based in components that get sent and stored in a queue instead of applied normally. I'm not sure if having dozens of small components is necessarily ideal though, but it would be easy to do, or merge all the inputs into a component on that parent

sharp warren
cursive gorge
#

And also if we want to send these inputs directly their format might need to be changed or verified anyway. Sending a Vec2 for input allows for cheating if not normalized everywhere

sharp warren
cursive gorge
#

Haven't looked at BEI in detail yet, but I'll see if I can make something universal when I get to porting my game over

thick kiln
#

That might work; we would still have a release with the networking enabled for the current design, right?

inner sky
#

Seems like a good use-case for components from my limited perspective. Might even open up some design venues you didn’t even think of πŸ™‚

#

As you could have eg observers on actions. Not sure what that would do, but the possibility is there

sharp warren
#

Will try to present the design document today πŸ™‚

thick kiln
#

It would be useful for me to be able to draft a release with working BEI integration

#

I won't have time to work on this too much in the coming weeks so I'd like to release a version with what we have

sharp warren
#

Okay, I'll draft a new release this evening after work

outer wyvern
sharp warren
#

Yes, yes, I meant a custom relation type πŸ™‚
Working on a detailed design right now

sharp warren
sharp warren
# sharp warren Described my vision in this issue: https://github.com/projectharmonia/bevy_enhan...

Modifiers and conditions in their current form don’t work nicely with this approach. Two problems:

  • To evaluate them, I need access to the state of other actions. But I can’t mutate an action from a query and at the same time access action states from it.
  • To attach multiple conditions/modifiers, I have to pass them in a component-like tuple to a single condition/modifier component. This won't work with scenes.

Not sure how else they could be expressed πŸ€” Currently a major blocker for this rework.

sharp warren
#

After writing this, I somehow came to an idea: turn them into components.

So conditions would become regular components on action and binding entities.
But the order of modifiers is important. So I need to store them on additional entities related to actions and bindings with its own relation type.
This solves the scene problem.
And in order to evaluate them, we could use observers with traversal feature.

limpid lake
#

Sounds amazing!

sharp warren
compact island
#

It seems to me I've found another issue, now with the Pulse condition in version 0.13. It appears Fired is triggered on first Started only. I believe I found a fix.
Also, something fishy in HoldAndRelease: it fires only if released in exact time πŸ˜† so, if interval is 1 second you(player) should release in exactly 1 second passed not a nanosecond later )
I'll open issues and submit a corresponding PRs

sharp warren
outer wyvern
sharp warren
sharp warren
compact island
sharp warren
sharp warren
sharp warren
#

The release is up @thick kiln

I won't do the announcement this time since it's just a few changes.

thick kiln
#

Perfect thanks

static hearth
#

I am currently making a space game where players would typically use a dual Joystick setup.
Would it be possible to bind actions to specific Gamepads and use buttons on a Gamepad as a modifier for an action bound to another Gamepad?.

This is a binding that I currently use in Elite Dangerous:
The main Stick (Stick 2) has a dpad that normally is for target selection. But when a button on Stick 1 (the Throttle) is pressed, that dpad is used for ui navigation.

I took a look at the code and if I understand it correctly it is currently only possible to restrict an entire Context to a Gamepad and key modifiers only work with mouse and keyboard inputs.

heady mauve
static hearth
#

so something like this?

    actions.bind::<Navigate>().to(Cardinal::dpad_button()).with_conditions(Chord::<Modifier1>::default());
heady mauve
#

Yes.

#

If you are doing UI, you likely will want to use Started instead of Fired for the Trigger events so that it only activates on first press.

#

For order of actions I would also make sure your modifier actions are first, then the Chords, then any remaining actions. That way the modifiers can activate, which allows the Chords to consume the inputs before unmodified actions.

static hearth
#

How would I allow the player to make custom Modifiers, without having to create countless ModifierX actions?

heady mauve
#

I don't know if there is a good way to do that currently.

#

@sharp warren is there currently a way to define InputAction programmatically instead of declaring them in advance?

sharp warren
#

There is no way, but you can create your own modifier that internally holds programmable modifiers.

static hearth
#

I’m curious how that would work in practice. In an InputCondition (if that’s what you meant), I know in evaluate I can access registered actions via the TypeIdMap like Chord doesβ€”but as far as I know, creating dynamic actions at runtime isn’t supported.

sharp warren
#

(fixed the link)

#

What is your use case?

static hearth
#

A space game with completely customizable inputs.
For example, a button on one Gamepad acts as a modifier for a dpad on another Gamepad that is normally used for target selection but is then used for UI Navigation

sharp warren
static hearth
#

Yes that was maybe a bad example

heady mauve
#

How about this for an example. In Star Citizen there are over 100 keybinds for different functions (changing power, shield levels, targeting, etc). For a person with dual joysticks, having modifier buttons on the joysticks allow the player to have multiple actions they can perform per button, but you as a developer won't know the number of buttons and joysticks a player might have so defining modifier groups in advance doesn't necessarily work.

#

One player might want to have targetting without modifier but allow power changes with modifier. Another player might have more buttons available so neither needs modifiers.

static hearth
#

Yeah, that’s pretty much what I’m after.

heady mauve
#

Yeah, I currently use an external solution to allow joystick modifiers, but having it within the game would be great.

sharp warren
#

And they usually defined ahead of time anyway. A modifier for inventory actions, building, etc.

#

I don't know any game where you can bind arbitrary combinations of gamepad keys

heady mauve
#

Have you played any flight sim? It isn't just game pads, it is other input types like joysticks, yokes, control panels. If a switch on a control panel is flipped, many players want it to adjust what the buttons on their yoke or joystick does.

sharp warren
#

Never played πŸ€”
Will take a look into it after the refactor to components. This shold open many possibilities.

heady mauve
#

Yeah, with the upcoming refactor I figured things might change in that regard, but I couldn't recall if there was a way currently besides defining them in advance.

sharp warren
#

I don't think we need to define actions in advance.
We just need to allow arbitrary key combinations.

#

Gamepads, keyboard, any array of inputs.

sharp warren
sharp warren
#

Now I'll experiment with component-based approach.
Not entirely sure if it's possible to express evaluation through observers, but worth trying πŸ™‚

oak monolith
#

Hey folks! I tried this crate for the first time yesterday and it's pretty useful, thanks a lot for the effort that was put into it.

I have a quick question, and if the answer to it is no I might ask for guidance on how to properly design a solution for my problem. πŸ˜…

I'll start with the short question: is it possible to programmatically trigger the release of an action (that has the Hold condition for what it's worth)?

sharp warren
oak monolith
#

I'll try that out, thanks!

mystic torrent
#

I presume it is not possible to specify the SystemSet in which the input should be evaluated? I know the schedule can be specified.

I would expect/like/need something like: #[input_context(priority = 1, schedule = FixedPreUpdate, set = PreUpdateGame::Input)]

If not possible the only alternative for me, to have full control over when the input is evaluated during the game loop, define my own ScheduleLabel instead of just a SystemSet?

mystic torrent
#

ah that would work indeed, thanks!

sharp warren
heady mauve
sharp warren
sharp warren
#

This shapes nicely, but the type-erasure approach I use in Replicon doesn't fit nicely here because I don't know ahead of time which entities contain which modifiers/conditions. Checking this every time is expensive. I can create my custom QueryParam to cache this info, but I basically re-implementing https://github.com/joseph-gio/bevy-trait-query πŸ€”

GitHub

adds trait queries to the bevy game engine. Contribute to joseph-gio/bevy-trait-query development by creating an account on GitHub.

#

@outer wyvern what Bevy plans for the trait queries?

outer wyvern
#

But we'll do our very best not to break them

sharp warren
outer wyvern
#

Eh, I like them. Others don't, and worry about both performance and complexity

sharp warren
# outer wyvern Eh, I like them. Others don't, and worry about both performance and complexity

Got it!
Funny, but I actually need trait queries to reduce the complexity and increase the performance πŸ˜… Because other options are even slower:

  • Check each entity for all possible ComponentIds (i.e. all modifiers and conditions), then cast it into dyn Trait using custom registry.
  • Create an observer for each modifier/conditions and inside its body get it from entity if it contains it.
    The mentioned bevy-trait-query would work faster since it caches what each entity contains. But pulling it is not an option since Bevy doesn't plan to upstream it.
    Will keep thinking...
outer wyvern
#

Well, major first party consumers might help convince @desert summit and <@&1064697043869777990> πŸ˜‰

#

But yeah, don't pull it in unless they're on board

sharp warren
#

Makes sense. Let me expand on it properly then.

With BEI, you define input contexts. Each context has associated actions, and each action has associated bindings. Both actions and bindings can optionally have modifiers and conditions. Actions can also optionally have mocks.

Right now, the user inserts a single Actions component, and all the mentioned things are managed internally.
But it would be natural to use ECS for this:

  • Contexts would be entities with user-defined context components. Each entity could have multiple different contexts.
  • Actions would also be entities with a custom relation to contexts.
  • Bindings would be entities with a custom relation to actions.
  • Input modifiers would be entities with a custom relation to actions and inputs. They're not components, since their evaluation order is important.
  • Input conditions would be components for bindings and actions.

Right now, we even use a bundle-like syntax to define everything, which would naturally transform into actual bundles if we embrace ECS.

#

But how do we evaluate such a tree? Each entity could have multiple contexts, and contexts need to be evaluated in their configured priority order. So I need a way to type-erase Actions<C: InputContext>. I can use the same technique I use in bevy_replicon: store a pointer to a generic function, then get Ptr and pass it into the function to perform the evaluation or cast it into a trait.

But the problem is that I don’t know ahead of time which entity contains which components. For example, checking every action entity for the existence of each possible condition component would be expensive.

I could cache this information using archetypes, but then I’m basically reimplementing trait queries.

Another option is to use observers. So I call an observer for each binding, it bubbles through each modifier, then I create another observer which targets the action. Then the action accumulates all observers through all inputs and calls yet another observer to evaluate each modifier...
In other words β€” very expensive and slow.

sharp warren
#

So the use case for trait queries is to iterate over a relationship tree and execute custom logic based on trait implementations.

#

I think I'll use a single modifier and condition component to store boxed traits, just like before. We should still get other benefits from ECS :)

glad bough
#

So I updated from 0.12 to 0.14, and input seems to be broken. Trying to do the #[input_action(output = Vec2)] panics by saying something likeaction output should be stored as a Vec2

#

And when trying to do

#[derive(InputAction, Debug)]
#[input_action(output = f32)]
pub struct Zoom;

actions
  .bind::<Zoom>()
  .to(Input::mouse_wheel())
  .with_modofiers(Scale::splat(0.1));

Does not fire at all

#

No wait

#

Might be the compiler

#

I'm using the nightly one

sharp warren
sharp warren
glad bough
#

Horizontal!?

glad bough
sharp warren
glad bough
#

Yep,it was the compiler

sharp warren
glad bough
#

Yeah, sorry to bother

alpine socket
#

@sharp warren
In the lightyear FPS example there's a call to action_state.set_axis_pair() from leafwing. We're doing something similar in our project, just with a 3rd person camera instead. Is there something similar in BEI? i.e. some way to manually set the action value?

valid harbor
#

How can I have a button that triggers only once?
actions.state::<ShortCut1>()? == ActionState::Fired
This is actually spamming the trigger I just want something that fires once and wait for the key to be up
like with LIM and the JustPressed

heady mauve
valid harbor
#

There's no started

heady mauve
#

Sorry, was thinking of ActionEvent.

valid harbor
#

I haven't seen any example including Actions events

#

how does it work?

#

That's very likely what I need

heady mauve
#

actions.events::<ShortCut1>()? == ActionEvents::Started

valid harbor
#

like actions.events::<ShortCut1>()? == ActionEvents::STARTED{ doesn't do anything

#

if I do

if actions.events::<ShortCut1>()? == ActionEvents::STARTED{
    println!("started")
}
if actions.state::<ShortCut1>()? == ActionState::Fired{
    println!("fired")
}

fired is fired, but started never

heady mauve
#

ActionEvents::Started exists, ActionEvents::STARTED shouldn't.

valid harbor
heady mauve
#

Why are you capitalizing one but not the other?

heady mauve
#

Guess I've never used the capitalized version.

valid harbor
#

I mean where the non-capitalized version

#

how do I use it?

heady mauve
valid harbor
#

I don't have

#

It's not accessible?

heady mauve
#

Are you using main or a tagged release?

valid harbor
#

bevy_enhanced_input = "0.14.1"

#

just latest version

heady mauve
#

Is there a reason you went with polling instead of observers?

static hearth
# valid harbor

It is a bitflag

if actions.events::<ShortCut1>()?.contains(ActionEvents::STARTED){
    println!("started")
}

This should work

valid harbor
#

first time I ever encounter bitflags in rust

daring latch
#

DeadZone::default(), // Normalizes movement. huh?

#

What about single-axis non-buttons like triggers and scroll wheel?

heady mauve
#

A mouse scroll wheel has two axes due to some mice having side movement capability. Scroll wheels on other input devices like joysticks often are converted to button presses so do you have a device that doesn't? The one I have outputs button presses.

As for mapping a single axis, can you explain further why you would need a preset to do that since you can just take the input directly?

daring latch
#

For the second yeah I can see that point

heady mauve
#

Ok, so you want some preset to extract a single axis from multiple?

daring latch
#

I could maybe see some edge cases of two axes mapped to one axis (opposing each other), but meh

sharp warren
daring latch
#

I'm coming from LWIM and trying to see from docs alone how I would do the things I do in LWIM, in this

#

in LWIM it's just a single-axis with mwheelup and mwheeldown, I believe

heady mauve
#

I do think that getting mouse input is possibly confusing because the Y axis is the one usually used, so it might not be bad to have a way to extract either Y or maybe Z from a 2D or 3D input.

sharp warren
daring latch
#

btw why is DeadZone the modifier to normalize input?

sharp warren
#

That's what dead zones do

daring latch
#

maybe we're thinking of a different normalize? surely it doesn't make it a unit vector?

sharp warren
#

You get a unit vector

#

From the docs:
Remaps input values within the range Self::lower_threshold to Self::upper_threshold onto the range 0 to 1

heady mauve
sharp warren
#

I.e. just apply the swizzle modifier to get the axes you want

daring latch
#

and that is normalisation I guess but I found it confusing cause that's kind of secondary to the value "trimming"

heady mauve
#

Unless you have some graphics knowledge, I'm not sure how someone might find Swizzle.

daring latch
#

maybe just have it in the examples, if it otherwise looks nice

heady mauve
#

Pretty sure it is in the examples and the doc index page, but I think it might be overlooked at first because it isn't necessarily in common parlance.

daring latch
#

I think scroll wheel single-axis binding will be very common

sharp warren
#

But yea, I think I should remove the comment, it's confusing.

daring latch
#

I still don't really understand it still, what happens to an input of (0, 0.6) with a deadzone of 0.2?

sharp warren
daring latch
#

Where's the 0.5 from?

sharp warren
#

It's the length of your input

#

Updated the link to a version with the images πŸ™‚

#

Quite busy with the component-based refactoring at the moment. I’ll update the docs once that’s finished.
But if you're interested in helping with documentation, a PR would be very welcome!

daring latch
#

I wouldn't describe using normalising somewhere inside the math as normalising, especially since it seems to me like it could have been done without that step

#

in some mathematically equivalent way of phrasing it

heady mauve
#

The radial deadzone type performs normalization but the axial deadzone does not.

#

The radial is default though, so by default the deadzone normalizes.

daring latch
#

I feel like we just don't agree on what normalising is? if the final result is not a vector of length 1 I would not describe it as normalising, without some qualifiers, in this context

sharp warren
#

But it uses the normalization, so you don't have to normalize it again, you just need to multiple it by some value to achieve the desired speed.

thin zealot
#

Hi! Does BEI handle gestures and swipes for mobile devices? I didn't see anything specific in the documentation that covered it. Is just lumped under gamepad?

outer wyvern
#

(I do think we should be adding it in bevy_input though!)

thin zealot
inner sky
#

@sharp warren I quickly wanted to confirm the thing with mouse acceleration and whipped up an app that moves a sprite when the mouse motion action from BEI is Fired

#

This is with mouse acceleration turned off in my OS

#

I'm a bit confused why the ducky is moving faster than the mouse hmm

#

There's no multiplier in place

#

And the movement update happens in Update

#

so I would have expected the two to be in sync

#

Oh, I think I have it

#

looks like winit is receiving whatever value my cursor would have if this bar was in the middle

#

When I move it to the middle...

#

Now let's enable acceleration

#

Good to have this confirmed haha

sharp warren
#

Thanks for the investigation πŸ™‚

sharp warren
sharp warren
#

Ported all tests and now working on examples, but I think I went too far with "entitification".
Actions as entities are awesome!
But bindings as entities reduce ergonomics:

  • It makes presets less flexible because they have to be SpawnableList. But this means we can't support things like with_modifiers_each.
  • Since Binding is a component, I have to define them like this: Binding::from(KeyCode::KeyA) instead of just passing KeyCode::KeyA to the binding function. Or use a macro, which is also not ideal.
  • During evaluation, I now do entity lookups instead of simply iterating over a Vec.
  • Detecting changes to the bindings is trickier and slower. I need this to sort actions based on the number of modifiers.

I'll try a healthy middle ground: actions will remain entities, but I'll turn bindings into components on action entities. This should be the best of both worlds.

thick kiln
#

I hope this will still work with networking.. will we need to entity mapping between the action entities?

sharp warren
#

It will!
We just change how the actions are accessed.
Instead of querying for a context and extracting actions from it, you will be able to query for the actions directly. And instead of using a custom ID, you use Entity.

sharp warren
quasi epoch
#

how should i implement input buffers?

thick kiln
#

What is the input buffer for?

inner sky
thick kiln
#

I think you can simply store the inputs of every tick in a VecDeque

quasi epoch
inner sky
#

Just be advised that there is no proper way to assign those inputs to the correct fixed timestep since winit does not expose timing information on input. In other words, you just kinda have to decide on your own "when" you want to handle them

#

The difference is that you wouldn't only accumulate a single input in there, but have a VecDeque of different inputs that were not yet handled

#

How exactly you do that depends on the game and its invariants in practice

#

But that's a rough outline

#

Except if @sharp warren swoops in and tells me that BEI already has this builtin somehow hehe

thick kiln
inner sky
quasi epoch
inner sky
# quasi epoch oh?

I'm just saying there's a nonzero chance that there's an API that I might not know about πŸ˜‰

sharp warren
#

You are correct, we don't provide a built-in functionality for it πŸ™‚

I think it's also possible to create a an input buffer as a modifier if you just want to delay the input.

quasi epoch
sharp warren
#

You can store buffer inputs inside a modifier and return any other value

quasi epoch
#

so if you have the input stored, how do you get it?

sharp warren
#

Modifiers receive the current input and return any value instead of it. So they could modify the value or return anything else.

quasi epoch
#

how would i trigger the input though? maybe i should write events manually outside of the plugin?

#

what i'm trying to do is receive input x, but then delay the event until the player is touching the ground for example

#

i'm not sure how modifying the value would change anything

sharp warren
#

Ah, this should be outside of the plugin. Modifiers and inptus only for input-related stuff.

quasi epoch
#

ah ok

#

i think what i'll do is create an observer that receives all inputs, and puts them into a vec resource, then have a system that ticks down timers for each input, and triggers an event if the run conditions for the input is satisfied

sharp warren
#

To do this you need to query for Actions.
Will be way simpler in the upcoming version where ActionValue and ActionEvents are components.

quasi epoch
#

@sharp warren what type do i use to store the actions?

quasi epoch
#

would i have to have a different resource for each action then? it would be nice to have a single resource as a vec but if they are all stuck to their own type i guess i'll have to make separate resources

sharp warren
#

In the upcoming rework you should be able to just store entities.

#

If you building something that needs access to internals, I'd suggest to wait a few days. We have a big rework that's almost done. I plan to publish it tomorrow.

quasi epoch
#

oh cool

#

yeah, the input buffer isn't really that important

#

btw input buffers are pretty widely used, it would be cool if you added a built in feature for it at some point

sharp warren
#

Will ping you when I open the PR

#

So you can try the new API early if you want

quasi epoch
#

oh cool, thanks

sharp warren
# sharp warren This shapes nicely, but the type-erasure approach I use in Replicon doesn't fit ...

Found a fast and relatively simple way to turn conditions and modifiers into individual components.
When BSN comes, contexts could be defined as hot-reloadable scenes. And this also improves ergonomics in general πŸ™‚

Now the only thing I’m not satisfied with is presets. I express them as SpawnableLists, but you can’t easily attach more conditions to each binding. Need to figure this out. I think it could be expressed with BSN templates, but we need a solution for this now πŸ€”

sharp warren
#

It's too early too look into, opened mostly for my own convenience. Will try to finish tomorrow.

#

I hope we get reflect autoregistration in 0.17 πŸ˜…

inner sky
#

Lemme ping #engine-dev message

#

Update: added πŸŽ‰

crude summit
#

How to handle an input that is not tied to any entity?
For example, a key to save the scene

#

Should I use a stub entity?

sharp warren
crude summit
#

Ok, thanks

sharp warren
quasi epoch
#

@sharp warren there's some softening thing (idk what to call it) where it takes time for wasd to input to reach exactly 1, is there a way to change the acceleration of that to make input feel more snappy

sharp warren
quasi epoch
#

maybe?

#

in the wasd preset

sharp warren
#

But if you're talking about the examples, they also include the SmoothNudge modifier, which smooths the input using half decay.

quasi epoch
#

ohh yeah i just checked thats it

#

i should've actually read my own code before asking about it lol

sharp warren
#

It's fine, don't worry πŸ™‚

quasi epoch
#

@sharp warren is there an option for constant acceleration instead of exponential?

sharp warren
quasi epoch
#

ok! sounds cool

#

i'm at the stage where i don't do many prs so each one is fun lol

sharp warren
#

@quasi epoch I promised to ping you ^

#

@thick kiln the crate logic is unchanged, you will just need to access things differently. Even types are mostly the same, except timings now represented by separate ActionTiming component.

#

@outer wyvern please don't hate me πŸ˜…

#

But this approach is so much nicer!

inner sky
#

Though the syntax of Foo, actions!(Foo[ ... ]) looks really weird

#

I see what you're going for, but since [ and ] are already "reserved" in my mind for array access, this makes the macro instantly feel unrusty, and by extent slightly magic

#

Wish we had Foo.actions![ ... ] in Rust :/

sharp warren
inner sky
#

Well, in that case, your API choice makes a ton of sense

sharp warren
#

I hope the spawning API improves on the Bevy side. Especially with BSN πŸ™‚

outer wyvern
quasi epoch
quasi epoch
#

debugging is hard when it doesn't let you use dbg or println

inner sky
quasi epoch
#

ok, the code is working, i don't really know how to do tests though

#

if thats not necessary i'll open the pr rn

#

@sharp warren is that ok?

#

i'll just open it, and can change stuff later

cursive gorge
quasi epoch
#

opened it

sharp warren
sharp warren