#bevy_enhanced_input
1 messages Β· Page 2 of 1
Could you elaborate?
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.
Ah, yes
Then the fact that Combo doesn't work on all inputs by default is a feature, not a bug.
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
Yes. We don't have a built-in condition for it, but you could write your own.
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()?
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
Not sure if I get the question π What are you trying to do?
It's a preset to conveniently assign buttons to a Vec2 output. If you need something custom, you can do it manually. I tried to explain it in this section of the quick start guide.
Input manager inspired by Unreal Engine Enhanced Input for Bevy.
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 π
Not exactly... Let's break it down for the Y axis as an example:
- You have a button that outputs a
bool. - We apply
SwizzleAxis::YXZ, which turns it into aVec2with:- The X axis equal to zero
- The Y axis equal to the output value
(Swizzle does the minimal possible dimension expansion.)
Negatealso 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.
Yeah, in my opinion it works quite intuitively - you shouldn't need to dig into the internals π But since you asked for an explanation ^
If you need only one axis (if I understand you correctly), you can try Bidirectional preset instead.
A preset to map buttons as 1-dimensional input.
I actually wanted to go up another dimension so an output of Vec3
Ah, got it! Yeah, then you need a custom preset or set the modifiers manually π
Do you think it'd be useful to upstream it?
I could throw in a PR
It depends on how the preset looks like. If it's general purpose - then yeah.
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
What are you using it for? Helicopter controls? π
It actually fits, yes
Fly camera rn
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
Might want to use rawinput or OS equivalent for that
Or just start ignoring cursor events right before centering cursor
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
In case you're targeting Wasm: https://github.com/bevyengine/bevy/issues/18855
MacOS for the issue above
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?
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.
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! :)
Just place them next to each other. If they are in a single module, it's obvious that they are related.
Yes, you can't use enums anymore. It's because enums won't work nicely with observers.
Alrighty, fair enough! :) Thanks very much
You are welcome :)
You could still preface them with the old enum name and what was their enum variant, they just wouldn't be enums but individual structs.
That hurts my Rusty mind too much π I'll group them in some other way instead hehe
Same. Trigger<WorldSpeedActionFast> looks horrible π
Just pick a descriptive name, like FastSpeed, etc or something.
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
It's also a good approach
I suppose just having an inner mod would be fine for grouping them as well
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
Gotcha, good to know! I'll consider that as an option as well
So if I have a module with some actions, I usually define actions and context for them in the same module
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
I have no idea lol
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
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
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.
You can use old-style schecking. Just obtain Actions components for your context and check the state in a system.
Durp! I was literally just reading that part in the docs π
My apologies
No problem!
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)
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
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
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 π
Yes, it's because wheels may have a horizontal scroll. We probably should mention it in the Input::MouseWheel documentation
Yup that's why I figured to even try it! But yeah, would be nice to have a mention in the docs! :)
Interesting, we didn't change much in 0.11, it's just updates to the latest Bevy and adds schedule configuratgion support.
But glad that there is no issue in the latest version.
I remembered!
I actually discovered a bug in a proc macro related to priority. I fixed it, but forgot to backport to 0.10 π’
As a workaround you can implement the InputContext trait manually.
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 π
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...
You're missing app.add_input_context::<OnFoot>();
ah thanks
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.
I wonder what the best way to catch this sort of mistake automatically is π€
Maybe linting?
Or inventory-style automatic registration
I think it won't be compatible with WASM π€
This is why reflection is not automatic.
I'd lean towards linting unless bevy decides to go with something more automated for all the similar cases we have like Events, Assets, all the other things that require explicit registration to work.
Yeah, we would need to figure out an answer for reflection first
Indeed π
It would be nice to not have to worry about those registration steps any more though...
Save me personally a lot of recompiles π
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
How would you even call it? π€
name wise or?
tbh this is what I expected the default to be for key presses, or Press
esaeleR
Not sure if I get you.
Press triggers when you keep pressing the button.
Do you want to trigger only once? We have JustPress
Basically I want ActionState::Fired to activate once on a press and then ActionState::Ongoing if it is held in subsequent frames
Ah, got it. But I wouldn't expect Press to work like this π€
Have you tried Pulse?
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
Sounds like Sustain which might be worthwhile, although that meaning is better known in audio circles.
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"
If you look at ActionEvents in the documents it provides a table of what the various state transitions output for events.
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.
You can get the values from the Actions component just like with LWIM.
Ongoing refers to something that has started meeting the condition but hasn't fully satisfied it yet.
Tip: you can watch any tutorial for UE enhanced input, it will be applicable with this plugin, the naming is currently the same.
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).
I found it weird to have an action that outputs Vec3 in which only X and Z actually used. I prefer to apply this conversion when I apply the input to components in 3D.
i fixed that by using y-forward z-up coordinate system π
I also found it a bit wonky FWIW
It seemed like the crate "forgot" the 3D use-case from an API standpoint and that I had to patch it in myself.
I realize that is not the case, but thatβs how it feels to me
I don't know, I personally expect things like WASD output X and Y π€ Returning a Vec3 from action where Y is always zero just to apply X to X and Z to Z is a bit odd as for my taste...
However, I'm opend to add a modifier to map 2D movement to Bevy's 3D space! I think there is even a built-in modifier for it in Unreal.
Yes, it's called ToWorldSpace
Yeah, that would be great π I'm not really asking for a Vec3 output per se, just a blessed, builtin, obvious way to get there somehow π
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 :)
yeah, that sounds perfect!
I would appretiate a PR, currently busy with different stuff...
Or I will take a look into it later myself.
I'm about to go to bed, otherwise I would have PRd it π
Have a good night!
But if you do it tomorrow, it would be also great π
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 π
If Bevy used Blender coord system this would not be issue at all.
Yeah, not a big fan of the Bevy choice...
2d side scrolling games and game menus in some types of games probably will not encounter this issue.
only 3d and topdown 2d.
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.
If Bevy had a character controller, the direction provided by it would be a Vec2 (or a Dir2). You modify it and the translation would be updated automatically.
Avian kcc prototyping https://discord.com/channels/1367974345070088405/1367974345514680453 . Currently uses this crate as input manager.
https://github.com/Ploruto/kcc_prototyping/tree/main .
I'll hold off with PRing it, then
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

