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.