feat: Implement Game Over state and UI cleanup
This commit is contained in:
parent
9e5addb59d
commit
0f4737fffd
|
@ -43,10 +43,10 @@ nix develop --command bash -c "cargo build"
|
||||||
* ~~Add a `PlayerLives` resource (e.g., starting with 3).~~
|
* ~~Add a `PlayerLives` resource (e.g., starting with 3).~~
|
||||||
* ~~Modify `check_player_enemy_collisions`: Instead of just printing, decrement the lives count.~~
|
* ~~Modify `check_player_enemy_collisions`: Instead of just printing, decrement the lives count.~~
|
||||||
* ~~Implement player destruction (despawn the player sprite) and respawn logic (maybe after a short delay, with temporary invincibility).~~
|
* ~~Implement player destruction (despawn the player sprite) and respawn logic (maybe after a short delay, with temporary invincibility).~~
|
||||||
* **Game Over State:**
|
* ~~**Game Over State:**~~ **(DONE)**
|
||||||
* Use Bevy's `States` (e.g., `Playing`, `GameOver`).
|
* ~~Use Bevy's `States` (e.g., `Playing`, `GameOver`).~~
|
||||||
* Transition to `GameOver` when `PlayerLives` reaches zero.
|
* ~~Transition to `GameOver` when `PlayerLives` reaches zero.~~
|
||||||
* In the `GameOver` state: stop enemy spawning, stop player controls, display a "Game Over" message (using `bevy_ui`), potentially offer a restart option.
|
* ~~In the `GameOver` state: stop enemy spawning, stop player controls, display a "Game Over" message (using `bevy_ui`), potentially offer a restart option.~~
|
||||||
* **Scoring:**
|
* **Scoring:**
|
||||||
* Add a `Score` resource.
|
* Add a `Score` resource.
|
||||||
* Increment the score in `check_bullet_collisions` when an enemy is hit.
|
* Increment the score in `check_bullet_collisions` when an enemy is hit.
|
||||||
|
|
95
src/main.rs
95
src/main.rs
|
@ -1,6 +1,12 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*; // Removed unused AppExit
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
// --- Game States ---
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
|
||||||
|
enum AppState {
|
||||||
|
#[default]
|
||||||
|
Playing,
|
||||||
|
GameOver,
|
||||||
|
}
|
||||||
// --- Constants ---
|
// --- Constants ---
|
||||||
const WINDOW_WIDTH: f32 = 600.0;
|
const WINDOW_WIDTH: f32 = 600.0;
|
||||||
const WINDOW_HEIGHT: f32 = 800.0;
|
const WINDOW_HEIGHT: f32 = 800.0;
|
||||||
|
@ -53,9 +59,65 @@ struct PlayerRespawnTimer {
|
||||||
timer: Timer,
|
timer: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Game Over UI ---
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct GameOverUI;
|
||||||
|
|
||||||
|
fn setup_game_over_ui(mut commands: Commands) {
|
||||||
|
println!("Entering GameOver state. Setting up UI.");
|
||||||
|
commands.spawn((
|
||||||
|
TextBundle::from_section(
|
||||||
|
"GAME OVER",
|
||||||
|
TextStyle {
|
||||||
|
font_size: 100.0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_style(Style {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
align_self: AlignSelf::Center,
|
||||||
|
justify_self: JustifySelf::Center,
|
||||||
|
top: Val::Percent(40.0), // Center vertically roughly
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
GameOverUI, // Tag the UI element
|
||||||
|
));
|
||||||
|
// TODO: Add "Press R to Restart" text later
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_game_over_ui(mut commands: Commands, query: Query<Entity, With<GameOverUI>>) {
|
||||||
|
println!("Exiting GameOver state. Cleaning up UI.");
|
||||||
|
for entity in query.iter() {
|
||||||
|
commands.entity(entity).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cleanup ---
|
||||||
|
|
||||||
|
fn cleanup_game_entities(
|
||||||
|
mut commands: Commands,
|
||||||
|
bullet_query: Query<Entity, With<Bullet>>,
|
||||||
|
enemy_query: Query<Entity, With<Enemy>>,
|
||||||
|
// Optionally despawn player too, or handle separately if needed for restart
|
||||||
|
// player_query: Query<Entity, With<Player>>,
|
||||||
|
) {
|
||||||
|
println!("Exiting Playing state. Cleaning up game entities.");
|
||||||
|
for entity in bullet_query.iter() {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
for entity in enemy_query.iter() {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
// for entity in player_query.iter() {
|
||||||
|
// commands.entity(entity).despawn();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new() // Start App builder
|
||||||
|
.init_state::<AppState>() // Initialize the AppState *after* App::new()
|
||||||
.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
|
.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
|
@ -76,19 +138,29 @@ fn main() {
|
||||||
})
|
})
|
||||||
// Add Systems
|
// Add Systems
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
|
// Systems running only when Playing
|
||||||
.add_systems(Update, (
|
.add_systems(Update, (
|
||||||
move_player,
|
move_player,
|
||||||
player_shoot.run_if(player_exists), // Only shoot if player exists
|
player_shoot.run_if(player_exists),
|
||||||
move_bullets,
|
move_bullets,
|
||||||
move_enemies,
|
move_enemies,
|
||||||
spawn_enemies,
|
spawn_enemies,
|
||||||
check_bullet_collisions,
|
check_bullet_collisions,
|
||||||
check_player_enemy_collisions.run_if(player_vulnerable), // Only check if player exists and is not invincible
|
check_player_enemy_collisions.run_if(player_vulnerable),
|
||||||
respawn_player.run_if(should_respawn_player), // Conditionally run respawn
|
respawn_player.run_if(should_respawn_player),
|
||||||
manage_invincibility, // Handle invincibility timer and blinking
|
manage_invincibility,
|
||||||
update_window_title, // Show lives in window title
|
// Game Over check is now implicit in check_player_enemy_collisions
|
||||||
// (Game Over check would go here later)
|
).run_if(in_state(AppState::Playing)))
|
||||||
|
// Systems running regardless of state (or managing state transitions)
|
||||||
|
.add_systems(Update, (
|
||||||
|
update_window_title, // Keep title updated
|
||||||
|
// Add system to check for restart input in GameOver state later
|
||||||
|
bevy::window::close_on_esc, // Allow closing anytime
|
||||||
))
|
))
|
||||||
|
// Systems for entering/exiting states
|
||||||
|
.add_systems(OnEnter(AppState::GameOver), setup_game_over_ui)
|
||||||
|
.add_systems(OnExit(AppState::GameOver), cleanup_game_over_ui)
|
||||||
|
.add_systems(OnExit(AppState::Playing), cleanup_game_entities) // Cleanup when leaving Playing
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,6 +347,7 @@ fn check_player_enemy_collisions(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut lives: ResMut<PlayerLives>,
|
mut lives: ResMut<PlayerLives>,
|
||||||
mut respawn_timer: ResMut<PlayerRespawnTimer>,
|
mut respawn_timer: ResMut<PlayerRespawnTimer>,
|
||||||
|
mut next_state: ResMut<NextState<AppState>>, // Resource to change state
|
||||||
// Query player without Invincible component - relies on run_if condition too
|
// Query player without Invincible component - relies on run_if condition too
|
||||||
player_query: Query<(Entity, &Transform), (With<Player>, Without<Invincible>)>,
|
player_query: Query<(Entity, &Transform), (With<Player>, Without<Invincible>)>,
|
||||||
enemy_query: Query<(Entity, &Transform), With<Enemy>>,
|
enemy_query: Query<(Entity, &Transform), With<Enemy>>,
|
||||||
|
@ -300,7 +373,7 @@ fn check_player_enemy_collisions(
|
||||||
println!("Respawn timer started.");
|
println!("Respawn timer started.");
|
||||||
} else {
|
} else {
|
||||||
println!("GAME OVER!");
|
println!("GAME OVER!");
|
||||||
// Game Over logic would go here - e.g., transition to a GameOver state
|
next_state.set(AppState::GameOver); // Transition to GameOver state
|
||||||
}
|
}
|
||||||
// Important: Break after handling one collision per frame for the player
|
// Important: Break after handling one collision per frame for the player
|
||||||
break;
|
break;
|
||||||
|
@ -351,7 +424,7 @@ fn manage_invincibility(
|
||||||
// Ensure player is visible when invincibility ends
|
// Ensure player is visible when invincibility ends
|
||||||
if let Some(ref mut vis) = visibility {
|
if let Some(ref mut vis) = visibility {
|
||||||
**vis = Visibility::Visible;
|
**vis = Visibility::Visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue