feat: add game stats

This commit is contained in:
Kristofers Solo 2025-01-16 23:26:37 +02:00
parent 472a238a1c
commit d2dd57bcff
13 changed files with 174 additions and 5 deletions

View File

@ -3,3 +3,6 @@ pub const WALL_OVERLAP_MODIFIER: f32 = 1.25;
pub const FLOOR_Y_OFFSET: u8 = 200; pub const FLOOR_Y_OFFSET: u8 = 200;
pub const MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown pub const MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown
pub const TITLE: &str = "Maze Ascension: The Labyrinth of Echoes"; pub const TITLE: &str = "Maze Ascension: The Labyrinth of Echoes";
pub const FLOOR_SCORE_MULTIPLIER: f32 = 100.;
pub const TIME_SCORE_MULTIPLIER: f32 = 10.0;

View File

@ -3,9 +3,11 @@ pub mod components;
mod systems; mod systems;
use bevy::{ecs::system::RunSystemOnce, prelude::*}; use bevy::{ecs::system::RunSystemOnce, prelude::*};
use components::IdleTimer;
pub(super) fn plugin(app: &mut App) { pub(super) fn plugin(app: &mut App) {
app.add_plugins(systems::plugin); app.register_type::<IdleTimer>()
.add_plugins(systems::plugin);
} }
pub fn spawn_hint_command(world: &mut World) { pub fn spawn_hint_command(world: &mut World) {

View File

@ -8,6 +8,7 @@ pub mod hint;
pub mod maze; pub mod maze;
pub mod player; pub mod player;
pub mod screens; pub mod screens;
pub mod stats;
pub mod theme; pub mod theme;
use bevy::{ use bevy::{
@ -69,6 +70,7 @@ impl Plugin for AppPlugin {
floor::plugin, floor::plugin,
player::plugin, player::plugin,
hint::plugin, hint::plugin,
stats::plugin,
)); ));
// Enable dev tools for dev builds. // Enable dev tools for dev builds.

View File

@ -1,8 +1,9 @@
//! The screen state for the main gameplay. //! The screen state for the main gameplay.
use crate::player::spawn_player_command; use crate::{
use crate::screens::Screen; hint::spawn_hint_command, maze::spawn_level_command, player::spawn_player_command,
use crate::{hint::spawn_hint_command, maze::spawn_level_command}; screens::Screen, stats::spawn_stats_command,
};
use bevy::{input::common_conditions::input_just_pressed, prelude::*}; use bevy::{input::common_conditions::input_just_pressed, prelude::*};
@ -13,6 +14,7 @@ pub(super) fn plugin(app: &mut App) {
spawn_level_command, spawn_level_command,
spawn_player_command, spawn_player_command,
spawn_hint_command, spawn_hint_command,
spawn_stats_command,
) )
.chain(), .chain(),
); );

9
src/stats/components.rs Normal file
View File

@ -0,0 +1,9 @@
use bevy::prelude::*;
#[derive(Debug, Reflect, Component, Deref, DerefMut)]
#[reflect(Component)]
pub struct Score(pub usize);
#[derive(Debug, Reflect, Component)]
#[reflect(Component)]
pub struct StatsText;

20
src/stats/mod.rs Normal file
View File

@ -0,0 +1,20 @@
pub mod components;
pub mod resources;
pub mod stats;
mod systems;
use bevy::{ecs::system::RunSystemOnce, prelude::*};
use components::Score;
use resources::{FloorTimer, GameTimer};
pub(super) fn plugin(app: &mut App) {
app.register_type::<Score>()
.init_resource::<GameTimer>()
.init_resource::<FloorTimer>()
.insert_resource(FloorTimer(Timer::from_seconds(0.0, TimerMode::Once)))
.add_plugins(systems::plugin);
}
pub fn spawn_stats_command(world: &mut World) {
let _ = world.run_system_once(systems::setup::setup);
}

21
src/stats/resources.rs Normal file
View File

@ -0,0 +1,21 @@
use bevy::prelude::*;
#[derive(Debug, Reflect, Resource, Deref, DerefMut)]
#[reflect(Resource)]
pub struct GameTimer(pub Timer);
#[derive(Debug, Reflect, Resource, Deref, DerefMut)]
#[reflect(Resource)]
pub struct FloorTimer(pub Timer);
impl Default for GameTimer {
fn default() -> Self {
Self(Timer::from_seconds(0.0, TimerMode::Once))
}
}
impl Default for FloorTimer {
fn default() -> Self {
Self(Timer::from_seconds(0.0, TimerMode::Once))
}
}

