bevy: triggering state transition from player input locks the game
Bevy version
c78b76bba8def0d72b579b4a06673843f32e8532
What you did
In my game, I have several screens which I can go through by clicking the mouse (with just_pressed
on Input<MouseButton>
). Each screen has its own state.
Very dumb example that reproduce my issue:
use bevy::prelude::*;
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_state(AppState::State1)
.add_system_set(SystemSet::on_enter(AppState::State1).with_system(enter_state.system()))
.add_system_set(SystemSet::on_update(AppState::State1).with_system(next_state.system()))
.add_system_set(SystemSet::on_enter(AppState::State2).with_system(enter_state.system()))
.add_system_set(SystemSet::on_update(AppState::State2).with_system(next_state.system()))
.run();
}
#[derive(Clone, Eq, PartialEq, Debug)]
enum AppState {
State1,
State2,
}
fn enter_state(state: Res<State<AppState>>) {
eprintln!("entering {:?}", state.current());
}
fn next_state(mut state: ResMut<State<AppState>>, mouse_button_input: Res<Input<MouseButton>>) {
if mouse_button_input.just_pressed(MouseButton::Left) {
match state.current() {
AppState::State1 => state.set_next(AppState::State2),
AppState::State2 => state.set_next(AppState::State1),
}
.unwrap();
}
}
What you expected to happen
One click to go from State1
to State2
, then one click to go from State2
to State1
.
What actually happened
On first click, it changes state in a loop and lock the game.
Additional information
Now that system set can rerun in same frame, input isn’t reset between two passes so just_pressed
is always true
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 1
- Comments: 18 (16 by maintainers)
Commits related to this issue
- add documentation on `Input` (#1781) related to #1700 This PR: * documents all methods on `Input<T>` * adds documentation on the struct about how to use it, and how to implement it for a new in... — committed to bevyengine/bevy by mockersf 3 years ago
- add documentation on `Input` (#1781) related to #1700 This PR: * documents all methods on `Input<T>` * adds documentation on the struct about how to use it, and how to implement it for a new in... — committed to jihiggins/bevy by mockersf 3 years ago
- add documentation on `Input` (#1781) related to #1700 This PR: * documents all methods on `Input<T>` * adds documentation on the struct about how to use it, and how to implement it for a new in... — committed to ostwilkens/bevy by mockersf 3 years ago
I just ran into this when porting to 0.5 and I have to say this is surprising behavior. My mental intuition was that states own the entire frame. So if a state transition happens, it doesn’t apply until the next frame.
There is no loop, but the gist of the problem is the same: state changes are processed faster than input, so if you’re consecutively switching several states based on an input edge you’re gonna experience this issue. I still don’t think this is something that needs fixing on Bevy side.
My solution would be a layer of indirection that converts inputs into app-specific events (the same layer would also handle keybindings). So, when a key or a button is down (or just pressed), instead of changing state an event is emitted, and the state change system consumes that event.
a workaround is to add a
on_enter
system for your states that just does:I hope we can find a better fix 😄
The Bevy Cheatbook lists the following workaround.
“If you want to use Input<T> to trigger state transitions using a button/key press, you need to clear the input manually by calling .reset:”