fix(game-state): reset score, lives, stage, and formation on restart; despawn explosions on cleanup

Reset Score, PlayerLives, CurrentStage, and FormationState resources
when restarting from GameOver. Extend cleanup_game_entities to also
despawn Explosion entities.
This commit is contained in:
Harald Hoyer 2026-05-09 11:02:09 +02:00
parent 73c76e75be
commit 933d23cc35

View file

@ -1,9 +1,13 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::components::{ use crate::components::{
Bullet, Enemy, EnemyBullet, GameOverUI, HighScoreText, RestartMessage, StartButton, StartMenuUI, Bullet, Enemy, EnemyBullet, Explosion, GameOverUI, HighScoreText, RestartMessage, StartButton,
StartMenuUI,
};
use crate::constants::STARTING_LIVES;
use crate::resources::{
CurrentStage, FormationState, HighScore, PlayerLives, RestartPressed, Score,
}; };
use crate::resources::{HighScore, RestartPressed, Score};
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)] #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
pub enum AppState { pub enum AppState {
@ -85,7 +89,15 @@ pub fn cleanup_game_over_ui(
pub fn cleanup_game_entities( pub fn cleanup_game_entities(
mut commands: Commands, mut commands: Commands,
query: Query<Entity, Or<(With<Bullet>, With<EnemyBullet>, With<Enemy>)>>, query: Query<
Entity,
Or<(
With<Bullet>,
With<EnemyBullet>,
With<Enemy>,
With<Explosion>,
)>,
>,
) { ) {
for entity in &query { for entity in &query {
commands.entity(entity).despawn(); commands.entity(entity).despawn();
@ -198,9 +210,170 @@ pub fn handle_restart_input(
pub fn restart_game_system( pub fn restart_game_system(
mut next_state: ResMut<NextState<AppState>>, mut next_state: ResMut<NextState<AppState>>,
mut restart: ResMut<RestartPressed>, mut restart: ResMut<RestartPressed>,
mut score: ResMut<Score>,
mut lives: ResMut<PlayerLives>,
mut stage: ResMut<CurrentStage>,
mut formation: ResMut<FormationState>,
) { ) {
if restart.pressed { if restart.pressed {
restart.pressed = false; restart.pressed = false;
score.value = 0;
lives.count = STARTING_LIVES;
stage.number = 1;
stage.waiting_for_clear = false;
formation.next_slot_index = 0;
formation.total_spawned_this_stage = 0;
formation.formation_complete = false;
next_state.set(AppState::Playing); next_state.set(AppState::Playing);
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::components::Explosion;
use crate::constants::STARTING_LIVES;
use crate::resources::{CurrentStage, FormationState, PlayerLives};
use bevy::ecs::world::World;
#[test]
fn restart_game_resets_score_to_zero() {
let mut world = World::default();
world.insert_resource(Score { value: 5000 });
world.insert_resource(PlayerLives { count: 1 });
world.insert_resource(CurrentStage {
number: 5,
waiting_for_clear: true,
});
world.insert_resource(FormationState {
next_slot_index: 4,
total_spawned_this_stage: 10,
formation_complete: true,
});
world.insert_resource(NextState::<AppState>::Pending(AppState::Playing));
world.insert_resource(RestartPressed { pressed: true });
world.register_system(restart_game_system);
world
.run_system_cached(restart_game_system)
.expect("system should run");
let score = world.resource::<Score>();
assert_eq!(score.value, 0, "Score should be reset to 0 on restart");
}
#[test]
fn restart_game_resets_player_lives_to_starting_lives() {
let mut world = World::default();
world.insert_resource(Score { value: 5000 });
world.insert_resource(PlayerLives { count: 1 });
world.insert_resource(CurrentStage {
number: 1,
waiting_for_clear: false,
});
world.insert_resource(FormationState::default());
world.insert_resource(NextState::<AppState>::Pending(AppState::Playing));
world.insert_resource(RestartPressed { pressed: true });
world.register_system(restart_game_system);
world
.run_system_cached(restart_game_system)
.expect("system should run");
let lives = world.resource::<PlayerLives>();
assert_eq!(
lives.count, STARTING_LIVES,
"PlayerLives should be reset to STARTING_LIVES on restart"
);
}
#[test]
fn restart_game_resets_current_stage_to_defaults() {
let mut world = World::default();
world.insert_resource(Score { value: 0 });
world.insert_resource(PlayerLives {
count: STARTING_LIVES,
});
world.insert_resource(CurrentStage {
number: 5,
waiting_for_clear: true,
});
world.insert_resource(FormationState::default());
world.insert_resource(NextState::<AppState>::Pending(AppState::Playing));
world.insert_resource(RestartPressed { pressed: true });
world.register_system(restart_game_system);
world
.run_system_cached(restart_game_system)
.expect("system should run");
let stage = world.resource::<CurrentStage>();
assert_eq!(
stage.number, 1,
"CurrentStage.number should be reset to 1 on restart"
);
assert_eq!(
stage.waiting_for_clear, false,
"CurrentStage.waiting_for_clear should be reset to false on restart"
);
}
#[test]
fn restart_game_resets_formation_state_to_defaults() {
let mut world = World::default();
world.insert_resource(Score { value: 0 });
world.insert_resource(PlayerLives {
count: STARTING_LIVES,
});
world.insert_resource(CurrentStage {
number: 1,
waiting_for_clear: false,
});
world.insert_resource(FormationState {
next_slot_index: 4,
total_spawned_this_stage: 10,
formation_complete: true,
});
world.insert_resource(NextState::<AppState>::Pending(AppState::Playing));
world.insert_resource(RestartPressed { pressed: true });
world.register_system(restart_game_system);
world
.run_system_cached(restart_game_system)
.expect("system should run");
let formation = world.resource::<FormationState>();
assert_eq!(
formation.next_slot_index, 0,
"FormationState.next_slot_index should be reset to 0"
);
assert_eq!(
formation.total_spawned_this_stage, 0,
"FormationState.total_spawned_this_stage should be reset to 0"
);
assert_eq!(
formation.formation_complete, false,
"FormationState.formation_complete should be reset to false"
);
}
#[test]
fn cleanup_game_entities_despawns_explosions() {
let mut world = World::default();
world.spawn(Explosion {
timer: Timer::from_seconds(0.4, TimerMode::Once),
});
world.register_system(cleanup_game_entities);
world
.run_system_cached(cleanup_game_entities)
.expect("system should run");
let mut query = world.query::<&Explosion>();
let explosion_count = query.iter(&world).count();
assert_eq!(
explosion_count, 0,
"Explosion entities should be despawned during cleanup"
);
}
}