The Snake game is fairly playable now. It’s recognizable as Snake in any case.
We still don’t have any way to end the game or restart the game once it ends.
To do that we’re going to introduce game states.
In lib.rs
we can create a new enum to represent the states we could be in. We’ll have one for the main menu and one for playing the game.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GameState {
Menu,
Playing,
}
Then in main.rs
we’ll insert the Playing
state.
.add_loopless_state(GameState::Playing)
To show off how game states can help us, we’ll update the tick
system to only run in the Playing
state.
The run_in_state
function comes from iyes_loopless and is called on the system function itself.
.add_stage_before(
CoreStage::Update,
"snake_tick",
FixedTimestepStage::new(Duration::from_millis(
100,
))
.with_stage(
SystemStage::parallel().with_system(
tick.run_in_state(GameState::Playing),
),
),
)
Now, if we run the game the snake should still respond to our movement and eat apples, etc.
If we change the add_loopless_state
function to insert GameState::Menu
instead, our snake won’t move because the tick
system won’t be running!
This is how we’ll switch back and forth between the game running and the menu system after a game over.
We can also apply this to any of the other systems we have running. In our FoodPlugin
, bring use iyes_loopless::prelude::*;
and crate::GameState
into scope, then apply run_in_state
to food_event_listener
.
impl Plugin for FoodPlugin {
fn build(&self, app: &mut App) {
app.add_event::<NewFoodEvent>().add_system(
food_event_listener
.run_in_state(GameState::Playing),
);
}
}
and ControlsPlugin
impl Plugin for ControlsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Direction>().add_system(
user_input.run_in_state(GameState::Playing),
);
}
}