johan helsing.studio

Extreme Bevy 4: Keeping score

In this part, we’ll be implementing respawning and adding a simple UI for keeping track of score.

Our game in its current state quickly becomes... lonely. As soon as the other player is dead, there is nothing else to do.

In the original Extreme Violence, what happens when a player dies, the game stops briefly, a sound effect plays, and then a new level is generated and the camera pans around it and then the players are respawned. We will be doing some of that as well.

So it’s obvious that a lot of things will happen at once when a round ends. Input is disabled, camera behaves differently etc. etc. This kind of screams for yet another state to be added to our app. Otherwise we’d need to add ifs and buts to almost all our systems.

Well use the inventive name Interlude to signify this state:

enum GameState {
    AssetLoading,
    Matchmaking,
    InGame,
    Interlude,
}

And when a player dies, we’ll transition to it:

fn kill_players(
    mut commands: Commands,
    mut state: ResMut<State<GameState>>, // <-- new
    player_query: Query<(Entity, &Transform), (With<Player>, Without<Bullet>)>,
    bullet_query: Query<&Transform, With<Bullet>>,
) {
    for (player, player_transform) in player_query.iter() {
        for bullet_transform in bullet_query.iter() {
            let distance = Vec2::distance(
                player_transform.translation.xy(),
                bullet_transform.translation.xy(),
            );
            if distance < PLAYER_RADIUS + BULLET_RADIUS {
                commands.entity(player).despawn_recursive();
                // state.set errors if we try to set it multiple times,
                // let's just ignore it
                let _ = state.set(GameState::Interlude); // <-- new
            }
        }
    }
}

Let’s move all our in game rollback systems to the InGame state. We need to add a special system set to handle the state transitions as well

                    .with_system_set(State::<GameState>::get_driver())
                    .with_system_set(
                        SystemSet::on_update(GameState::InGame)
                            .with_system(move_players)
                            .with_system(reload_bullet)
                            .with_system(fire_bullets.after(move_players).after(reload_bullet))
                            .with_system(move_bullet)
                            .with_system(kill_players.after(move_bullet).after(move_players)),
                    ),

TODO: are states really rollback safe?? There is a Local in the run criteria...

Also, it kind of makes sense to respawn the players as we enter the new round, so let’s move spawn_players to a new system set inside our rollback schedule:

This tutorial is not finished yet, but you can view the work in progress here

Reference implementation

Diff for this part