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 crate::components::{Bullet, Enemy, EnemyBullet, GameOverUI, RestartMessage, StartButton, StartMenuUI};
|
||||
use crate::resources::RestartPressed;
|
||||
use crate::components::{
|
||||
Bullet, Enemy, EnemyBullet, GameOverUI, HighScoreText, RestartMessage, StartButton, StartMenuUI,
|
||||
};
|
||||
use crate::resources::{HighScore, RestartPressed, Score};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
|
||||
pub enum AppState {
|
||||
|
|
@ -16,7 +18,7 @@ const BUTTON_HOVER: Color = Color::srgb(0.2, 0.2, 0.7);
|
|||
|
||||
// --- 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((
|
||||
Text::new("GAME OVER"),
|
||||
TextFont {
|
||||
|
|
@ -33,6 +35,25 @@ pub fn setup_game_over_ui(mut commands: Commands) {
|
|||
},
|
||||
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((
|
||||
Text::new("Press R to Restart"),
|
||||
TextFont {
|
||||
|
|
@ -53,7 +74,7 @@ pub fn setup_game_over_ui(mut commands: Commands) {
|
|||
|
||||
pub fn cleanup_game_over_ui(
|
||||
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 {
|
||||
commands.entity(entity).despawn();
|
||||
|
|
@ -73,7 +94,7 @@ pub fn cleanup_game_entities(
|
|||
|
||||
// --- 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
|
||||
.spawn((
|
||||
Node {
|
||||
|
|
@ -99,6 +120,19 @@ pub fn setup_start_menu_ui(mut commands: Commands) {
|
|||
..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
|
||||
.spawn((
|
||||
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 {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
|
|
|
|||
18
src/lib.rs
18
src/lib.rs
|
|
@ -8,6 +8,7 @@ pub mod components;
|
|||
pub mod constants;
|
||||
pub mod enemy;
|
||||
pub mod game_state;
|
||||
pub mod persistence;
|
||||
pub mod player;
|
||||
pub mod resources;
|
||||
pub mod stage;
|
||||
|
|
@ -15,8 +16,7 @@ pub mod starfield;
|
|||
pub mod systems;
|
||||
|
||||
use bullet::{
|
||||
check_bullet_collisions, check_enemy_bullet_player_collisions, move_bullets,
|
||||
move_enemy_bullets,
|
||||
check_bullet_collisions, check_enemy_bullet_player_collisions, move_bullets, move_enemy_bullets,
|
||||
};
|
||||
use components::TractorBeam;
|
||||
use constants::{PLAYER_RESPAWN_DELAY, STARTING_LIVES, WINDOW_HEIGHT, WINDOW_WIDTH};
|
||||
|
|
@ -34,14 +34,14 @@ use player::{
|
|||
player_shoot, respawn_player,
|
||||
};
|
||||
use resources::{
|
||||
AttackDiveTimer, CurrentStage, EnemySpawnTimer, FormationState, PlayerLives,
|
||||
AttackDiveTimer, CurrentStage, EnemySpawnTimer, FormationState, HighScore, PlayerLives,
|
||||
PlayerRespawnTimer, RestartPressed, Score, StageConfigurations,
|
||||
};
|
||||
use stage::check_stage_clear;
|
||||
use starfield::scroll_starfield;
|
||||
use systems::{
|
||||
animate_explosion, player_exists, player_vulnerable, setup, should_respawn_player,
|
||||
update_window_title,
|
||||
animate_explosion, check_high_score, player_exists, player_vulnerable, setup,
|
||||
should_respawn_player, update_window_title,
|
||||
};
|
||||
|
||||
pub fn run() {
|
||||
|
|
@ -73,6 +73,9 @@ pub fn run() {
|
|||
timer: Timer::new(Duration::from_secs_f32(3.0), TimerMode::Once),
|
||||
})
|
||||
.init_resource::<Score>()
|
||||
.insert_resource(HighScore {
|
||||
value: persistence::load(&persistence::save_path()).value,
|
||||
})
|
||||
.init_resource::<CurrentStage>()
|
||||
.init_resource::<FormationState>()
|
||||
.init_resource::<StageConfigurations>()
|
||||
|
|
@ -121,7 +124,10 @@ pub fn run() {
|
|||
start_menu_button_system.run_if(in_state(AppState::StartMenu)),
|
||||
)
|
||||
// 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(
|
||||
Update,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ use bevy::prelude::*;
|
|||
use std::time::Duration;
|
||||
|
||||
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::resources::{CurrentStage, PlayerLives, Score, is_special_stage};
|
||||
use crate::resources::{is_special_stage, CurrentStage, HighScore, PlayerLives, Score};
|
||||
use crate::starfield::spawn_starfield;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// --- 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 ---
|
||||
|
||||
pub fn update_window_title(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue