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 MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown
|
||||
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;
|
||||
|
||||
use bevy::{ecs::system::RunSystemOnce, prelude::*};
|
||||
use components::IdleTimer;
|
||||
|
||||
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) {
|
||||
|
||||
@ -8,6 +8,7 @@ pub mod hint;
|
||||
pub mod maze;
|
||||
pub mod player;
|
||||
pub mod screens;
|
||||
pub mod stats;
|
||||
pub mod theme;
|
||||
|
||||
use bevy::{
|
||||
@ -69,6 +70,7 @@ impl Plugin for AppPlugin {
|
||||
floor::plugin,
|
||||
player::plugin,
|
||||
hint::plugin,
|
||||
stats::plugin,
|
||||
));
|
||||
|
||||
// Enable dev tools for dev builds.
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
//! The screen state for the main gameplay.
|
||||
|
||||
use crate::player::spawn_player_command;
|
||||
use crate::screens::Screen;
|
||||
use crate::{hint::spawn_hint_command, maze::spawn_level_command};
|
||||
use crate::{
|
||||
hint::spawn_hint_command, maze::spawn_level_command, player::spawn_player_command,
|
||||
screens::Screen, stats::spawn_stats_command,
|
||||
};
|
||||
|
||||
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
|
||||
|
||||
@ -13,6 +14,7 @@ pub(super) fn plugin(app: &mut App) {
|
||||
spawn_level_command,
|
||||
spawn_player_command,
|
||||
spawn_hint_command,
|
||||
spawn_stats_command,
|
||||
)
|
||||
.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 palette;
|
||||
mod systems;
|
||||
mod widgets;
|
||||
pub mod widgets;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub mod prelude {
|
||||
|
||||
@ -19,6 +19,8 @@ pub trait Widgets {
|
||||
|
||||
/// Spawn a simple text label.
|
||||
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 {
|
||||
@ -107,6 +109,20 @@ impl<T: SpawnUi> Widgets for T {
|
||||
));
|
||||
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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user