mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
feat: add game stats
This commit is contained in:
parent
472a238a1c
commit
d2dd57bcff
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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
9
src/stats/components.rs
Normal 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
20
src/stats/mod.rs
Normal 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
21
src/stats/resources.rs
Normal 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
22
src/stats/stats.rs
Normal 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
16
src/stats/systems/mod.rs
Normal 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)),
|
||||||
|
);
|
||||||
|
}
|
||||||
39
src/stats/systems/score.rs
Normal file
39
src/stats/systems/score.rs
Normal 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);
|
||||||
|
}
|
||||||
17
src/stats/systems/setup.rs
Normal file
17
src/stats/systems/setup.rs
Normal 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");
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user