From 3dc47f958d6437e82035c0c0814f12d8e4186d6a Mon Sep 17 00:00:00 2001 From: Crizomb Date: Mon, 29 Dec 2025 23:20:32 +0100 Subject: [PATCH] kirby can suck bubbles --- rustfmt.toml | 3 +- src/bubble.rs | 30 +++++++------ src/density_grid.rs | 4 +- src/kirby.rs | 72 +++++++++++++++++++------------ src/life.rs | 96 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 18 ++++---- src/map.rs | 12 +----- src/physics_body.rs | 16 ++----- src/sphere_collider.rs | 7 ++- 9 files changed, 182 insertions(+), 76 deletions(-) create mode 100644 src/life.rs diff --git a/rustfmt.toml b/rustfmt.toml index dcfbf66..4488439 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,4 +2,5 @@ max_width = 120 chain_width = 120 fn_call_width = 120 use_small_heuristics = "Max" - +imports_granularity = "Crate" +group_imports = "StdExternalCrate" diff --git a/src/bubble.rs b/src/bubble.rs index bfe7cc3..4f1d05c 100644 --- a/src/bubble.rs +++ b/src/bubble.rs @@ -1,13 +1,17 @@ -use crate::density_grid::DensityObject; -use crate::kirby::Kirby; -use crate::physics_body::PhysicsBody; +use std::{f32::consts::TAU, fmt::Debug}; + use bevy::prelude::*; use rand::Rng; -use std::f32::consts::TAU; -use std::fmt::Debug; + +use crate::{ + density_grid::DensityObject, + kirby::Kirby, + life::{DamageDealer, Life}, + physics_body::PhysicsBody, +}; #[derive(Component)] -#[require(Sprite, PhysicsBody, DensityObject)] +#[require(Sprite, PhysicsBody, DensityObject, Life, DamageDealer)] pub struct Bubble { move_force: f32, } @@ -16,7 +20,7 @@ pub fn bubble_spawn(mut commands: Commands, asset_server: Res) { let texture: Handle = asset_server.load("sprites/bubble.png"); let mut rng = rand::rng(); - for _ in 0..1000 { + for _ in 0..500 { let angle = rng.random_range(0.0..TAU); let pos = Vec2::from_angle(angle) * 500.0; @@ -25,27 +29,29 @@ pub fn bubble_spawn(mut commands: Commands, asset_server: Res) { Sprite { image: texture.clone(), ..default() }, Transform::from_translation(pos.extend(0.0)).with_scale(Vec3::splat(1.0)), PhysicsBody { mass: 10.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.05 }, + Life::new(10.0), + DamageDealer::new(0.5), )); } } pub fn bubble_move( - mut lana_query: Query<(&mut PhysicsBody, &Transform, &Bubble), With>, + mut bubble_query: Query<(&mut PhysicsBody, &Transform, &Bubble), With>, kirby_query: Query<(&Transform, &Kirby), With>, ) { - for (mut lana_body, lana_transform, lana) in &mut lana_query { + for (mut bubble_body, bubble_transform, bubble) in &mut bubble_query { let mut nearest_kirby_pos = Vec3::ZERO; let mut nearest_squared = f32::MAX; for (kirby_transform, _kirby) in kirby_query { - let dist_squared = kirby_transform.translation.distance_squared(lana_transform.translation); + let dist_squared = kirby_transform.translation.distance_squared(bubble_transform.translation); if (dist_squared) < nearest_squared { nearest_squared = dist_squared; nearest_kirby_pos = kirby_transform.translation; } } - let dir = (nearest_kirby_pos - lana_transform.translation).normalize().xy(); - lana_body.force += dir * lana.move_force; + let dir = (nearest_kirby_pos - bubble_transform.translation).normalize().xy(); + bubble_body.force += dir * bubble.move_force; } } diff --git a/src/density_grid.rs b/src/density_grid.rs index d6ff771..4b3c8df 100644 --- a/src/density_grid.rs +++ b/src/density_grid.rs @@ -1,10 +1,10 @@ use std::f32::consts::TAU; -use crate::globals::MAX_MAP_WIDTH; -use crate::physics_body::PhysicsBody; use bevy::prelude::*; use rand::Rng; +use crate::{globals::MAX_MAP_WIDTH, physics_body::PhysicsBody}; + const CELL_SIZE: usize = 8; const MAX_MAP_SIZE: usize = MAX_MAP_WIDTH * 2; const ARRAY_WIDTH: usize = MAX_MAP_SIZE / CELL_SIZE; diff --git a/src/kirby.rs b/src/kirby.rs index b2676c7..0a443b1 100644 --- a/src/kirby.rs +++ b/src/kirby.rs @@ -1,13 +1,23 @@ -use crate::animation::{AnimationIndices, AnimationTimer}; -use crate::physics_body::PhysicsBody; -use crate::sphere_collider::SphereCollider; use bevy::prelude::*; +const KIRBY_SCALE: f32 = 3.0; + +use crate::{ + animation::{AnimationIndices, AnimationTimer}, + life::{DamageDealer, Life}, + physics_body::PhysicsBody, + sphere_collider::SphereCollider, +}; + #[derive(Component)] -#[require(Sprite, PhysicsBody, SphereCollider, AnimationIndices)] +#[require(Transform, SphereCollider, DamageDealer)] +pub struct SuckArea; + +#[derive(Component)] +#[require(Sprite, PhysicsBody, SphereCollider, AnimationIndices, Life)] pub struct Kirby { speed_force: f32, - sucking: bool, + pub is_sucking: bool, } pub fn kirby_spawn( @@ -21,17 +31,29 @@ pub fn kirby_spawn( let texture_atlas_layout = texture_atlas_layouts.add(layout); let animation_indices = AnimationIndices { first: 3, last: 5 }; // idle - commands.spawn(( - Kirby { speed_force: 10000.0, sucking: false }, - Sprite::from_atlas_image( - texture, - TextureAtlas { layout: texture_atlas_layout, index: animation_indices.first }, - ), - Transform::from_scale(Vec3::splat(3.0)), - animation_indices, - AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)), - body, - )); + let kirby_entity = commands + .spawn(( + Kirby { speed_force: 10000.0, is_sucking: false }, + Sprite::from_atlas_image( + texture, + TextureAtlas { layout: texture_atlas_layout, index: animation_indices.first }, + ), + Transform::from_scale(Vec3::splat(KIRBY_SCALE)), + animation_indices, + AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)), + body, + SphereCollider::new(50.0), + )) + .id(); + + commands.entity(kirby_entity).with_children(|parent| { + parent.spawn(( + Transform::from_xyz(40.0, 0.0, 0.0), + SuckArea, + SphereCollider::new(80.0), + DamageDealer::new(10000.0), + )); + }); } pub fn get_dir(keys: Res>) -> Vec2 { @@ -55,26 +77,26 @@ pub fn get_dir(keys: Res>) -> Vec2 { pub fn kirby_actions( keys: Res>, - mut query: Query<(&mut PhysicsBody, &mut Kirby, &mut AnimationIndices, &mut Transform, &mut Sprite), With>, + mut query: Query<(&mut PhysicsBody, &mut Kirby, &mut AnimationIndices, &mut Transform), With>, ) { let space_just_pressed = keys.just_pressed(KeyCode::Space); let space_just_released = keys.just_released(KeyCode::Space); let dir = get_dir(keys); - for (mut body, mut kirby, mut anim_indices, mut transform, mut sprite) in &mut query { + for (mut body, mut kirby, mut anim_indices, mut transform) in &mut query { if space_just_released { - kirby.sucking = false + kirby.is_sucking = false }; if space_just_pressed { - kirby.sucking = true; + kirby.is_sucking = true; anim_indices.change(0, 2); }; - if kirby.sucking { + if kirby.is_sucking { continue; }; if dir.x != 0.0 { - sprite.flip_x = dir.x < 0.0; + transform.scale.x = if dir.x < 0.0 { -KIRBY_SCALE } else { KIRBY_SCALE }; } body.force += dir * kirby.speed_force; if dir == Vec2::ZERO { @@ -84,9 +106,3 @@ pub fn kirby_actions( } } } - -#[derive(Component)] -#[require(Sprite, Transform)] -pub struct KirbySuction { - box_size: Vec2, -} diff --git a/src/life.rs b/src/life.rs new file mode 100644 index 0000000..5ea0248 --- /dev/null +++ b/src/life.rs @@ -0,0 +1,96 @@ +use bevy::prelude::*; + +use crate::{ + bubble::Bubble, + kirby::{Kirby, SuckArea}, + sphere_collider::SphereCollider, +}; + +#[derive(Component)] +pub struct Life { + max_life: f32, + current_life: f32, +} + +impl Life { + pub fn new(max_life: f32) -> Self { + Self { max_life, current_life: max_life } + } + + pub fn damage(&mut self, amount: f32) { + self.current_life -= amount; + } + + pub fn heal(&mut self, amount: f32) { + self.current_life = (self.current_life + amount).min(self.max_life); + } +} + +impl Default for Life { + fn default() -> Self { + Life::new(100.0) + } +} + +pub fn despawn_if_dead(mut commands: Commands, query: Query<(Entity, &Life)>) { + for (entity, life) in query.iter() { + if life.current_life < 0.0 { + commands.entity(entity).despawn(); + } + } +} + +#[derive(Component, Default)] +#[require(SphereCollider)] +pub struct DamageDealer { + damage_strength: f32, +} + +impl DamageDealer { + pub fn new(damage: f32) -> Self { + DamageDealer { damage_strength: damage } + } +} + +// Considerating ennemies are ponctual +pub fn bubble_receive_damage( + time: Res>, + + mut enemy_query: Query<(&GlobalTransform, &mut Life), With>, + + kirby_query: Query<(&Kirby, &Children)>, + suck_area_query: Query<(&GlobalTransform, &SphereCollider, &DamageDealer), With>, +) { + for (kirby, children) in &kirby_query { + if !kirby.is_sucking { + continue; + } + + for child in children.iter() { + let Ok((suck_transform, suck_collider, suck_damage)) = suck_area_query.get(child) else { + continue; + }; + + for (enemy_transform, mut enemy_life) in &mut enemy_query { + if suck_collider.point_inside(suck_transform.translation(), enemy_transform.translation()) { + enemy_life.damage(suck_damage.damage_strength * time.delta_secs()); + } + } + } + } +} + +// Considerating allies are ponctual +pub fn kirby_receive_damage( + mut ally_query: Query<(&GlobalTransform, &mut Life), With>, + mut enemy_query: Query<(&GlobalTransform, &SphereCollider, &DamageDealer, &mut Life), With>, +) { + for (ally_transform, mut ally_life) in &mut ally_query { + for (enemy_transform, enemy_sphere_collider, enemy_damage_dealer, mut bubble_life) in &mut enemy_query { + if enemy_sphere_collider.point_inside(ally_transform.translation(), enemy_transform.translation()) { + ally_life.damage(enemy_damage_dealer.damage_strength); + bubble_life.damage(1000000.0); // Bubble explode + } + } + } +} diff --git a/src/main.rs b/src/main.rs index a459d2c..f70878d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,22 @@ use animation::animate_sprite; use bevy::prelude::*; -use bubble::bubble_move; -use bubble::bubble_spawn; +use bubble::{bubble_move, bubble_spawn}; use camera::spawn_camera; -use density_grid::DensityGrid; -use density_grid::density_grid_force; -use density_grid::density_grid_update; -use density_grid::init_density_object; -use kirby::kirby_actions; -use kirby::kirby_spawn; +use density_grid::{DensityGrid, density_grid_force, density_grid_update, init_density_object}; +use kirby::{kirby_actions, kirby_spawn}; +use life::despawn_if_dead; use map::MapBounds; use physics_body::integrate; +use crate::life::bubble_receive_damage; + mod animation; mod bubble; mod camera; mod density_grid; mod globals; mod kirby; +mod life; mod map; mod physics_body; mod sphere_collider; @@ -25,6 +24,7 @@ mod sphere_collider; fn main() { App::new() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) + .insert_resource(Time::::from_seconds(1.0 / 60.0)) .insert_resource(MapBounds { min: Vec2::ONE * -600.0, max: Vec2::ONE * 600.0 }) .insert_resource(DensityGrid::new()) .add_systems(Startup, spawn_camera) @@ -35,6 +35,8 @@ fn main() { .add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain()) .add_systems(FixedUpdate, kirby_actions) .add_systems(FixedUpdate, bubble_move) + .add_systems(FixedUpdate, bubble_receive_damage) + .add_systems(FixedPostUpdate, despawn_if_dead) .add_systems(FixedPostUpdate, integrate) .run(); } diff --git a/src/map.rs b/src/map.rs index 8fdb0c8..b581c65 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,17 +1,7 @@ -use bevy::{prelude::*, render::renderer}; - -#[derive(Component)] -pub struct MapRoot; +use bevy::prelude::*; #[derive(Resource)] pub struct MapBounds { pub min: Vec2, pub max: Vec2, } - -pub fn setup_map_bounds(mut commands: Commands) { - commands.insert_resource(MapBounds { - min: Vec2::new(-320.0, -240.0), - max: Vec2::new(320.0, 240.0), - }); -} diff --git a/src/physics_body.rs b/src/physics_body.rs index 90b5cbd..d14b757 100644 --- a/src/physics_body.rs +++ b/src/physics_body.rs @@ -1,6 +1,7 @@ -use crate::map::MapBounds; use bevy::prelude::*; +use crate::map::MapBounds; + #[derive(Component)] #[require(Transform)] pub struct PhysicsBody { @@ -12,20 +13,11 @@ pub struct PhysicsBody { impl Default for PhysicsBody { fn default() -> Self { - Self { - mass: 1.0, - force: Vec2::ZERO, - velocity: Vec2::ZERO, - drag: 0.0, - } + Self { mass: 1.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.0 } } } -pub fn integrate( - bounds: Res, - mut query: Query<(&mut Transform, &mut PhysicsBody)>, - time: Res>, -) { +pub fn integrate(bounds: Res, mut query: Query<(&mut Transform, &mut PhysicsBody)>, time: Res>) { for (mut transform, mut physicsbody) in &mut query { let force = physicsbody.force; let mass = physicsbody.mass; diff --git a/src/sphere_collider.rs b/src/sphere_collider.rs index 770ea63..ffd056b 100644 --- a/src/sphere_collider.rs +++ b/src/sphere_collider.rs @@ -7,10 +7,13 @@ pub struct SphereCollider { } impl SphereCollider { - fn point_inside(&self, self_pos: Vec3, point: Vec3) -> bool { + pub fn new(radius: f32) -> Self { + SphereCollider { radius: radius } + } + pub fn point_inside(&self, self_pos: Vec3, point: Vec3) -> bool { (self_pos - point).length_squared() < self.radius * self.radius } - fn collides(&self, self_pos: Vec3, other: &SphereCollider, other_pos: Vec3) -> bool { + pub fn collides(&self, self_pos: Vec3, other: &SphereCollider, other_pos: Vec3) -> bool { let radius_sum = self.radius + other.radius; (self_pos - other_pos).length_squared() < radius_sum * radius_sum }