kirby can suck bubbles
All checks were successful
Build Bevy Game (Linux) / build (push) Successful in 19m20s

This commit is contained in:
Crizomb 2025-12-29 23:20:32 +01:00
parent 0eb55dbe58
commit 3dc47f958d
9 changed files with 182 additions and 76 deletions

View file

@ -2,4 +2,5 @@ max_width = 120
chain_width = 120 chain_width = 120
fn_call_width = 120 fn_call_width = 120
use_small_heuristics = "Max" use_small_heuristics = "Max"
imports_granularity = "Crate"
group_imports = "StdExternalCrate"

View file

@ -1,13 +1,17 @@
use crate::density_grid::DensityObject; use std::{f32::consts::TAU, fmt::Debug};
use crate::kirby::Kirby;
use crate::physics_body::PhysicsBody;
use bevy::prelude::*; use bevy::prelude::*;
use rand::Rng; 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)] #[derive(Component)]
#[require(Sprite, PhysicsBody, DensityObject)] #[require(Sprite, PhysicsBody, DensityObject, Life, DamageDealer)]
pub struct Bubble { pub struct Bubble {
move_force: f32, move_force: f32,
} }
@ -16,7 +20,7 @@ pub fn bubble_spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
let texture: Handle<Image> = asset_server.load("sprites/bubble.png"); let texture: Handle<Image> = asset_server.load("sprites/bubble.png");
let mut rng = rand::rng(); let mut rng = rand::rng();
for _ in 0..1000 { for _ in 0..500 {
let angle = rng.random_range(0.0..TAU); let angle = rng.random_range(0.0..TAU);
let pos = Vec2::from_angle(angle) * 500.0; let pos = Vec2::from_angle(angle) * 500.0;
@ -25,27 +29,29 @@ pub fn bubble_spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
Sprite { image: texture.clone(), ..default() }, Sprite { image: texture.clone(), ..default() },
Transform::from_translation(pos.extend(0.0)).with_scale(Vec3::splat(1.0)), 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 }, 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( pub fn bubble_move(
mut lana_query: Query<(&mut PhysicsBody, &Transform, &Bubble), With<Bubble>>, mut bubble_query: Query<(&mut PhysicsBody, &Transform, &Bubble), With<Bubble>>,
kirby_query: Query<(&Transform, &Kirby), With<Kirby>>, kirby_query: Query<(&Transform, &Kirby), With<Kirby>>,
) { ) {
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_kirby_pos = Vec3::ZERO;
let mut nearest_squared = f32::MAX; let mut nearest_squared = f32::MAX;
for (kirby_transform, _kirby) in kirby_query { 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 { if (dist_squared) < nearest_squared {
nearest_squared = dist_squared; nearest_squared = dist_squared;
nearest_kirby_pos = kirby_transform.translation; nearest_kirby_pos = kirby_transform.translation;
} }
} }
let dir = (nearest_kirby_pos - lana_transform.translation).normalize().xy(); let dir = (nearest_kirby_pos - bubble_transform.translation).normalize().xy();
lana_body.force += dir * lana.move_force; bubble_body.force += dir * bubble.move_force;
} }
} }

View file

