Making Games in Rust - Part 8 - Towards adding a Main Menu
Tags: rust, bevy, tutorial, programming
Published: 2022-01-05 14:14:38

Our focus in this part of the tutorial is to refactor the code in a way that allows us to include a good looking start up screen to our game.

The notions explained in this tutorial:

- Bevy's States
- Bevy's System Sets

## Refactor to Add a Main Menu Screen

Since we wish to add a main menu screen, we need a way to:

1. Split our game in 2: the main menu and the game
2. Allow our user to control this state.
3. Register and remove resources as we go from one game state to the other.

### Game State

If we translate our need to code, the game state is an enum like this one:

```rs
// main.rs
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum AppState {
    MainMenu,
    InGame,
}
```

Bevy allows us to register a state as follow:

```rs
// main.rs
.add_state(AppState::MainMenu)
```

### Controlling the State

Our user could control the game state with the keyboard, as we learned in part 4. The state can be updated by modifying the state resource using Bevy's API.

```rs
// main.rs
// main function
.add_system(main_menu_controls.system())

// system
fn main_menu_controls(mut keys: ResMut<Input<KeyCode>>, mut app_state: ResMut<State<AppState>>) {
    if *app_state.current() == AppState::MainMenu {
        if keys.just_pressed(KeyCode::Return) {
            app_state.set(AppState::InGame).unwrap();
            keys.reset(KeyCode::Return);
        }
    } else {
        if keys.just_pressed(KeyCode::Escape) {
            app_state.set(AppState::MainMenu).unwrap();
            keys.reset(KeyCode::Escape);
        }
    }
}
```

The state of our game can now be controlled with the `Return` and `Esc` keys. The `.reset` call is because of [this current Bevy issue](https://github.com/bevyengine/bevy/issues/1700).

### Register and Remove Resources

The final step is to register and remove resources when the state of our game changes. Bevy's system sets allows us do just that. If you take a look at our `player.rs` file:

```rs
app.add_system_set(
        SystemSet::on_enter(AppState::InGame).with_system(spawn_player.system()),
    )
    .add_system_set(
        SystemSet::on_update(AppState::InGame)
            .with_system(player_jumps.system())
            .with_system(player_movement.system())
            .with_system(jump_reset.system()),
    )
    .add_system_set(SystemSet::on_enter(AppState::MainMenu).with_system(cleanup.system()))
    .add_system_set(SystemSet::on_exit(AppState::MainMenu).with_system(cleanup.system()));
```

A set of system can be run for a given state. Sets of systems can also run once when exiting or entering a state. The code above registers a system set that spawns the player when the app enters the `AppState::InGame` state.
It also registers a `cleanup` system when the app enters and exists the `AppState::MainMenu` state.
Finally, the code adds systems to run on update when the `AppState::InGame` state is active.

The cleanup systems code is the following:

```rs
fn cleanup(mut commands: Commands, query: Query<Entity>) {
    for entity in query.iter() {
        commands.entity(entity).despawn_recursive();
    }
}
```

## Final Code

My final code is available [here](https://github.com/belzile/platformer/tree/part-8). I am not taking the time to go over the details here. Mostly, I moved the code to end up with a `game` and a `main_menu` module.