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_entersystem 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:”