Log in to access Rust Adventure videos!

Lesson Details

Ideally when we click the “New Game” button it will start a new game.

Inside of ui.rs in our event handler, we don’t have access to Commands so we can use the context argument to get a mutable reference to the world instead, which will let us insert_resource with the state we want to move to.

In this case, we want to move into the Playing state to start the game.

let on_click_new_game =
    OnEvent::new(|ctx, event| match event.event_type {
        EventType::Click(..) => {
            let mut world =
                ctx.get_global_mut::<World>().unwrap();
            world.insert_resource(NextState(
                GameState::Playing,
            ));
        }
        _ => {}
    });

This works, at least for getting into the Playing state. There’s still a bug where when we hit “New Game” a second time, it throws us into the old game.

We need a way to reset the game when we transition into the Playing state.

In lib.rs we can create a game_reset system.

pub fn reset_game(
    mut commands: Commands,
    mut snake: ResMut<Snake>,
    positions: Query<Entity, With<Position>>,
    mut last_pressed: ResMut<controls::Direction>,
    mut food_events: EventWriter<NewFoodEvent>,
) {
    for entity in positions.iter() {
        commands.entity(entity).despawn_recursive();
    }

    food_events.send(NewFoodEvent);
    *snake = Default::default();
    *last_pressed = Default::default();
}

The game reset system removes the snake and the apples from the board, sends a new food event, resets the snake, and resets the last user input entered.

Removing any Entity that has a Position component will remove all snake segments and all apples. We can do this with the entity id and calling despawn_recursive to make sure we remove everything.

The snake and last_pressed variables already know what type they contain, so we can use Default::default to trigger the default trait implementation for each type.

After setting up the system, we need to trigger it when we enter the GameState::Playing state. iyes_loopless helps us out here with a add_enter_system function on our Bevy App.

.add_enter_system(&GameState::Playing, reset_game)

We can also now remove the spawning logic in board.rs for the snake and the apples, because the game_reset system is handling the apple spawn trigger while tick is handling rendering the snake itself as it starts moving.

for segment in snake.segments.iter() {
    commands.add({
        SpawnSnakeSegment { position: *segment }
    });
}
commands.add(SpawnApple {
    position: Position { x: 15, y: 15 },
})

We can also remove the snake reference in the arguments for the spawn_board system since we aren’t using it anymore.

pub fn spawn_board(mut commands: Commands) {

We can now restart the game as much as we want!