Bevy allows us to use Rust enums to build high-level state machines that control which systems are running. Typically these are used for creating behavior like pause menus. Our version of 2048 doesn't really benefit from a pause menu, so we'll be using these states to implement "Game Over".
First we can declare a RunState enum. Enums are types whose value can be one of a set of variants. In this case we have a RunState enum whose value can be Playing or GameOver.
We need to define which of these variants is going to be the default value. We can do this with the Default trait and the #[default] attribute macro.
The other derives here are necessary for the .add_state call we’re about to use.
#[derive(
Default, Debug, Clone, Eq, PartialEq, Hash, States,
)]
enum RunState {
#[default]
Playing,
GameOver,
}
In our App::new chain, we'll add the state to our game. This creates a new state machine with the drivers and extra functionality that makes it all work.
.add_state::<RunState>()
Now that we've derived all the traits we need for our type, we can start controlling which systems run in which RunState.
Bevy has sets of systems that run in an order that makes sense for Bevy’s internal behavior. This is the CoreSet, which contains sets like First, Update, and Last.
There are additionally a number of hooks we can use to run systems at different times, such as when we enter or exit a state.
We want to change the set of systems we already have to make them run in the CoreSet::Update set, but only when we’re in the RunState::Playing state.
We can take the tuple of our systems, and use the in_set function in combination with the OnUpdate struct to do this.
.add_systems(
(
render_tile_points,
board_shift,
render_tiles,
new_tile_handler,
end_game,
)
.in_set(OnUpdate(RunState::Playing)),
)
If we run the game now, we should see no differences in behavior.
To end the game we can add a query for the NextState<RunState> resource to the end_game system.
fn end_game(
tiles: Query<(&Position, &Points)>,
query_board: Query<&Board>,
mut run_state: ResMut<NextState<RunState>>,
) {
This lets us set the RunState to GameOver when we detect that there are no more moves.
if has_move == false {
dbg!("game over!");
run_state.set(RunState::GameOver);
}
Setting the state to RunState::GameOver will cause all of the systems we added to the RunState::Playing state to stop running. We can see this happen because we no longer see "game over!" spamming the console.