Skip to content

StateTransition schedule is inconsistent #19594

Open
@Wuketuke

Description

@Wuketuke

What I did

I have a state that holds information about the general game state (being in main menu, splash screen, main game etc).
Some resources in my application are only relevant in that specific state, so i want to remove them when they are not needed
I thought the StateTransition schedule is where the new state gets set, so i can put any system that removes / inserts the relevant resources there

What went wrong

Sometimes, the StateTransition schedule gets run 1 frame later than the global state is set (i think), so any systems relying on state specific resources will cause the game to crash
in this example, it will just print an error to the console

Workarounds

I could insert a custom resource that handles the game state and transitions, but what is the point of bevys own system feature then?
I dont understand what the point of a dedicated StateTransition schedule is, if not for things like this

Code

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Update, sys_start_transition)
        .add_systems(StateTransition, sys_do_transition)
        .add_systems(FixedUpdate, sys_main_menu.run_if(in_main_menu))
        .add_systems(FixedUpdate, sys_in_game.run_if(in_game))
        .init_state::<GameState>()
        .run();
}

#[derive(Clone, Copy, PartialEq, Eq, States, Debug, Hash, Default)]
enum GameState {
    #[default]
    Init,
    MainMenu,
    InGame,
}

/// key input to transition to a new state
fn sys_start_transition(
    mut transition: ResMut<NextState<GameState>>,
    mut commands: Commands,
    current: Res<State<GameState>>,
    keys: Res<ButtonInput<KeyCode>>,
) {
    let current = **current;
    let next = match current {
        GameState::Init => GameState::MainMenu,
        GameState::MainMenu => GameState::InGame,
        GameState::InGame => GameState::MainMenu,
    };

    // the issue occures no matter if the transition is triggered with Commands or NextState
    if keys.just_pressed(KeyCode::Space) {
        commands.set_state(next);
    } else if keys.just_pressed(KeyCode::Enter) {
        transition.set(next);
    } else {
        return;
    };

    info!("transition from {current:?} to {next:?}");
}

/// remove and insert the state specific resources
fn sys_do_transition(
    mut commands: Commands,
    mut events: EventReader<StateTransitionEvent<GameState>>,
) {
    for event in events.read() {
        let old = event.exited;
        let new = event.entered;

        // remove old state
        match old {
            Some(GameState::Init) => (),
            Some(GameState::MainMenu) => commands.remove_resource::<MainMenuData>(),
            Some(GameState::InGame) => commands.remove_resource::<InGameData>(),
            None => (),
        }

        // insert new state
        match new {
            Some(GameState::Init) => (),
            Some(GameState::MainMenu) => commands.init_resource::<MainMenuData>(),
            Some(GameState::InGame) => commands.init_resource::<InGameData>(),
            None => (),
        }
    }
}

#[derive(Default, Resource)]
struct MainMenuData;

#[derive(Default, Resource)]
struct InGameData;

/// example system that gets run in the game
fn sys_in_game(data: Option<ResMut<InGameData>>) {
    if data.is_none() {
        error!("InGameData cannot be found!!")
    }
}

/// example system that gets run in the main menu
fn sys_main_menu(data: Option<ResMut<MainMenuData>>) {
    if data.is_none() {
        error!("MainMenuData cannot be found!!")
    }
}

fn in_main_menu(state: Res<State<GameState>>) -> bool {
    matches!(**state, GameState::MainMenu)
}

fn in_game(state: Res<State<GameState>>) -> bool {
    matches!(**state, GameState::InGame)
}

Bevy version

i tested this on 0.15.0, 0.15.3, 0.16.1, and the main branch

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-StatesApp-level states machinesC-BugAn unexpected or incorrect behaviorS-Needs-InvestigationThis issue requires detective work to figure out what's going wrong

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions