game state and restart, but at what cost?? (my mental health)
All checks were successful
Build Bevy Game (Linux + Windows) / build-windows (push) Successful in 18m21s
Build Bevy Game (Linux + Windows) / build-linux (push) Successful in 18m36s

This commit is contained in:
Crizomb 2026-01-04 04:48:34 +01:00
parent 4b9101ad2c
commit 002ad4a162
11 changed files with 143 additions and 23 deletions

View file

@ -5,6 +5,7 @@ use rand::Rng;
use crate::{ use crate::{
core::{ core::{
game_state::DispawnOnGameOver,
kirby::Kirby, kirby::Kirby,
life::{DamageDealer, Life}, life::{DamageDealer, Life},
wave::BubbleSplash, wave::BubbleSplash,
@ -13,7 +14,7 @@ use crate::{
}; };
#[derive(Component)] #[derive(Component)]
#[require(Sprite, PhysicsBody, DensityObject, Life, DamageDealer, SphereCollider)] #[require(Sprite, PhysicsBody, DensityObject, Life, DamageDealer, SphereCollider, DispawnOnGameOver)]
pub struct Bubble { pub struct Bubble {
move_force: f32, move_force: f32,
} }

View file

@ -1,6 +1,7 @@
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
use crate::core::game_state::{DispawnOnGameOver, GameStartupSet, GameState};
use crate::core::kirby::{Kirby, kirby_spawn}; use crate::core::kirby::{Kirby, kirby_spawn};
use crate::physics::sphere_collider::SphereCollider; use crate::physics::sphere_collider::SphereCollider;
use bevy::prelude::*; use bevy::prelude::*;
@ -17,7 +18,7 @@ pub enum CollectableType {
} }
#[derive(Component)] #[derive(Component)]
#[require(SphereCollider, Sprite)] #[require(SphereCollider, Sprite, DispawnOnGameOver)]
pub struct Collectable(CollectableType); pub struct Collectable(CollectableType);
// Not a system. // Not a system.
@ -83,6 +84,7 @@ fn new_kirby(kirby_pos: Vec3, commands: &mut Commands) {
} }
#[derive(Component)] #[derive(Component)]
#[require(DispawnOnGameOver)]
struct KirbySpawnHandler { struct KirbySpawnHandler {
timer: Timer, timer: Timer,
position: Vec3, position: Vec3,
@ -108,7 +110,7 @@ pub struct CollectablePlugin;
impl Plugin for CollectablePlugin { impl Plugin for CollectablePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, collectable_spawner_init) app.add_systems(OnEnter(GameState::Starting), collectable_spawner_init.in_set(GameStartupSet))
.add_systems(FixedUpdate, collectable_actions_system) .add_systems(FixedUpdate, collectable_actions_system)
.add_systems(Update, kirby_spawn_timer); .add_systems(Update, kirby_spawn_timer);
} }

87
src/core/game_state.rs Normal file
View file

@ -0,0 +1,87 @@
use bevy::{ecs::system::SystemParam, prelude::*};
use crate::core::{kirby::Kirby, wave::BubbleWaves};
#[derive(Event)]
struct EndGameEvent {
pub reason: EndGameReason,
}
#[derive(Clone, Copy, Debug)]
enum EndGameReason {
Die,
Victory,
}
fn no_kirbies_emetter(mut commands: Commands, query: Query<(), With<Kirby>>) {
if query.is_empty() {
commands.trigger(EndGameEvent { reason: EndGameReason::Die });
};
}
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
pub enum GameState {
#[default]
Starting,
Running,
Paused,
}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub struct GameStartupSet;
#[derive(Component, Default)]
pub struct DispawnOnGameOver;
pub trait Resetable {
fn reset(&mut self);
}
fn reset_resource<T: Resource + Resetable>(mut res: ResMut<T>) {
res.reset();
}
#[derive(SystemParam)]
struct GameRes<'w> {
bubble_waves: ResMut<'w, BubbleWaves>,
}
fn on_reset_world(
_: On<ResetWorldEvent>,
mut commands: Commands,
entities: Query<Entity, With<DispawnOnGameOver>>,
game_res: GameRes,
) {
for e in &entities {
commands.entity(e).despawn();
}
reset_resource(game_res.bubble_waves);
println!("Set state");
commands.set_state(GameState::Starting);
}
#[derive(Event)]
struct ResetWorldEvent;
fn on_end_game(event: On<EndGameEvent>, mut commands: Commands) {
let reason = event.reason;
println!("end game reason {:?}", reason);
commands.trigger(ResetWorldEvent);
}
fn change_to_running_state(mut commands: Commands) {
println!("Running state !");
commands.set_state(GameState::Running);
}
pub struct GameEventsPlugin;
impl Plugin for GameEventsPlugin {
fn build(&self, app: &mut App) {
app.init_state::<GameState>()
.add_systems(Update, no_kirbies_emetter.run_if(in_state(GameState::Running)))
.add_systems(OnEnter(GameState::Starting), change_to_running_state.after(GameStartupSet))
.add_observer(on_end_game)
.add_observer(on_reset_world);
}
}

View file

@ -1,20 +1,23 @@
use crate::core::game_state::{GameStartupSet, DispawnOnGameOver};
use bevy::prelude::*; use bevy::prelude::*;
const KIRBY_SCALE: f32 = 3.0; const KIRBY_SCALE: f32 = 3.0;
use crate::{ use crate::{
core::life::{DamageDealer, Life}, core::{
game_state::GameState,
life::{DamageDealer, Life},
},
juice::animation::{AnimationIndices, AnimationTimer}, juice::animation::{AnimationIndices, AnimationTimer},
physics::physics_body::PhysicsBody, physics::{physics_body::PhysicsBody, sphere_collider::SphereCollider},
physics::sphere_collider::SphereCollider,
}; };
#[derive(Component)] #[derive(Component)]
#[require(Transform, SphereCollider, DamageDealer)] #[require(Transform, SphereCollider, DamageDealer, DispawnOnGameOver)]
pub struct SuckArea; pub struct SuckArea;
#[derive(Component)] #[derive(Component)]
#[require(Sprite, PhysicsBody, SphereCollider, AnimationIndices, Life)] #[require(Sprite, PhysicsBody, SphereCollider, AnimationIndices, Life, DispawnOnGameOver)]
pub struct Kirby { pub struct Kirby {
pub speed_force: f32, pub speed_force: f32,
pub is_sucking: bool, pub is_sucking: bool,
@ -26,6 +29,7 @@ pub fn kirby_spawn_start(
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>, mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) { ) {
println!("Start Kirby");
kirby_spawn(&mut commands, &asset_server, &mut texture_atlas_layouts, Vec3::ZERO); kirby_spawn(&mut commands, &asset_server, &mut texture_atlas_layouts, Vec3::ZERO);
// kirby_spawn(&mut commands, &asset_server, &mut texture_atlas_layouts, Vec3 { x: 500.0, y: 0.0, z: 0.0 }); // kirby_spawn(&mut commands, &asset_server, &mut texture_atlas_layouts, Vec3 { x: 500.0, y: 0.0, z: 0.0 });
} }
@ -52,7 +56,7 @@ pub fn kirby_spawn(
SphereCollider::new(20.0), SphereCollider::new(20.0),
animation_indices, animation_indices,
AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)), AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
Life::new(1000.0), Life::new(10.0),
Transform::from_translation(pos).with_scale(Vec3::splat(KIRBY_SCALE)), Transform::from_translation(pos).with_scale(Vec3::splat(KIRBY_SCALE)),
)) ))
.id(); .id();
@ -119,6 +123,7 @@ pub struct KirbyPlugin;
impl Plugin for KirbyPlugin { impl Plugin for KirbyPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, kirby_spawn_start).add_systems(FixedUpdate, kirby_actions); app.add_systems(OnEnter(GameState::Starting), kirby_spawn_start.in_set(GameStartupSet))
.add_systems(FixedUpdate, kirby_actions);
} }
} }

View file

@ -1,13 +1,17 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::{ use crate::{
core::bubble::Bubble, core::{
core::counter::{BubbleExplodedCountThisFrame, BubbleSuckedCountThisFrame, KirbyHitCountThisFrame}, bubble::Bubble,
core::kirby::{Kirby, SuckArea}, counter::{BubbleExplodedCountThisFrame, BubbleSuckedCountThisFrame, KirbyHitCountThisFrame},
game_state::DispawnOnGameOver,
kirby::{Kirby, SuckArea},
},
physics::sphere_collider::SphereCollider, physics::sphere_collider::SphereCollider,
}; };
#[derive(Component)] #[derive(Component)]
#[require(DispawnOnGameOver)]
pub struct Life { pub struct Life {
max_life: f32, max_life: f32,
current_life: f32, current_life: f32,
@ -42,7 +46,7 @@ pub fn despawn_if_dead(mut commands: Commands, query: Query<(Entity, &Life)>) {
} }
#[derive(Component, Default)] #[derive(Component, Default)]
#[require(SphereCollider)] #[require(SphereCollider, DispawnOnGameOver)]
pub struct DamageDealer { pub struct DamageDealer {
damage_strength: f32, damage_strength: f32,
} }

View file

@ -1,6 +1,7 @@
pub mod bubble; pub mod bubble;
pub mod collectible; pub mod collectible;
pub mod counter; pub mod counter;
pub mod game_state;
pub mod kirby; pub mod kirby;
pub mod life; pub mod life;
pub mod wave; pub mod wave;

View file

@ -4,7 +4,12 @@ use bevy::prelude::*;
use rand::distr::slice::Empty; use rand::distr::slice::Empty;
use crate::{ use crate::{
core::bubble::{Bubble, bubble_spawn_wave}, core::{
bubble::{Bubble, bubble_spawn_wave},
game_state::Resetable,
kirby::Kirby,
life::Life,
},
map::map::{BOTTOM, LEFT, RIGHT, TOP}, map::map::{BOTTOM, LEFT, RIGHT, TOP},
}; };
@ -50,7 +55,7 @@ const GREEN_BUBBLE: BubbleType = BubbleType {
#[derive(Resource)] #[derive(Resource)]
pub struct BubbleWaves { pub struct BubbleWaves {
pub waves: Vec<Vec<BubbleSplash>>, waves: Vec<Vec<BubbleSplash>>,
pub current_wave: usize, pub current_wave: usize,
} }
@ -75,6 +80,12 @@ fn get_bubble_waves() -> BubbleWaves {
} }
} }
impl Resetable for BubbleWaves {
fn reset(&mut self) {
*self = get_bubble_waves();
}
}
fn no_ennemy_left(bubble_query: Query<(), With<Bubble>>) -> bool { fn no_ennemy_left(bubble_query: Query<(), With<Bubble>>) -> bool {
bubble_query.is_empty() bubble_query.is_empty()
} }
@ -88,10 +99,16 @@ fn change_wave(mut bubble_wave: ResMut<BubbleWaves>, mut commands: Commands, ass
bubble_spawn_wave(&mut commands, &asset_server, bubble_wave.waves[bubble_wave.current_wave].as_slice()); bubble_spawn_wave(&mut commands, &asset_server, bubble_wave.waves[bubble_wave.current_wave].as_slice());
} }
fn heal_kirby(query: Query<&mut Life, With<Kirby>>) {
for mut kirby_life in query {
kirby_life.heal(f32::MAX);
}
}
pub struct WavePlugin; pub struct WavePlugin;
impl Plugin for WavePlugin { impl Plugin for WavePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(get_bubble_waves()).add_systems(Update, change_wave.run_if(no_ennemy_left)); app.insert_resource(get_bubble_waves()).add_systems(Update, (change_wave, heal_kirby).run_if(no_ennemy_left));
} }
} }

