feat(ui): wire high score into game lifecycle and display
Add check_high_score system on GameEnter, insert HighScore resource loaded from disk at startup, display best score on Start Menu and Game Over screens.
This commit is contained in:
parent
060a9a2a14
commit
52b5c9d7e6
3 changed files with 71 additions and 14 deletions
|
|
@ -1,7 +1,9 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::components::{Bullet, Enemy, EnemyBullet, GameOverUI, RestartMessage, StartButton, StartMenuUI};
|
use crate::components::{
|
||||||
use crate::resources::RestartPressed;
|
Bullet, Enemy, EnemyBullet, GameOverUI, HighScoreText, RestartMessage, StartButton, StartMenuUI,
|
||||||
|
};
|
||||||
|
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 {
|
||||||
|
|
@ -16,7 +18,7 @@ const BUTTON_HOVER: Color = Color::srgb(0.2, 0.2, 0.7);
|
||||||
|
|
||||||
// --- Game Over UI ---
|
// --- Game Over UI ---
|
||||||
|
|
||||||
pub fn setup_game_over_ui(mut commands: Commands) {
|
pub fn setup_game_over_ui(mut commands: Commands, high_score: Res<HighScore>, score: Res<Score>) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Text::new("GAME OVER"),
|
Text::new("GAME OVER"),
|
||||||
TextFont {
|
TextFont {
|
||||||
|
|
@ -33,6 +35,25 @@ pub fn setup_game_over_ui(mut commands: Commands) {
|
||||||
},
|
},
|
||||||
GameOverUI,
|
GameOverUI,
|
||||||
));
|
));
|
||||||
|
commands.spawn((
|
||||||
|
Text::new(format!(
|
||||||
|
"Best: {} Your Score: {}",
|
||||||
|
high_score.value, score.value
|
||||||
|
)),
|
||||||
|
TextFont {
|
||||||
|
font_size: 32.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TextColor(Color::WHITE),
|
||||||
|
Node {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
align_self: AlignSelf::Center,
|
||||||
|
justify_self: JustifySelf::Center,
|
||||||
|
top: Val::Percent(48.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
HighScoreText,
|
||||||
|
));
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Text::new("Press R to Restart"),
|
Text::new("Press R to Restart"),
|
||||||
TextFont {
|
TextFont {
|
||||||
|
|
@ -53,7 +74,7 @@ pub fn setup_game_over_ui(mut commands: Commands) {
|
||||||
|
|
||||||
pub fn cleanup_game_over_ui(
|
pub fn cleanup_game_over_ui(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
query: Query<Entity, Or<(With<GameOverUI>, With<RestartMessage>)>>,
|
query: Query<Entity, Or<(With<GameOverUI>, With<RestartMessage>, With<HighScoreText>)>>,
|
||||||
) {
|
) {
|
||||||
for entity in &query {
|
for entity in &query {
|
||||||
commands.entity(entity).despawn();
|
commands.entity(entity).despawn();
|
||||||
|
|
@ -73,7 +94,7 @@ pub fn cleanup_game_entities(
|
||||||
|
|
||||||
// --- Start Menu UI ---
|
// --- Start Menu UI ---
|
||||||
|
|
||||||
pub fn setup_start_menu_ui(mut commands: Commands) {
|
pub fn setup_start_menu_ui(mut commands: Commands, high_score: Res<HighScore>) {
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Node {
|
Node {
|
||||||
|
|
@ -99,6 +120,19 @@ pub fn setup_start_menu_ui(mut commands: Commands) {
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
parent.spawn((
|
||||||
|
Text::new(format!("Best: {}", high_score.value)),
|
||||||
|
TextFont {
|
||||||
|
font_size: 32.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TextColor(Color::WHITE),
|
||||||
|
Node {
|
||||||
|
margin: UiRect::bottom(Val::Px(30.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
HighScoreText,
|
||||||
|
));
|
||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -127,7 +161,10 @@ pub fn setup_start_menu_ui(mut commands: Commands) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cleanup_start_menu_ui(mut commands: Commands, query: Query<Entity, With<StartMenuUI>>) {
|
pub fn cleanup_start_menu_ui(
|
||||||
|
mut commands: Commands,
|
||||||
|
query: Query<Entity, Or<(With<StartMenuUI>, With<HighScoreText>)>>,
|
||||||
|
) {
|
||||||
for entity in &query {
|
for entity in &query {
|
||||||
commands.entity(entity).despawn();
|
commands.entity(entity).despawn();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -8,6 +8,7 @@ pub mod components;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod enemy;
|
pub mod enemy;
|
||||||
pub mod game_state;
|
pub mod game_state;
|
||||||
|
pub mod persistence;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod stage;
|
pub mod stage;
|
||||||
|
|
@ -15,8 +16,7 @@ pub mod starfield;
|
||||||
pub mod systems;
|
pub mod systems;
|
||||||
|
|
||||||
use bullet::{
|
use bullet::{
|
||||||
check_bullet_collisions, check_enemy_bullet_player_collisions, move_bullets,
|
check_bullet_collisions, check_enemy_bullet_player_collisions, move_bullets, move_enemy_bullets,
|
||||||
move_enemy_bullets,
|
|
||||||
};
|
};
|
||||||
use components::TractorBeam;
|
use components::TractorBeam;
|
||||||
use constants::{PLAYER_RESPAWN_DELAY, STARTING_LIVES, WINDOW_HEIGHT, WINDOW_WIDTH};
|
use constants::{PLAYER_RESPAWN_DELAY, STARTING_LIVES, WINDOW_HEIGHT, WINDOW_WIDTH};
|
||||||
|
|
@ -34,14 +34,14 @@ use player::{
|
||||||
player_shoot, respawn_player,
|
player_shoot, respawn_player,
|
||||||
};
|
};
|
||||||
use resources::{
|
use resources::{
|
||||||
AttackDiveTimer, CurrentStage, EnemySpawnTimer, FormationState, PlayerLives,
|
AttackDiveTimer, CurrentStage, EnemySpawnTimer, FormationState, HighScore, PlayerLives,
|
||||||
PlayerRespawnTimer, RestartPressed, Score, StageConfigurations,
|
PlayerRespawnTimer, RestartPressed, Score, StageConfigurations,
|
||||||
};
|
};
|
||||||
use stage::check_stage_clear;
|
use stage::check_stage_clear;
|
||||||
use starfield::scroll_starfield;
|
use starfield::scroll_starfield;
|
||||||
use systems::{
|
use systems::{
|
||||||
animate_explosion, player_exists, player_vulnerable, setup, should_respawn_player,
|
animate_explosion, check_high_score, player_exists, player_vulnerable, setup,
|
||||||
update_window_title,
|
should_respawn_player, update_window_title,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
|
@ -73,6 +73,9 @@ pub fn run() {
|
||||||
timer: Timer::new(Duration::from_secs_f32(3.0), TimerMode::Once),
|
timer: Timer::new(Duration::from_secs_f32(3.0), TimerMode::Once),
|
||||||
})
|
})
|
||||||
.init_resource::<Score>()
|
.init_resource::<Score>()
|
||||||
|
.insert_resource(HighScore {
|
||||||
|
value: persistence::load(&persistence::save_path()).value,
|
||||||
|
})
|
||||||
.init_resource::<CurrentStage>()
|
.init_resource::<CurrentStage>()
|
||||||
.init_resource::<FormationState>()
|
.init_resource::<FormationState>()
|
||||||
.init_resource::<StageConfigurations>()
|
.init_resource::<StageConfigurations>()
|
||||||
|
|
@ -121,7 +124,10 @@ pub fn run() {
|
||||||
start_menu_button_system.run_if(in_state(AppState::StartMenu)),
|
start_menu_button_system.run_if(in_state(AppState::StartMenu)),
|
||||||
)
|
)
|
||||||
// Game over.
|
// Game over.
|
||||||
.add_systems(OnEnter(AppState::GameOver), setup_game_over_ui)
|
.add_systems(
|
||||||
|
OnEnter(AppState::GameOver),
|
||||||
|
(setup_game_over_ui, check_high_score),
|
||||||
|
)
|
||||||
.add_systems(OnExit(AppState::GameOver), cleanup_game_over_ui)
|
.add_systems(OnExit(AppState::GameOver), cleanup_game_over_ui)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@ use bevy::prelude::*;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::components::{Explosion, Invincible, Player};
|
use crate::components::{Explosion, Invincible, Player};
|
||||||
use crate::constants::{EXPLOSION_BASE_SIZE, EXPLOSION_COLOR, EXPLOSION_DURATION, EXPLOSION_MAX_SIZE};
|
use crate::constants::{
|
||||||
|
EXPLOSION_BASE_SIZE, EXPLOSION_COLOR, EXPLOSION_DURATION, EXPLOSION_MAX_SIZE,
|
||||||
|
};
|
||||||
|
use crate::persistence;
|
||||||
use crate::player::spawn_player_ship;
|
use crate::player::spawn_player_ship;
|
||||||
use crate::resources::{CurrentStage, PlayerLives, Score, is_special_stage};
|
use crate::resources::{is_special_stage, CurrentStage, HighScore, PlayerLives, Score};
|
||||||
use crate::starfield::spawn_starfield;
|
use crate::starfield::spawn_starfield;
|
||||||
|
|
||||||
pub fn setup(mut commands: Commands) {
|
pub fn setup(mut commands: Commands) {
|
||||||
|
|
@ -27,6 +30,17 @@ pub fn should_respawn_player(lives: Res<PlayerLives>, query: Query<&Player>) ->
|
||||||
query.is_empty() && lives.count > 0
|
query.is_empty() && lives.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- High Score ---
|
||||||
|
|
||||||
|
pub fn check_high_score(score: Res<Score>, mut high_score: ResMut<HighScore>) {
|
||||||
|
if score.value > high_score.value {
|
||||||
|
high_score.value = score.value;
|
||||||
|
if let Err(e) = persistence::save(&high_score, &persistence::save_path()) {
|
||||||
|
eprintln!("Failed to save high score: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- HUD ---
|
// --- HUD ---
|
||||||
|
|
||||||
pub fn update_window_title(
|
pub fn update_window_title(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue