-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
triggering state transition from player input locks the game #1700
Comments
Looks like it's working as intended: state chanes are the inner loop, compared to input processing. You'll have to break that loop yourself. |
Yup I know, but I think that's very user-unfriendly, I would hope we have a better solution for that |
From my actual game (but not compiled like this): use bevy::{app::AppExit, prelude::*};
fn main() {
App::build()
.add_state(AppState::Menu)
.add_system_set(SystemSet::on_update(AppState::Menu).with_system(menu_input.system())
.add_system_set(SystemSet::on_update(AppState::Menu).with_system(game_input.system())
.run();
pub enum AppState {
Menu,
InGame,
}
pub fn menu_input(
keyboard_input: Res<Input<KeyCode>>,
mut app_state: ResMut<State<AppState>>,
mut app_exit_events: EventWriter<AppExit>,
) {
if keyboard_input.just_pressed(KeyCode::Space) {
app_state.set_next(AppState::InGame).unwrap();
} else if keyboard_input.just_pressed(KeyCode::Escape) {
app_exit_events.send(AppExit);
}
}
pub fn input(
mut app_state: ResMut<State<AppState>>,
keyboard_input: Res<Input<KeyCode>>,
mut pause_events: EventWriter<PauseEvent>,
) {
if keyboard_input.just_pressed(KeyCode::Escape) {
app_state.set_next(AppState::Menu).unwrap();
}
if keyboard_input.just_pressed(KeyCode::Space) {
//pause logic omited
pause_events.send(PauseEvent);
}
} On 0.4 you could start the game by pressing space and the game would start unpaused, on current main it starts paused. |
a workaround is to add a fn reset_input(mut keyboard_input: ResMut<Input<KeyCode>>,) {
keyboard_input.update();
} I hope we can find a better fix 😄 |
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. |
No need for a whole new system. I ended up taking your suggestion, but simply updating right after reading just_pressed: if keyboard_input.just_pressed(KeyCode::Space) {
app_state.set_next(AppState::InGame).unwrap();
keyboard_input.update();
} Still feels against the spirit of |
In this case I think it probably makes sense to just use a shared This is a bit of a foot-gun though. Worth playing around with ways to make it fool-proof (edit: I'm not implying that people that hit it are fools 😄) |
I've removed this from the 0.5 milestone because I don't think theres much we can do in the short term about this and we do have workarounds. |
If we find a better solution before release (and its not a major change) I'll consider including it. |
not a fix, but I added documentation and helpers around this issue in #1781 |
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. |
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 input type * renames method `update` to a easier to understand `clear` * adds two methods to check for state and clear it after, allowing easier use in the case of #1700 Co-authored-by: Carter Anderson <mcanders1@gmail.com>
related to bevyengine#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 input type * renames method `update` to a easier to understand `clear` * adds two methods to check for state and clear it after, allowing easier use in the case of bevyengine#1700 Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This has bitten me hard twice today. First I hit this exact issue, though further complicated by my use of bevy_input_actionmap which was an extra layer of abstraction between me and the keycodes which made clearing more difficult. I added a very aggressive Now I'm hitting this trying to make egui accessible. My use case is that I'm trying to set an initial keyboard focus. When I transition to a new menu, I transition to a new state and set its keyboard focus. But because state changes are instantaneous, I suspect the old menu isn't clearing. So the click event from the first menu is still around because it's in the same frame, which propagates back to the first menu. I've been shuffling things around, and everything basically looks like I'm pushing state transitions for menus, but the actual buttons getting clicked are on menus from previous states because the immediate mode GUI isn't getting an opportunity to clear its state and rerender. In short, things might be a lot easier if state changes took hold the next frame, letting any retained state reset. |
Emiting event doesn't help to solve this issue / workaround. See #2205 |
The Bevy Cheatbook lists the following workaround. "If you want to use Input to trigger state transitions using a button/key press, you need to clear the input manually by calling .reset:" fn esc_to_menu(
mut keys: ResMut<Input<KeyCode>>,
mut app_state: ResMut<State<AppState>>,
) {
if keys.just_pressed(KeyCode::Escape) {
app_state.set(AppState::MainMenu).unwrap();
keys.reset(KeyCode::Escape);
}
} |
related to bevyengine#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 input type * renames method `update` to a easier to understand `clear` * adds two methods to check for state and clear it after, allowing easier use in the case of bevyengine#1700 Co-authored-by: Carter Anderson <mcanders1@gmail.com>
I think a better potential workaround might be to check whether the resource 'is_changed'. I.e. whether the input has been updated since the last time this system ran. This would be a slightly unwieldy pattern, so it might make sense to add a special Alternatively we could add an extension trait for |
This sounds like it could be the same issue I've run into numerous times when switching between different screens in my game. The way I've solved it so far is by having an IgnoreInput resource that gets checked by each input system and tells them to ignore input for a specified number of frames (usually 1), which gets incremented whenever changing screens. If it's possible, resetting the inputs might be a better solution. |
Fixed by #7267. |
Bevy version
c78b76b
What you did
In my game, I have several screens which I can go through by clicking the mouse (with
just_pressed
onInput<MouseButton>
). Each screen has its own state.Very dumb example that reproduce my issue:
What you expected to happen
One click to go from
State1
toState2
, then one click to go fromState2
toState1
.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 trueThe text was updated successfully, but these errors were encountered: