DEV Community

Sébastien Belzile
Sébastien Belzile

Posted on • Updated on

Making Games in Rust - Part 8 - Towards adding a Main Menu

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:

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

Bevy allows us to register a state as follow:

// main.rs
.add_state(AppState::MainMenu)
Enter fullscreen mode Exit fullscreen mode

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.

// 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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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:

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()));
Enter fullscreen mode Exit fullscreen mode

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:

fn cleanup(mut commands: Commands, query: Query<Entity>) {
    for entity in query.iter() {
        commands.entity(entity).despawn_recursive();
    }
}
Enter fullscreen mode Exit fullscreen mode

Final Code

My final code is available here. 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.

Discussion (0)