22
src/stats/stats.rs Normal file
View File

@ -0,0 +1,22 @@
use bevy::prelude::*;
pub trait StatsContainer {
fn ui_stats(&mut self) -> EntityCommands;
}
impl StatsContainer for Commands<'_, '_> {
fn ui_stats(&mut self) -> EntityCommands {
self.spawn((
Name::new("Stats Root"),
Node {
position_type: PositionType::Absolute,
top: Val::Px(10.),
right: Val::Px(10.),
row_gap: Val::Px(8.),
align_items: AlignItems::End,
flex_direction: FlexDirection::Column,
..default()
},
))
}
}

16
src/stats/systems/mod.rs Normal file
View File

@ -0,0 +1,16 @@
mod score;
pub mod setup;
use bevy::prelude::*;
use score::{update_score, update_score_display};
use crate::screens::Screen;
pub(super) fn plugin(app: &mut App) {
app.add_systems(
Update,
(update_score, update_score_display)
.chain()
.run_if(in_state(Screen::Gameplay)),
);
}

View File

@ -0,0 +1,39 @@
use bevy::prelude::*;
use crate::{
constants::{FLOOR_SCORE_MULTIPLIER, TIME_SCORE_MULTIPLIER},
floor::components::{CurrentFloor, Floor},
stats::{components::Score, resources::GameTimer},
};
pub fn update_score(
mut score_query: Query<&mut Score>,
mut game_timer: ResMut<GameTimer>,
floor_query: Query<&Floor, With<CurrentFloor>>,
time: Res<Time>,
) {
game_timer.tick(time.delta());
let Ok(mut score) = score_query.get_single_mut() else {
return;
};
let Ok(current_floor) = floor_query.get_single() else {
return;
};
let time_score = game_timer.elapsed_secs() * TIME_SCORE_MULTIPLIER;
let floor_score = current_floor.0 as f32 * FLOOR_SCORE_MULTIPLIER;
score.0 = (time_score + floor_score) as usize;
}
pub fn update_score_display(score_query: Query<&Score>, mut text_query: Query<&mut Text>) {
let Ok(score) = score_query.get_single() else {
return;
};
let Ok(mut text) = text_query.get_single_mut() else {
return;
};
text.0 = format!("Score: {}", score.0);
}

View File

@ -0,0 +1,17 @@
use bevy::prelude::*;
use crate::{
stats::{components::Score, stats::StatsContainer},
theme::widgets::Widgets,
};
pub fn setup(mut commands: Commands) {
commands.spawn((Name::new("Score"), Score(0)));
commands.ui_stats().with_children(|parent| {
parent.stats("Floor", "0");
parent.stats("Score", "0");
parent.stats("Floor timer", "00:00");
parent.stats("Game timer", "00:00");
});
}

View File

@ -8,7 +8,7 @@ pub mod components;
pub mod events; pub mod events;
pub mod palette; pub mod palette;
mod systems; mod systems;
mod widgets; pub mod widgets;
#[allow(unused_imports)] #[allow(unused_imports)]
pub mod prelude { pub mod prelude {

View File

@ -19,6 +19,8 @@ pub trait Widgets {
/// Spawn a simple text label. /// Spawn a simple text label.
fn label(&mut self, text: impl Into<String>) -> EntityCommands; fn label(&mut self, text: impl Into<String>) -> EntityCommands;
fn stats(&mut self, text: impl Into<String>, value: impl Into<String>) -> EntityCommands;
} }
impl<T: SpawnUi> Widgets for T { impl<T: SpawnUi> Widgets for T {
@ -107,6 +109,20 @@ impl<T: SpawnUi> Widgets for T {
)); ));
entity entity
} }
fn stats(&mut self, text: impl Into<String>, value: impl Into<String>) -> EntityCommands {
let text = text.into();
let entity = self.spawn_ui((
Name::new(text.clone()),
Text(format!("{text}: {}", value.into())),
TextFont {
font_size: 24.0,
..default()
},
TextColor(RosePineDawn::Foam.to_color()),
));
entity
}
} }
/// An extension trait for spawning UI containers. /// An extension trait for spawning UI containers.