View file

@ -1,7 +1,8 @@
use crate::core::game_state::DispawnOnGameOver;
use bevy::prelude::*; use bevy::prelude::*;
// Source : https://bevy.org/examples/2d-rendering/sprite-sheet/
#[derive(Component, Default)] #[derive(Component, Default)]
#[require(DispawnOnGameOver)]
pub struct AnimationIndices { pub struct AnimationIndices {
pub first: usize, pub first: usize,
pub last: usize, pub last: usize,
@ -15,6 +16,7 @@ impl AnimationIndices {
} }
#[derive(Component, Deref, DerefMut)] #[derive(Component, Deref, DerefMut)]
#[require(DispawnOnGameOver)]
pub struct AnimationTimer(pub Timer); pub struct AnimationTimer(pub Timer);
pub fn animate_sprite(time: Res<Time>, mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut Sprite)>) { pub fn animate_sprite(time: Res<Time>, mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut Sprite)>) {

View file

@ -21,6 +21,7 @@ mod juice;
use juice::animation::animate_sprite; use juice::animation::animate_sprite;
use juice::camera::MyCameraPlugin; use juice::camera::MyCameraPlugin;
use crate::core::game_state::GameEventsPlugin;
use crate::core::wave::WavePlugin; use crate::core::wave::WavePlugin;
fn main() { fn main() {
@ -34,6 +35,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugins(GameEventsPlugin)
.add_plugins(CounterPlugin) .add_plugins(CounterPlugin)
.add_plugins(KirbyPlugin) .add_plugins(KirbyPlugin)
.add_plugins(DensityGridPlugin) .add_plugins(DensityGridPlugin)

View file

@ -4,7 +4,7 @@ use bevy::prelude::*;
use rand::Rng; use rand::Rng;
pub static MAX_MAP_WIDTH: usize = 1024; pub static MAX_MAP_WIDTH: usize = 1024;
use crate::physics::physics_body::PhysicsBody; use crate::{core::game_state::GameState, physics::physics_body::PhysicsBody};
const CELL_SIZE: usize = 8; const CELL_SIZE: usize = 8;
const MAX_MAP_SIZE: usize = MAX_MAP_WIDTH * 2; const MAX_MAP_SIZE: usize = MAX_MAP_WIDTH * 2;
@ -78,11 +78,9 @@ pub fn density_grid_force(
let index = get_index_from_uvec2(neighbor_cell); let index = get_index_from_uvec2(neighbor_cell);
let density = density_grid.grid[index]; let density = density_grid.grid[index];
// println!("offset : {:?} density: {:?}", offset.as_vec2(), density);
density_gradient += offset.as_vec2() * density as f32; density_gradient += offset.as_vec2() * density as f32;
} }
} }
// println!("density : {:?}", density_gradient);
density_gradient += density_obj.random_offset; // To not have a "grid" thingy density_gradient += density_obj.random_offset; // To not have a "grid" thingy
physics_body.force -= density_gradient * DENSITY_FORCE_SCALE; physics_body.force -= density_gradient * DENSITY_FORCE_SCALE;
} }
@ -93,7 +91,7 @@ pub struct DensityGridPlugin;
impl Plugin for DensityGridPlugin { impl Plugin for DensityGridPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(DensityGrid::new()) app.insert_resource(DensityGrid::new())
.add_systems(PostStartup, init_density_object) .add_systems(OnExit(GameState::Starting), init_density_object)
.add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain()); .add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain());
} }
} }

View file

@ -1,9 +1,10 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::core::game_state::DispawnOnGameOver;
use crate::map::map::MapBounds; use crate::map::map::MapBounds;
#[derive(Component)] #[derive(Component)]
#[require(Transform)] #[require(Transform, DispawnOnGameOver)]
pub struct PhysicsBody { pub struct PhysicsBody {
pub mass: f32, pub mass: f32,
pub force: Vec2, pub force: Vec2,