fwiw I would expect a vec3 from an input to be a 3d input device, like a space mouse
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.
[yeah, but you can still use it yourself](#1297361733886677036 message)
I think some things like atmosphere assume Y=up, FYI
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.
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.
We definitely need the auto-registration π
If anyone figure this out without breaking WASM - let me know!
Thereβs a PR on Bevy in the works FYI
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.
It's serializable: https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/input/enum.Input.html
Wraps input from any source.
It sounds like you trying to serialize events.
But you probably want to serialize the state and the value instead.
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
You need to reorder your actions
Thank you, that worked!
I hadn't considered the fact that the order may be important π
We considering automating it π
@sharp warren found the PR I mentioned before: https://github.com/bevyengine/bevy/pull/15030
Thanks you! I subscribed to notifications. Sad that it breaks no_std support.
Maybe we will also have this as a feature like in Bevy.
But I want to wait for the decision to be made on the Bevy side first.
Yeah I'd do the same π
How do I access this input? It is an enum, and not a list of all the inputs in a frame.
The list of all the inputs - from my knowledge - is the action_map
It's for binding.
If you need all inputs in the frame, you need to access Actions component.
But I didn't add an iterator over the list π€
If you need it - feel free to PR this function.
You could also add an input modifier like so: .with_modifiers((SwizzleAxis::XZY, Negate::z()))
should translate a Vec2 wasd into worldspace (well, character object space really)
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?
You can manually trigger event.
But we do need a better input mocking solution π€
Ah I see, thanks. So individually triggering Fired<Aim> events and so on as needed for now
Yep!
I planning to add a nicer mocking system by directly setting data on Actions component.
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?
Yes
Thank you.
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?
Looks correct to me.
But I can't reproduce it on my side. X and Z aren't zero for me.
Are you using version 0.11.0?
On the latest master, but I didn't do much changes since then
Thank you for your response. Even when I used the latest master, the issue still persisted. Iβve worked around it by using a different method instead of SwizzleAxis::XZY.
@unkempt ferry Could you provide a minimal reproducible example?
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.
Thanks for your hard work fixing the bug. My actual use case is in 3D β the code I shared with you was adapted from a basic example I used to test how to use bevy_enhanced_input.
Ah, got it π
@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?
Could you elaborate on this?
Sure, I'll write up an example later π
I assume you know how fixed updates work?
Yes, and the plugin is compatible with the Fixed* schedules.
By default all contexts evaluate in PreUpdate, but you can move evaluation of specific contexts to something like FixedPreUpdate and it will trigger events as expected.
Makes sense, but my issue is regarding inputs that were fired in-between fixed updates.
E.g. if we have three PreUpdate frames before a fixed update, and the player quickly presses W on one of them, we get:
- var1: nothing
- var2: W
- var3: nothing
- fixed: nothing
While what I want is
- var1: nothing
- var2: W
- var3: nothing
- fixed: W
Because W was the most recent input that was not handled yet
Or are you saying that moving triggers into fixed updates will already do that?
I see!
No, we don't handle it right now. But I think it might worth to handle stuff like this π€
I'll open an issue real quick π
Thanks! I Currently busy with replicion, but if you want to tackle soon, all the input reading functionality located in this module:
https://github.com/projectharmonia/bevy_enhanced_input/blob/master/src/input_reader.rs
I'm honestly not sure how a good API for it would look like. "Report the last input that was pressed" is simple, but e.g. for holding jump, you'll want to report whether it was kept pressed during all variable steps before the fixed update
Makes sense, I will think about it π
I would not be surprised if the answer is "that's the character controller's job". Curious how other engines deal with this
π Spent a bit of time integrating BEI into the default 2d template, so if anyone is interested you can see it here!
https://github.com/mr-joshcrane/bevy-gamejam-6/commit/51a2d8039ded9bb9f7fa4089e97d20880cd8c00a
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
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
Just a quick look at both, it appears that @compact flume's team is resetting controller.intent in their apply_movement function (which is separated from getting the input). Your team take the intent and uses it directly but you don't zero it so need the separate Completed trigger to then cancel the movement.
Ah, so it βconsumesβ the intent each frame - makes total sense thanks for the explanation!
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?
Only manually, we provide only timing information.
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
What do you mean?
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
Diagonal is allowed, x and y movement are independent
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,
}
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.
It just seems odd to convert bool to float to bool to float.
Your movement isn't in booleans, it's in float.
That is bool (key state), float (action value), bool (fixed speed), float (player position)
Why fixed speed is bool?
It's comparing the action output to a threshold value I guess
Not sure if I get you π€
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
I see it like this:
- Game device: bool or float.
- Action value: Vec2
- Action speed: Vec2, you multiple value axes by it.
- Position: Vec3, you add values from axes.
For this type of game, we don't want to allow the player to move slowly
You can clamp your values with Clamp modifier
I'd suggest to describe what do you want first and we could suggest how could you achieve it with BEI
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.
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 π€
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
But pacman doesn't have a diagonal movement π€
It's an outlier in that sense
I played galaxian on NES, but I do you move faster with diagonals? π€
Well, I knew a guy who was a master at Descent and he always flew diagonally to get the extra speed
The diagonal movement doesn't matter to the approach - I was just curious.
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.
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.
I will most likely have different speed factors in x and y, so it won't be splat
actually it's weirder than that
Makes sense, you can use new then. Or write a custom modifier if it's dynamic.
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
Not to be that guy, I'm genuinely curious. But what are the advantages of this crate over leafwing input manager?
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.
Ultimately, I think that observers are a better paradigm for responding to input
And this is a better foundation for building out more complex forms of input handling that map to things like "hold for 3 seconds"
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
Just tried with my Xbox controller, can't reproduce π€
Try to enable logging via RUST_LOG=bevy_enhanced_input=debug cargo run .. Maybe the values are too low to notice the movement or something like that?
finally tried with an xbox controller and it seems to be working just fine now... inputs from my pro controller arent getting picked up at all though even with debug logs enabled
Try to reproduce with raw input. I suspect that the Bevy input just can't see it.
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
You probably need to report it upstream. Maybe even not to Bevy, just directly to gilrs.
Just to gilrs
And there's a high chance it's actually a driver bug T_T
Am I right in understand that the pull-style API doesn't provide information about how long a key was pressed?
So I need to use a bunch of triggers to accumulate state for a fixed tick?
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)
Push-style APIs are triggers. With the Completed event, you should be able to get the firing time.
is this crate better than leafwing-input-manager ?
It's a new iteration of it.
I already have working code using leafwing-input-manager, should I upgrade to this crate?
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.
alright thanks for your feedback
Sorry, I meant pull-style, aka Actions
You pull them every frame, I'd suggest to track the last time inside such function π€
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
But the action itself runs in PreUpdate?
Yes, EnhancedInputSystem runs in PreUpdate, so it has higher resolution
Oh, you meant where InputContext is evaluated, I didn't specified that, so it runs also in PreUpdate
If you work with FixedUpdate, I'd expect you to set the context schedule to FixedPreUpdate. It should work as you expect.
But I will get less precise resolution π€
Actually, you probably want it to be fixed update resolution anyway, so it's fine
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.
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.
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!
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.
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.
Maybe just make 2 abstract schedules, the first just one stores the input events, and the second one erases them?
And then provide an API to iterate through those events.
Then we can just .filter().map().sum() them
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.
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.
Yeah, this might solve the missing inputs.
But this won't help with the precision issue you mentioned π€
We might need to re-think how the timing information is accessible.
Yeah, it's my first time even looking at the library, so don't take anything I say as good info unless it makes sense to you! :p I'm sure you know better than I do. I'll play around with it some more before I'd feel comfortable trying to actually suggest anything :p
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());
[...]
}
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;
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?
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.
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.
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.
actually... now that I think about it... it probably needs a threshold for gamepad values.
it's probably not a time value
Yes, it is an activation threshold.
probably after deadzone is applied to gamepads, so f32::EPSILON is probably fine... I'm not really looking at gamepad support anyways.
This will at least get me going! thanks for the help!
Fired triggers when the action is active. You may want Started or Completed.
wait... I don't have to spam .with_condition(Press) all over? π
No π
I probably need to add references to those 2 events from Fired and clarify.
@fickle latch added a preset for roguelikes: https://github.com/projectharmonia/bevy_enhanced_input/pull/112
You can copy the logic from the PR (the changed swizzle modifier and the preset) into a module and start using it.
Hope this will make it more clear: https://github.com/projectharmonia/bevy_enhanced_input/pull/113
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
wonder if a "JustBound" thing would work
If you switching between contexts, you need to set the require_reset property
See this example: https://github.com/projectharmonia/bevy_enhanced_input/blob/ff554e69cddb1c34bfca1c89f1b8288b42d3be6b/examples/context_switch.rs#L98
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.
That's exactly what I've already done.
I just wanted to get rid of that boilerplate
Got, will work on the implementation and draft a new release.
And you may have misunderstood me.
What I meant was that while evaluating the input context, you can store the input states in a ring buffer inside the Actions component.
So I could iterate over these states instead of writing dozens of triggers
I can also store a ring buffer, but this might be too game specific and reduce the ergonomics. Usually you don't want to get all hold time, it's usually the last hold time.
And do you need it for every action? It could be a single observer with a generic parameter.
The size of the ring buffer can be configurable. And with a size of 1 it will be similar to the current solution
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/PreUpdatewill 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
Sure, I mean that it would make the API a bit weird π€ I would see this kind of functionality implemented for game. It's not a ton of boilerplate. Once we adjust the scheduling, of course π
Could you elaborate on this part?
Ah, you rollback some specific schedule π€
But how can I keep the schedule abstraction and avoid missing inputs π€
Resumulation is like runnig FixedUpdate for physics several times for past ticks, and it happens inside single FixedUpdate tick
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
I don't understand how abstract schedules related to missing inputs
Good question. Let me explain.
Right now you can specify in which schedule we evaluate each context. And imagine you set FixedPreUpdate and you get 4 frames before your Fixed schedule runs:
0. Press W
- Release W
- No input
- No Input, Fixed schedule runs, you miss the input.
And another issue with this approach, as you mentioned, is that you lose precision.
A ring buffer would solve this
Then we no longer abstracted over a schedule
why?
We need a special logic to run in PreUpdate to collect the ring buffer of action states
Potential workaround: you can switch to evluating inputs in PreUpdate to fix the precision and never miss inputs.
But then on rollback you will get weird behavior with Completed/Started states and triggers won't fire.
We need just 2 schedules: one to populate the ring buffer, and one to clear
So it's also unacceptable.
Instead of using ring buffer we can also use some user callback to accumulate the inputs
We probably think differently about the ring buffer.
By any chance you refer to buffering inputs? If yes, the plugin logic just won't work this way. I can't update the state based on events. The update logic works in other way: I iterate over bindings and check the state. If I need multiple changes to the state, I need to run this logic multiple times.
This is what powers combining the results from multiple inputs. I.e. Move could be evaluated by reading 4 buttons.
I previosly though we were talking about actions state buffering. This is doable, but the API will be weird...
Like a trigger but generic over different inputs
Could you elaborate on this?
@cursive gorge quick question - you replay inputs during rollback in your game, right?
Iβm wondering how that works with LWIM π€
Yea, I load whichever input was stored for that tick when it is replayed
Ah, right, LWIM don't have triggers.
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)
I just realized that this can probably already be implemented using a custom input modifier
Something like a SmoothNudge, but cleared in FixedPostUpdate
But you probably need a way to clean it somehow π€
@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:
- 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.
- You also need to evaluate during the fixed schedule run. So you need to put the schedule evaluation here as well.
- You don't want to lose the precision and miss inputs. So evaluating in your rollback and fixed schedules is not enough.
- Modifiers might not be flexible enough. We need to provide more information, such as which schedule are you running right now.
- I also use separate structs for the inputs that I read inside the physics code. And I don't want to use any input library here for flexibility.
2/3/4. I would make my own schedule that I would run inside the PreUpdate and resimulation ticks.
- Those structures populated from your library inside FixedPreUpdate
- Makes sense. So your code is abstracted over the input library and you just provide integration layers?
Yes, and the server doesn't know anything about input library
I like this design!
Okay, let's think what is reasonable to provide from BEI side:
- We might want to remove the
Scheduleconfiguration from the context. If you configure it for your schedule, you can miss inputs. We might always want to evaluatePreUpdatebecause of it. - When fixed schedule runs more then 1 time, we additionally evaluate the state in
FixedPreUpdatefor each additional fixed run to properly trigger events and deal withCompletedandFinished. - 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?
Yes, it looks good
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.
Forgot to say you, thank you for BEI and replicon
Small note: not all contexts are tied to FixedUpdate π€
I'll add something like fixed_update = <true/false> in the macro and the trait to split contexts into two sets.
Non-fixed contexts will run only in PreUpdate, while fixed-enabled contexts will also run additional times in FixedPreUpdate and user-defined stages.
We don't want to see UI contexts getting rolled back π
leafwing stores separate values for the update and fixedupdate schedules, and loads the correct one depending on the current schedule: https://github.com/Leafwing-Studios/leafwing-input-manager/pull/522
Context
Fixes #252
(see description of the issue in bevyengine/bevy#6183)
It would also fix cBournhonesque/lightyear#349
(I did a write-up here: https://hackmd.io/_TGuaUTnRBeuisvUMr0QoQ?both)
i.e. ...
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
PreUpdateto avoid missing inputs, and additionally inFixedPreUpdatefor 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.
Sounds good, I think that would work!
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)));
@sharp warren Is there a way to access Components or Resources in an InputCondition? I assume a cooldown can be done just using a ConditionTimer but it might be nice for UI to have something externally controlled so it can be displayed.
Toss is consuming the input before it gets to TossAll. You can try reversing the order you have those actions to see if that takes care of it.
If you currently want to make a cooldown, you might want to try code similar to a comment in the following PR and see if it works for you. It makes a new InputCondition that is based on keeping track of a cooldown:
https://github.com/projectharmonia/bevy_enhanced_input/pull/103
I tried but then just TossAll would work
Can you explain what Toss and TossAll are suppose to do? It is possible you might want to look at the different events being fired and perform what Toss would do if the hold is cancelled early.
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
Basically it throws the equipped item/weapon, and there can be multiples of those so my goal is when holding "Q" it will toss all of them but when tapping it just the last one LIFO
hmmmm so basically listen to all events and implement the logic inside the observer function that will check the conditions?
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)
Ahhh that looks great let me check this quickly and update
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.
Nice!! it works, thanks @heady mauve
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
mouse_wheel() outputs a Vec2 because there can be both vertical and horizontal scrolling.
If you want only vertical scrolling, you can apply a swizzle modifier as use f32.
No, unfortunately. Can't be done due to Rust mutability rules.
Sorry I didnβt reply on the GH issue yet, but this sounds like a good plan π
@sharp warren I wrote up a long discussion w/r/t integrating the core widgets (button, slider and so on) with input mappers, your input would be welcome: https://github.com/bevyengine/bevy/discussions/19585
I'm not sure if UI interraction is a job for an input manager. Actions are gameplay-based things.
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 π€
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.
Well, I would prefer that my slider widget not have to know or care whether it's running on an XBox or a PS. This sounds like a job for an input mapper to me.
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.
It's a job for bevy_input, gamepad buttons already abstracted.
bevy_enhanced_input is not a replacement, it just extends the functionality, similar to LWIM.
Represents gamepad input types that are mapped in the range [0.0, 1.0].
I think UI navigation and focus aren't really the responsibility of input managers π€
We just need a way to prevent the input manager from reading inputs that are currently used by the UI.
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 π€
I really think we should support / promote this
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.
It's important for accessibility
It seemed simple to set up an InputContext that is just associated with UI when I need it.
You think we should use BEI for navigation?
Yeah, that's exactly what I think.
It should be easily hookable, but the navigation logic doesn't need to be implemented through the input manager.
Accessibility is very important, but for navigation it's usually about configurable actuation and similar settings β not rebindable actions π€
Although, it might be good to reuse some of the logic π€
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 π
Yes, but it doesn't need to be complicated
I feel quite strongly that no user inputs should ever be hardcoded in a production game
That's basically what I do. I only have specific Actions associated for some things. Basic UI navigation lives in one place so I can navigate the UI.
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)
@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).
Perfect π
that looks like a very neat API, good job π
Makes total sense to me!
And on BEI side we can provide built-in UI contexts to avoid users manually defining all the bindings.
@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...
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.
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 π€
A fixed run needs a way to "consume" an input, so that the next fixed run cannot consume it again.
For example, when you hold down a key, PreUpdate accumulates some value, and then FixedUpdate subtracts from it.
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.
Ah, got it! But it's much easier to use observers for it. It will trigger exactly once. Yes, it triggers outside of the fixed schedule, but it doesn't matter since you don't run the simulation inside the observer. You usually update some component and then fixed update uses it.
*observer for Completed/Started
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.
Correct, otherwise both timing and Completed/Started indicators might be incorrect.
For use in a fixed schedule, I'd probably always advise using observers to write the necessary data to components (like position, jumping, etc.), and then evaluate these components in the fixed schedule.
If we go this route where I evaluate inputs in PreUpdate and let users abstract it themselves.
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
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.
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
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:
- User press button A.
- User releases button B.
- 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 aJumpaction, 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.
Looking into Godot implementation and I think I have an idea of how to get the this work without the mentioned issues π€
If you can already store it somewhere that works, if you can't it's often just more practical to store it rather than emit changes. In networking especially this stops errors from compounding when an input didn't arrive at the server
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.
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
Yes, it's a totally correct approach.
If you want to just reduce the rate when you hold the button, you can use Pulse condition.
If you want to fire only once for each press, you can observe to Completed or Started events instead of Fired.
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
aha!
Magical it works! Thanks @sharp warren
You are welcome π
Lifehack for BEI patterns: search for any tutorials for enhanced input for Unreal Engine, they all applicable here.
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
If treated like a sum from multiple gamepad axes, isn't it be possible to hold a direction on one gamepad and the opposite direction on the other which would cancel out the input?
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.
in my case Any and sum is exactly what I needed
Can you explain your usecase though? Because if you have two gamepads that provide opposite input they will cancel each other out.
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 )
It's a configurable behavior
Hm... Yes, I think it should be a sum π€
@compact island If you haven't yet, you might want to create an issue and a PR if possible.
ok, I'll open an issue and link a PR to it
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?..
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.
Is there a default binding to move cursor with gamepad right stick?..
Cursor position stored insideWindowcomponent. Just create an observer that moves it from your actions input.
Just make your observer generic and register it with RedirectFreelookCamera and RedirectFreelookCameraSmooth separately.
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?
I wouldn't create a marker trait in this case since it's pub(crate).
Would you necessarily discourage it?
No, it's fine π
But I personally wouldn't do it. I prefer to be practical. In my opinion everything is quite clear already from your method name.
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?
@sharp warren π₯² Thanks Github semi-outage
Trigger<Binding<C>> fires when the context C is added as an Actions::<C> component to an entity.
However, you currently do have to register the Context.
See add_input_context for how to do that:
https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/action_instances/trait.InputContextAppExt.html#tymethod.add_input_context
An extension trait for App to assign input to components.
But IIUC I do not need to register every single action, right?
You only need to register the Context. The actions themselves are done in your observer that uses the Trigger<Bound<Context>>.
Not sure if I get the question π€
Are you wondering how to use the menu with multiple gamepads while still allowing local multiplayer?
Looks like you have at least one more comment that is marked as pending. By any chance you forgot to press "Submit" or the GitHub just doesn't display it for some reason?
It's just more duplicates of the same thing π
Ah, got it π
Applied and fixed CI
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?
No need to delete, just give your menu context a higher priority. And destroy the menu context together with the menu.
No need to create an enum. Contexts can accept input from any gamepad and it's actually the default behavior.
oh, ok, so if I do not actually need for players to have different key bindings, I do not even need an enum? nice
Yep
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.)
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?
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`
Yes, it's correct!
Could you, please, PR the docs update with this info?
You can get the value from the movement action by querying the Actions component.
Tracking manually is also an option, especially if you want to be able to dash without movement.
Pulse is condition. You need with_conditions.
But why are you using it for your menu? π€
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
You need to use Completed or Started event.
I highly suggest to read the quick start guide first π
I did :*)
Might've missed it
No, we might just need to improve the docs π€
I'm not a native speaker, so things might be a bit rough. Alice said she'll help me with this later.
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.
Not sure what your query does, but if you need access things that loaded after in order to create bindings, then you need to insert the Actions component once they are loaded.
Or skip binding if it's not loaded yet and rebind later on load.
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");
Then you most likely missing GameplayCtx on your entity.
indeed. hulk meme warranted
@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.
Yeah, timestamps from the driver would be ideal
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 Oh, I see Actions component. How do i get input for any given action?value() method, nvm
I don't have experience, but Fired is triggered every frame, so you can use it as well.
But pull-based API (querying for Actions) is also fine.
On the latest master it's even fully typed.
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
By any chance you forgot to register context?
nope, observers work, but polling action value through query yield nothing, tried wit hbool action as well
Show us the logs via RUST_LOG=bevy_enhanced_input=debug cargo run ...
When are you running the system?
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...
Is there a reason you are doing a Query instead of a Single?
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
Try setting it up like this says:
https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/#pull-style
Input manager for Bevy, inspired by Unreal Engine Enhanced Input.
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.
hmm, somehow same
So the info doesn't fire?
nope, but debug still fires, I'm confused
Can you show your updated system?
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
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.
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
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.
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
Do you have any info in the bind observer? If not, might want to add one.
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
oh, wait you mean the bind observer
the bind observer is definitely fired, game would crash otherwise
upd: just checked with log
Ok
I see NavigateModal in your logs, but Navigate in your function.
Would it be possible to make some of the types Reflect? (so that they can be debugged via the inspector)
ActionState, ActionValue, etc.
Drop a PR please!
indeed! thank you!
it means priority works, but I do need to delete the menu context in gameplay after all
Good idea! Yes, should be doable.
BTW, just curious, what are you working on? π
Multiplayer offroad driving simulator
Cool!
I just published a new release on crates.io which should be network-ready.
If you face any issues or have any questions about how to network it, let me know.
i'm using this branch currently for the networking: https://github.com/projectharmonia/bevy_enhanced_input/pull/125
Yeah, I think get_mut is more convenient then iter_mut π€
Though, I never written in Rust or made games before π₯² .
I come from the world of ML, CUDA and C++
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
I'm also a former C++ programmer π
I think users will never need a reverse lookup π€
I.e. if apply the state back, it's better to iterate over your current state binding and use get_mut.
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?
We can't merge mocked value and the current state. When the state is evaluated, it's a transition from the current state to the next one (mocked one in the mentioned case). This is why mocking doesn't affect the action immediately.
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.
Component that stores actions with their bindings for a specific InputContext.
@sharp warren I think i have it working, we can merge the PR above
Awesome, approved the PR! Could you apply cargo fmt to it?
yep!
Once Alice approves, I'll merge and draft a patch release since all changes are non-breaking
@thick kiln one small thing:
https://github.com/projectharmonia/bevy_enhanced_input/pull/125#discussion_r2159498473
oh sorry
No problem π
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
You previosly mentioned that you register types for contexts, right?
This way you should be able to bind actions to the context by storing pointers that perform the registration.
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>()
It would be nice if you manage to avoid it. Because it would be less boilerplate for users to register.
You can store generic pointers.
Like this:
struct BindRegistrations<C: InputContext> {
f: fn(&mut Actions<C>),
}
So users will have to register contexts and actions on app.
what i meant is that InputActions and Contexts should be independent, right? I don't want to register an InputAction for a specific Context
It would be nice, yes. But not entirely necessary.
I mean registration like this app.network_action<ContextName, ActionName>() would work, but a bit boilerplate to write.
Another idea. What if you run the bind function on the server, but just discard all the mappings? Just to make it work automatically.
The PR is merged, let me know when you done, I will just draft a patch
It would work, but I feel like it's not the spirit of the crate, no? It feels like the crate encourages being able to dynamically attach actions to any context.
I'll do that as a last resort
Yes, I totally agree π
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
Not sure if I get you, could you elaborate? π€
Basically I would need something like this: https://github.com/projectharmonia/bevy_enhanced_input/pull/128/files
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
Ah, got it, so you need Actions without a C.
But don't you need C to obtain the action_map and bindings from a component?
Ah, right, even knowing the C, we can't store a function pointer for a generic context, you need concrete types.
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
I got the idea, let me provide you with a little bit nicer API for it!
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
Let me play with it for a little to make it a bit more encapsulated π
In the meanwhile, you can continue using with the bind_untyped draft.
yea, just trying to get a full working example, i think i'm close
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 snapshotserver::UpdateActions.before(EnhancedInputSystem)on the server, because I need to update the Actions using the snapshot before triggering events on the serverclient::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
Why in host-server mode you need to buffer what server does? π€
There could be some input_delay, so client actions are stored in the buffer with N tick of delay
Makes sense. Luckily triggering is decoupled, so it's not hard to split it.
The changes looks good
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
Yes, from the logs I see you getting Completed. No Fired in the logs?
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
Hmmm... Completed is a transition from Triggered to None. So yeah, I think you do an extra update somehwere.
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
Mocking should override it. Shouldn't you mock the input?
I'd expect you to set the initial state and then mock the next one.
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
But if you update the state directly, there won't be any transitions.
I'd expect users to set the next state via mocking
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
We could do this, but right now it behaves identically to "no input" and the action gets None
I don't get this part. Why you don't mock the next state on the client?
I'd expect you on each tick to do both: set the initial state for this tick and then mock the next value
on rollbacks?
Yeah
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,
}
?
Yes, but instead of duplicating the next state and value, I would fetch it from the next Snapshot
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
Correct!
One clarification, just to ensure we are on the same page:
and when we resimulate tick T+1, we mock the state/value from tick T+1
You mock the state and value for T + 1 and then resimulate. Actions will be evaluated inFixedPreUpdate, so you simulation will deal with correct events and value.
I'm open to reorganizing things as well. So if you think we might need to change the API to make it easier to network, I'm open to it.
I tried to "predict" how the networking API would look like, but I need an actual impl like yours to help me polish/adjust it.
@thick kiln ah, I think instead of T and T+1 it should be T-1 and just T. Sorry for confusing.
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
Agree!
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
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
Yes! After reading this, I think mocking is just not the right tool for this.
So here is what I think:
- Split
updateandtriggeras you already did in your PR. This way you can read the input and quickly swap the entire state before any triggers. - On headless server we don't need
updateat 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. - 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?
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
Regarding 2: headless server usually controlled with features, maybe worth putting it under a feature, so users could conveniently disable it π€
Regarding 3: Agree, let's continue with type erasure.
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 ?
Bitset with events triggered by updating ActionState for an action.
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
It's totally up to you.
It's just quite expensive to send. With durations and events it's 9 extra bytes. And you send them in batches, so it's per input.
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
Sure, let's go with something working first.
As for mocking, I think storing events for rollback is the right call in general. It's only a single byte. So I wouldn't use mocking. But I would definitely work delta-compression and avoiding events with durations from sending.
I mean in the future, not now π
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 π
My example is here if you want to try: https://github.com/cBournhonesque/lightyear/tree/main/examples/bevy_enhanced_inputs
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
sure, let me know!
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
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:
- No type erasure for
Actions<C>needed to bind actions because actions are just entities. Should make it simpler for @thick kiln. - Binding process could be done by loading a Bevy scene, which automatically solves https://github.com/projectharmonia/bevy_enhanced_input/issues/101.
- 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:
- Pull-based API will work with
ActionValueenum instead of the defined output of the action. - I have to rewrite a lot of code π
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
I like this abstraction!
As for merging all inputs into a single component, we already have all inputs in a single Actions<C> component, where C is a generic marker. But it also includes bindings, mockings, etc.
But on server you don't really need bindings or mocking and mocking is optional for clients too. So I feel like I reimplementing components on entities π€
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
Good point. Any ideas on the API for it?
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
That might work; we would still have a release with the networking enabled for the current design, right?
Oof, I know the feeling. Sometimes I implement something fancy only to realize I invented a worse version of the ECS haha
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
I were thinking about releasing only the rewrite π€ But I can merge your PR and draft a release if you want. Just not sure how useful it would be, the new version will have a drastically different API.
Will try to present the design document today π
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
Okay, I'll draft a new release this evening after work
Very interesting. I'm open to this, but I'm going to ask for a custom relation type, rather than using children for this
Yes, yes, I meant a custom relation type π
Working on a detailed design right now
Described my vision in this issue:
https://github.com/projectharmonia/bevy_enhanced_input/issues/129
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.
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.
Sounds amazing!
@outer wyvern by any chance you missed https://github.com/projectharmonia/bevy_enhanced_input/pull/128 ?
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
Ah, thanks, would appretiate it!
I didn't think you wanted my opinion too π
Second opinion never hurts π
We agreed on 2 approvals for each PR and one for our own.
Waiting for your PR and I'll draft a new release
well, I plan to do it tomorrow
Then I'll draft it today and then do a patch π
deal
One more thing and I'll draft a new release:
https://github.com/projectharmonia/bevy_enhanced_input/pull/132
The release is up @thick kiln
I won't do the announcement this time since it's just a few changes.
Perfect thanks
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.
You'll need to set up separate modifier actions and then use those modifier actions in a Chord to get the behavior you want.
so something like this?
actions.bind::<Navigate>().to(Cardinal::dpad_button()).with_conditions(Chord::<Modifier1>::default());
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.
How would I allow the player to make custom Modifiers, without having to create countless ModifierX actions?
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?
There is no way, but you can create your own modifier that internally holds programmable modifiers.
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.
Ah, I think I misunderstood you. You can't create dynamic actions as for right now.
But it's something we might support after the refactor: https://github.com/projectharmonia/bevy_enhanced_input/issues/129
(fixed the link)
What is your use case?
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
Don't you usually have a hardcoded list of such actions? Like UiChord and such.
Yes that was maybe a bad example
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.
Yeah, thatβs pretty much what Iβm after.
Yeah, I currently use an external solution to allow joystick modifiers, but having it within the game would be great.
With gamepad you can't have insane amount of modifiers. You just won't be able to memorize them π€
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
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.
Never played π€
Will take a look into it after the refactor to components. This shold open many possibilities.
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.
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.
The patch is up!
Now I'll experiment with component-based approach.
Not entirely sure if it's possible to express evaluation through observers, but worth trying π
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)?
Hi! Thanks!
You can mock actions using Actions::mock
I'll try that out, thanks!
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?
You can configure your set to run after or before https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/enum.EnhancedInputSet.html
Label for the system that updates input context instances.
ah that would work indeed, thanks!
No, observers is not the right tool π€
Maybe I can express the current trait-based modifiers/conditions using dynamic queries, will explore this path.
One shot systems called recursively?
Similar to observers: expensive and hard to reason about the logic π€
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 π€
@outer wyvern what Bevy plans for the trait queries?
SMEs disagree on introducing them
But we'll do our very best not to break them
Is it considered a bad pattern?
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 intodyn Traitusing custom registry. - Create an observer for each modifier/conditions and inside its body get it from entity if it contains it.
The mentionedbevy-trait-querywould 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...
Well, major first party consumers might help convince @desert summit and <@&1064697043869777990> π
But yeah, don't pull it in unless they're on board
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.
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 :)
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
This should never happen. Can you reproduce this on stable?
This will fire only for horizontal scroll movement.
Horizontal!?
I'll do it in a second
Yeah, in Bevy it's Vec2. And for vertical you need Y.
Yep,it was the compiler
Weird π
Yeah, sorry to bother
@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?
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
Try ActionState::Started instead of ActionState::Fired
huh?
There's no started
Sorry, was thinking of ActionEvent.
I haven't seen any example including Actions events
how does it work?
That's very likely what I need
actions.events::<ShortCut1>()? == ActionEvents::Started
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
ActionEvents::Started exists, ActionEvents::STARTED shouldn't.
Why are you capitalizing one but not the other?
This shows Started without capitalization:
https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/input_context/events/struct.ActionEvents.html
Bitset with events triggered by updating ActionState for an action.
Guess I've never used the capitalized version.
Triggers when an action switches its state from ActionState::None to ActionState::Fired or ActionState::Ongoing.
Are you using main or a tagged release?
Is there a reason you went with polling instead of observers?
It is a bitflag
if actions.events::<ShortCut1>()?.contains(ActionEvents::STARTED){
println!("started")
}
This should work
yep it does, many thanks
first time I ever encounter bitflags in rust
DeadZone::default(), // Normalizes movement. huh?
https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/input_context/preset/index.html Feel like this list could use some fleshing out
What about single-axis non-buttons like triggers and scroll wheel?
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?
I don't get your first thing because it doesn't matter what it is, right, the point of the abstraction layer is that it's what we do with it? Even if my scroll wheel had 100 axes if I'm only using 1 I want a single-axis thing
For the second yeah I can see that point
Ok, so you want some preset to extract a single axis from multiple?
I could maybe see some edge cases of two axes mapped to one axis (opposing each other), but meh
You can mock values using Actions::mock
Well, I can still do it without a preset I guess, I'm happy then
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
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.
dead zones are similar to regular normalization, but works with digital values as well, like sticks
btw why is DeadZone the modifier to normalize input?
That's what dead zones do
maybe we're thinking of a different normalize? surely it doesn't make it a unit vector?
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
Remaps input values within the range Self::lower_threshold to Self::upper_threshold onto the range 0 to 1. Values outside this range are clamped.
https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/input_context/input_modifier/dead_zone/struct.DeadZone.html
Remaps input values within the range Self::lower_threshold to Self::upper_threshold onto the range 0 to 1. Values outside this range are clamped.
You can do this manually, they are simple enough.
I.e. just apply the swizzle modifier to get the axes you want
Well that doesn't make it a unit vector. Like if your dead zone is 0.2 and your stick is at (0, 0.6) it will now be at (0, 0.5)
and that is normalisation I guess but I found it confusing cause that's kind of secondary to the value "trimming"
It still might be helpful to have common cases like Y or Z extraction.
Unless you have some graphics knowledge, I'm not sure how someone might find Swizzle.
maybe just have it in the examples, if it otherwise looks nice
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.
I think scroll wheel single-axis binding will be very common
Ah, let me clarify this a bit. There are 2 kinds of dead zones. Radial and Axial. By default it's radial, which normalizes the input and multiplies by the clamped length.
But yea, I think I should remove the comment, it's confusing.
I still don't really understand it still, what happens to an input of (0, 0.6) with a deadzone of 0.2?
It will normalize the input, so you get (0.0, 1.0) and then it will multiply it by 0.5.
Where's the 0.5 from?
It's the length of your input
The docs for dead zone are quite confusing, I probably should adjust them.
But in the meanwhile, I'd suggest this article:
http://web.archive.org/web/20210830071724/https://www.gamedeveloper.com/disciplines/doing-thumbstick-dead-zones-right
Thumbstick dead zones are a subtle thing, but handling them incorrectly can make your game feel awkward or broken. Here are some simple techniques Iβve learned
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!
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
The radial deadzone type performs normalization but the axial deadzone does not.
The radial is default though, so by default the deadzone normalizes.
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
Agree
I agree with you, I phrased it poorly. The output is not normalized π
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.
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?
This is missing functionality in bevy_input, rather than something that BEI should be trying to add functionality for right now
(I do think we should be adding it in bevy_input though!)
Ah, I saw that bevy_input had gestures, so I thought it already had swipes. That would make sense as to why BEI doesn't have it yet. Lol Thanks!
@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 
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...
...it looks to be in sync
Now let's enable acceleration
And indeed, at least for GNOME + Wayland, BEI is using the raw input π
Good to have this confirmed haha
Thanks for the investigation π
This went really nice. Porting tests right now. We have a ton of tests π’
But they are quite helpful, already catched a few bugs.
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 likewith_modifiers_each. - Since
Bindingis a component, I have to define them like this:Binding::from(KeyCode::KeyA)instead of just passingKeyCode::KeyAto 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.
I hope this will still work with networking.. will we need to entity mapping between the action entities?
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.
Actually, after writing this down, I have a few ideas how to solve the mentioned ergonomic problems 
Will experiment more.
how should i implement input buffers?
What is the input buffer for?
Queuing inputs for a platformer, I presume from previous context π
I think you can simply store the inputs of every tick in a VecDeque
but how do i do that with the crate
You can create a component with the VecDeque, then push_back when a new input comes in, then pop_front when you want to handle it.
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
Look at how the physics in a fixed timestep example handles inputs through the AccumulatedInput component.
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
You would have to query for the value/state of all actions using the functions in https://github.com/projectharmonia/bevy_enhanced_input/blob/master/src/input_context/actions.rs#L175
oh?
Or store them into the component when the observer is triggered
oh nvm i misunderstood
I'm just saying there's a nonzero chance that there's an API that I might not know about π
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.
how would that work? would you be able to get all the inputs in the buffer?
You can store buffer inputs inside a modifier and return any other value
so if you have the input stored, how do you get it?
Modifiers receive the current input and return any value instead of it. So they could modify the value or return anything else.
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
Ah, this should be outside of the plugin. Modifiers and inptus only for input-related stuff.
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
This should work, yes. Just consume/write the values between EnhancedInputSet::Update and EnhancedInputSet::Trigger :https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/enum.EnhancedInputSet.html#variant.Update
Label for the system that updates input context instances.
To do this you need to query for Actions.
Will be way simpler in the upcoming version where ActionValue and ActionEvents are components.
@sharp warren what type do i use to store the actions?
These 2:
https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/action_value/enum.ActionValue.html
https://docs.rs/bevy_enhanced_input/latest/bevy_enhanced_input/input_context/input_action/enum.ActionState.html
Value from Input for Action.
State for Action.
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
You can use a single resource and just store TypeId of the context and action.
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.
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
I'm open to it, will be ready to discuss after the release.
Will ping you when I open the PR
So you can try the new API early if you want
oh cool, thanks
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 π€
Presets now also done! I love the new API.
The draft is up: https://github.com/projectharmonia/bevy_enhanced_input/pull/140
Need to update the docs, finish porting all examples and maybe a few more final touches.
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 π
Not in the milestone, and we just did a feature freeze π
Lemme ping #engine-dev message
Update: added π
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?
Yes, I would create an entity for it.
It's like observers are also entities.
Ok, thanks
All examples ported, made quite a few ergonomic improvements along the way.
Now I just need to finish the docs π
Exciting!
@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
By default there is no softening.
Are you talking about SmoothNudge modifier?
thats probably it
No, the wasd preset on its own doesn't include any smoothing.
But if you're talking about the examples, they also include the SmoothNudge modifier, which smooths the input using half decay.
ohh yeah i just checked thats it
i should've actually read my own code before asking about it lol
It's fine, don't worry π
@sharp warren is there an option for constant acceleration instead of exponential?
We don't have a built-in modifier for it π€
But you can write it yourself by implementing InputModifier (and potentioally upstream it).
Okay, the PR is ready for review:
https://github.com/projectharmonia/bevy_enhanced_input/pull/140
@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!
from a user perspective, I like that the actions are now defined in the spawn instead of in a weird binding observer π
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 :/
Agree! I just copied the design of the related! macro.
Returns a SpawnRelatedBundle that will insert the given RelationshipTarget, spawn a SpawnableList of entities with given bundles that relate to the RelationshipTarget entity via the RelationshipTarget::Relationship component, and reserve space in the RelationshipTarget for each spawned entity.
huh, didn't notice Bevy had that too
Well, in that case, your API choice makes a ton of sense
I hope the spawning API improves on the Bevy side. Especially with BSN π
Lol I guess this is what I'm doing tomorrow
does this add the input buffer feature? btw the linear acceleration modifier pr will probably be up in the next hour or two
debugging is hard when it doesn't let you use dbg or println
You can set up a logger in tests. But in a pinch, a panic! also helps as a debug message π
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
[Adds Shatur to the list right below cart and his Bevy Assets v2]
opened it
No, this only contains the API change π
Sure, no problem. I will add tests myself look today later after work.