@ -1,10 +1,10 @@
use std::f32::consts::TAU; use std::f32::consts::TAU;
use crate::globals::MAX_MAP_WIDTH;
use crate::physics_body::PhysicsBody;
use bevy::prelude::*; use bevy::prelude::*;
use rand::Rng; use rand::Rng;
use crate::{globals::MAX_MAP_WIDTH, 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;
const ARRAY_WIDTH: usize = MAX_MAP_SIZE / CELL_SIZE; const ARRAY_WIDTH: usize = MAX_MAP_SIZE / CELL_SIZE;

View file

@ -1,13 +1,23 @@
use crate::animation::{AnimationIndices, AnimationTimer};
use crate::physics_body::PhysicsBody;
use crate::sphere_collider::SphereCollider;
use bevy::prelude::*; 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)] #[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 { pub struct Kirby {
speed_force: f32, speed_force: f32,
sucking: bool, pub is_sucking: bool,
} }
pub fn kirby_spawn( pub fn kirby_spawn(
@ -21,17 +31,29 @@ pub fn kirby_spawn(
let texture_atlas_layout = texture_atlas_layouts.add(layout); let texture_atlas_layout = texture_atlas_layouts.add(layout);
let animation_indices = AnimationIndices { first: 3, last: 5 }; // idle let animation_indices = AnimationIndices { first: 3, last: 5 }; // idle
commands.spawn(( let kirby_entity = commands
Kirby { speed_force: 10000.0, sucking: false }, .spawn((
Sprite::from_atlas_image( Kirby { speed_force: 10000.0, is_sucking: false },
texture, Sprite::from_atlas_image(
TextureAtlas { layout: texture_atlas_layout, index: animation_indices.first }, texture,
), TextureAtlas { layout: texture_atlas_layout, index: animation_indices.first },
Transform::from_scale(Vec3::splat(3.0)), ),
animation_indices, Transform::from_scale(Vec3::splat(KIRBY_SCALE)),
AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)), animation_indices,
body, 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<ButtonInput<KeyCode>>) -> Vec2 { pub fn get_dir(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
@ -55,26 +77,26 @@ pub fn get_dir(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
pub fn kirby_actions( pub fn kirby_actions(
keys: Res<ButtonInput<KeyCode>>, keys: Res<ButtonInput<KeyCode>>,
mut query: Query<(&mut PhysicsBody, &mut Kirby, &mut AnimationIndices, &mut Transform, &mut Sprite), With<Kirby>>, mut query: Query<(&mut PhysicsBody, &mut Kirby, &mut AnimationIndices, &mut Transform), With<Kirby>>,
) { ) {
let space_just_pressed = keys.just_pressed(KeyCode::Space); let space_just_pressed = keys.just_pressed(KeyCode::Space);
let space_just_released = keys.just_released(KeyCode::Space); let space_just_released = keys.just_released(KeyCode::Space);
let dir = get_dir(keys); 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 { if space_just_released {
kirby.sucking = false kirby.is_sucking = false
}; };
if space_just_pressed { if space_just_pressed {
kirby.sucking = true; kirby.is_sucking = true;
anim_indices.change(0, 2); anim_indices.change(0, 2);
}; };
if kirby.sucking { if kirby.is_sucking {
continue; continue;
}; };
if dir.x != 0.0 { 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; body.force += dir * kirby.speed_force;
if dir == Vec2::ZERO { if dir == Vec2::ZERO {
@ -84,9 +106,3 @@ pub fn kirby_actions(
} }
} }
} }
#[derive(Component)]
#[require(Sprite, Transform)]
pub struct KirbySuction {
box_size: Vec2,
}

96
src/life.rs Normal file
View file

@ -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<Time<Fixed>>,
mut enemy_query: Query<(&GlobalTransform, &mut Life), With<Bubble>>,
kirby_query: Query<(&Kirby, &Children)>,
suck_area_query: Query<(&GlobalTransform, &SphereCollider, &DamageDealer), With<SuckArea>>,
) {
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<Kirby>>,
mut enemy_query: Query<(&GlobalTransform, &SphereCollider, &DamageDealer, &mut Life), With<Bubble>>,
) {
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
}
}
}
}

View file

@ -1,23 +1,22 @@
use animation::animate_sprite; use animation::animate_sprite;
use bevy::prelude::*; use bevy::prelude::*;
use bubble::bubble_move; use bubble::{bubble_move, bubble_spawn};
use bubble::bubble_spawn;
use camera::spawn_camera; use camera::spawn_camera;
use density_grid::DensityGrid; use density_grid::{DensityGrid, density_grid_force, density_grid_update, init_density_object};
use density_grid::density_grid_force; use kirby::{kirby_actions, kirby_spawn};
use density_grid::density_grid_update; use life::despawn_if_dead;
use density_grid::init_density_object;
use kirby::kirby_actions;
use kirby::kirby_spawn;
use map::MapBounds; use map::MapBounds;
use physics_body::integrate; use physics_body::integrate;
use crate::life::bubble_receive_damage;
mod animation; mod animation;
mod bubble; mod bubble;
mod camera; mod camera;
mod density_grid; mod density_grid;
mod globals; mod globals;
mod kirby; mod kirby;
mod life;
mod map; mod map;
mod physics_body; mod physics_body;
mod sphere_collider; mod sphere_collider;
@ -25,6 +24,7 @@ mod sphere_collider;
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.insert_resource(Time::<Fixed>::from_seconds(1.0 / 60.0))
.insert_resource(MapBounds { min: Vec2::ONE * -600.0, max: Vec2::ONE * 600.0 }) .insert_resource(MapBounds { min: Vec2::ONE * -600.0, max: Vec2::ONE * 600.0 })
.insert_resource(DensityGrid::new()) .insert_resource(DensityGrid::new())
.add_systems(Startup, spawn_camera) .add_systems(Startup, spawn_camera)
@ -35,6 +35,8 @@ fn main() {
.add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain()) .add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain())
.add_systems(FixedUpdate, kirby_actions) .add_systems(FixedUpdate, kirby_actions)
.add_systems(FixedUpdate, bubble_move) .add_systems(FixedUpdate, bubble_move)
.add_systems(FixedUpdate, bubble_receive_damage)
.add_systems(FixedPostUpdate, despawn_if_dead)
.add_systems(FixedPostUpdate, integrate) .add_systems(FixedPostUpdate, integrate)
.run(); .run();
} }

View file

@ -1,17 +1,7 @@
use bevy::{prelude::*, render::renderer}; use bevy::prelude::*;
#[derive(Component)]
pub struct MapRoot;
#[derive(Resource)] #[derive(Resource)]
pub struct MapBounds { pub struct MapBounds {
pub min: Vec2, pub min: Vec2,
pub max: 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),
});
}

View file

@ -1,6 +1,7 @@
use crate::map::MapBounds;
use bevy::prelude::*; use bevy::prelude::*;
use crate::map::MapBounds;
#[derive(Component)] #[derive(Component)]
#[require(Transform)] #[require(Transform)]
pub struct PhysicsBody { pub struct PhysicsBody {
@ -12,20 +13,11 @@ pub struct PhysicsBody {
impl Default for PhysicsBody { impl Default for PhysicsBody {
fn default() -> Self { fn default() -> Self {
Self { Self { mass: 1.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.0 }
mass: 1.0,
force: Vec2::ZERO,
velocity: Vec2::ZERO,
drag: 0.0,
}
} }
} }
pub fn integrate( pub fn integrate(bounds: Res<MapBounds>, mut query: Query<(&mut Transform, &mut PhysicsBody)>, time: Res<Time<Fixed>>) {
bounds: Res<MapBounds>,
mut query: Query<(&mut Transform, &mut PhysicsBody)>,
time: Res<Time<Fixed>>,
) {
for (mut transform, mut physicsbody) in &mut query { for (mut transform, mut physicsbody) in &mut query {
let force = physicsbody.force; let force = physicsbody.force;
let mass = physicsbody.mass; let mass = physicsbody.mass;

View file

@ -7,10 +7,13 @@ pub struct SphereCollider {
} }
impl 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 (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; let radius_sum = self.radius + other.radius;
(self_pos - other_pos).length_squared() < radius_sum * radius_sum (self_pos - other_pos).length_squared() < radius_sum * radius_sum
} }