Compare commits

...

3 commits

Author SHA1 Message Date
0eb55dbe58 kirby animation state
All checks were successful
Build Bevy Game (Linux) / build (push) Successful in 19m17s
2025-12-28 21:55:32 +01:00
b3e1359c63 sprite sheet 2025-12-28 21:07:03 +01:00
ad6eaa7d79 small density adjusts 2025-12-28 03:08:48 +01:00
11 changed files with 154 additions and 52 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/sprites/bubble.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

BIN
assets/sprites/shield.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

BIN
assets/sprites/speed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

67
src/animation.rs Normal file
View file

@ -0,0 +1,67 @@
use bevy::prelude::*;
// Source : https://bevy.org/examples/2d-rendering/sprite-sheet/
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites
.add_systems(Startup, setup)
.add_systems(Update, animate_sprite)
.run();
}
#[derive(Component, Default)]
pub struct AnimationIndices {
pub first: usize,
pub last: usize,
}
impl AnimationIndices {
pub fn change(&mut self, first: usize, second: usize) {
self.first = first;
self.last = second;
}
}
#[derive(Component, Deref, DerefMut)]
pub struct AnimationTimer(pub Timer);
pub fn animate_sprite(time: Res<Time>, mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut Sprite)>) {
for (indices, mut timer, mut sprite) in &mut query {
timer.tick(time.delta());
if timer.just_finished()
&& let Some(atlas) = &mut sprite.texture_atlas
{
atlas.index = if atlas.index >= indices.last || atlas.index < indices.first {
indices.first
} else {
atlas.index + 1
};
}
}
}
#[allow(dead_code)]
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
let texture = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let layout = TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None);
let texture_atlas_layout = texture_atlas_layouts.add(layout);
// Use only the subset of sprites in the sheet that make up the run animation
let animation_indices = AnimationIndices { first: 1, last: 6 };
commands.spawn(Camera2d);
commands.spawn((
Sprite::from_atlas_image(
texture,
TextureAtlas { layout: texture_atlas_layout, index: animation_indices.first },
),
Transform::from_scale(Vec3::splat(6.0)),
animation_indices,
AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
));
}

View file

@ -1,7 +0,0 @@
use bevy::{prelude::*, render::renderer};
#[derive(Component, Default)]
#[require(Transform)]
pub struct BoxCollider {
pub dimensions: Vec2,
}

View file

@ -1,36 +1,36 @@
use crate::density_grid::DensityObject; use crate::density_grid::DensityObject;
use crate::kirby::Kirby; use crate::kirby::Kirby;
use crate::physics_body::PhysicsBody; use crate::physics_body::PhysicsBody;
use crate::sphere_collider::SphereCollider;
use bevy::prelude::*; use bevy::prelude::*;
use rand::Rng; use rand::Rng;
use std::f32::consts::TAU; use std::f32::consts::TAU;
use std::fmt::Debug;
#[derive(Component)] #[derive(Component)]
#[require(Sprite, PhysicsBody, SphereCollider, DensityObject)] #[require(Sprite, PhysicsBody, DensityObject)]
pub struct Lana { pub struct Bubble {
move_force: f32, move_force: f32,
} }
pub fn lana_spawn(mut commands: Commands, asset_server: Res<AssetServer>) { pub fn bubble_spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
let texture: Handle<Image> = asset_server.load("sprites/lana.png"); let texture: Handle<Image> = asset_server.load("sprites/bubble.png");
let mut rng = rand::rng(); let mut rng = rand::rng();
for _ in 0..1_000 { for _ in 0..1000 {
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;
commands.spawn(( commands.spawn((
Lana { move_force: 3000.0 }, Bubble { move_force: 3000.0 },
Sprite { image: texture.clone(), ..default() }, Sprite { image: texture.clone(), ..default() },
Transform::from_translation(pos.extend(0.0)).with_scale(Vec3::splat(0.02)), 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 },
)); ));
} }
} }
pub fn lana_move( pub fn bubble_move(
mut lana_query: Query<(&mut PhysicsBody, &Transform, &Lana), With<Lana>>, mut lana_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 lana_body, lana_transform, lana) in &mut lana_query {
@ -49,9 +49,3 @@ pub fn lana_move(
lana_body.force += dir * lana.move_force; lana_body.force += dir * lana.move_force;
} }
} }
#[derive(Component)]
#[require(Sprite, Transform)]
pub struct KirbySuction {
box_size: Vec2,
}

View file

@ -1,13 +1,16 @@
use std::f32::consts::TAU;
use crate::globals::MAX_MAP_WIDTH; use crate::globals::MAX_MAP_WIDTH;
use crate::physics_body::PhysicsBody; use crate::physics_body::PhysicsBody;
use bevy::prelude::*; use bevy::prelude::*;
use rand::Rng;
const CELL_SIZE: usize = 32; 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;
const GRID_ARRAY_SIZE: usize = ARRAY_WIDTH * ARRAY_WIDTH; const GRID_ARRAY_SIZE: usize = ARRAY_WIDTH * ARRAY_WIDTH;
const DENSITY_FORCE_SCALE: f32 = 100.0; const DENSITY_FORCE_SCALE: f32 = 6000.0;
#[derive(Resource)] #[derive(Resource)]
pub struct DensityGrid { pub struct DensityGrid {
@ -34,12 +37,16 @@ fn get_index_from_uvec2(grid_cell: UVec2) -> usize {
} }
#[derive(Component, Default)] #[derive(Component, Default)]
pub struct DensityObject {} pub struct DensityObject {
random_offset: Vec2,
}
pub fn init_density_object(mut density_grid: ResMut<DensityGrid>, mut query: Query<&Transform>) { pub fn init_density_object(mut density_grid: ResMut<DensityGrid>, mut query: Query<(&Transform, &mut DensityObject)>) {
for transform in &mut query { let mut rng = rand::rng();
for (transform, mut density_obj) in &mut query {
let grid_cell = pos_to_grid_cell(transform.translation.xy()); let grid_cell = pos_to_grid_cell(transform.translation.xy());
// println!("grid cell {:?}", grid_cell); // println!("grid cell {:?}", grid_cell);
density_obj.random_offset = Vec2::from_angle(rng.random_range(0.0..TAU)) * 0.2;
let index = get_index_from_uvec2(grid_cell); let index = get_index_from_uvec2(grid_cell);
density_grid.grid[index] += 1; density_grid.grid[index] += 1;
} }
@ -57,9 +64,9 @@ pub fn density_grid_update(mut density_grid: ResMut<DensityGrid>, query: Query<&
pub fn density_grid_force( pub fn density_grid_force(
density_grid: Res<DensityGrid>, density_grid: Res<DensityGrid>,
mut query: Query<(&mut PhysicsBody, &Transform), With<DensityObject>>, mut query: Query<(&mut PhysicsBody, &Transform, &DensityObject)>,
) { ) {
for (mut physics_body, transform) in &mut query { for (mut physics_body, transform, density_obj) in &mut query {
let grid_cell = pos_to_grid_cell(transform.translation.xy()); let grid_cell = pos_to_grid_cell(transform.translation.xy());
let mut density_gradient = Vec2::ZERO; let mut density_gradient = Vec2::ZERO;
@ -75,6 +82,7 @@ pub fn density_grid_force(
} }
} }
// println!("density : {:?}", density_gradient); // println!("density : {:?}", density_gradient);
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;
} }
} }

View file

@ -1,25 +1,40 @@
use crate::animation::{AnimationIndices, AnimationTimer};
use crate::physics_body::PhysicsBody; use crate::physics_body::PhysicsBody;
use crate::sphere_collider::SphereCollider; use crate::sphere_collider::SphereCollider;
use bevy::prelude::*; use bevy::prelude::*;
#[derive(Component)] #[derive(Component)]
#[require(Sprite, PhysicsBody, SphereCollider)] #[require(Sprite, PhysicsBody, SphereCollider, AnimationIndices)]
pub struct Kirby { pub struct Kirby {
speed_force: f32, speed_force: f32,
sucking: bool,
} }
pub fn kirby_spawn(mut commands: Commands, asset_server: Res<AssetServer>) { pub fn kirby_spawn(
let sprite = Sprite::from_image(asset_server.load("sprites/kirby.png")); mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
let texture: Handle<Image> = asset_server.load("sprites/Sprite_kirby-Sheet.png");
let body = PhysicsBody { mass: 10.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.05 }; let body = PhysicsBody { mass: 10.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.05 };
let transform = Transform::from_xyz(0.0, 0.0, 0.0).with_scale(Vec3::ONE * 0.25); let layout = TextureAtlasLayout::from_grid(UVec2::splat(32), 3, 3, None, None);
commands.spawn((Kirby { speed_force: 10000.0 }, transform, sprite, body)); 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,
));
} }
pub fn get_dir(keys: Res<ButtonInput<KeyCode>>) -> Vec2 { pub fn get_dir(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
if keys.pressed(KeyCode::Space) {
println!("SUCKING");
}
let mut dir = Vec2::ZERO; let mut dir = Vec2::ZERO;
if keys.pressed(KeyCode::KeyW) { if keys.pressed(KeyCode::KeyW) {
@ -38,13 +53,35 @@ pub fn get_dir(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
dir.normalize_or_zero() dir.normalize_or_zero()
} }
pub fn kirby_player_move(keys: Res<ButtonInput<KeyCode>>, mut query: Query<(&mut PhysicsBody, &Kirby), With<Kirby>>) { pub fn kirby_actions(
if keys.pressed(KeyCode::Space) { keys: Res<ButtonInput<KeyCode>>,
println!("SUCKING"); mut query: Query<(&mut PhysicsBody, &mut Kirby, &mut AnimationIndices, &mut Transform, &mut Sprite), With<Kirby>>,
} ) {
let space_just_pressed = keys.just_pressed(KeyCode::Space);
let space_just_released = keys.just_released(KeyCode::Space);
let dir = get_dir(keys); let dir = get_dir(keys);
for (mut body, kirby) in &mut query { for (mut body, mut kirby, mut anim_indices, mut transform, mut sprite) in &mut query {
if space_just_released {
kirby.sucking = false
};
if space_just_pressed {
kirby.sucking = true;
anim_indices.change(0, 2);
};
if kirby.sucking {
continue;
};
if dir.x != 0.0 {
sprite.flip_x = dir.x < 0.0;
}
body.force += dir * kirby.speed_force; body.force += dir * kirby.speed_force;
if dir == Vec2::ZERO {
anim_indices.change(3, 5);
} else {
anim_indices.change(6, 8);
}
} }
} }

View file

@ -1,37 +1,40 @@
use animation::animate_sprite;
use bevy::prelude::*; use bevy::prelude::*;
use bubble::bubble_move;
use bubble::bubble_spawn;
use camera::spawn_camera; use camera::spawn_camera;
use density_grid::DensityGrid; use density_grid::DensityGrid;
use density_grid::density_grid_force; use density_grid::density_grid_force;
use density_grid::density_grid_update; use density_grid::density_grid_update;
use density_grid::init_density_object; use density_grid::init_density_object;
use kirby::kirby_player_move; use kirby::kirby_actions;
use kirby::kirby_spawn; use kirby::kirby_spawn;
use lana::lana_move;
use lana::lana_spawn;
use map::MapBounds; use map::MapBounds;
use physics_body::integrate; use physics_body::integrate;
mod animation;
mod bubble;
mod camera; mod camera;
mod density_grid; mod density_grid;
mod globals; mod globals;
mod kirby; mod kirby;
mod lana;
mod map; mod map;
mod physics_body; mod physics_body;
mod sphere_collider; mod sphere_collider;
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.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)
.add_systems(Startup, kirby_spawn) .add_systems(Startup, kirby_spawn)
.add_systems(Startup, lana_spawn) .add_systems(Startup, bubble_spawn)
.add_systems(PostStartup, init_density_object) .add_systems(PostStartup, init_density_object)
.add_systems(Update, animate_sprite)
.add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain()) .add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain())
.add_systems(FixedUpdate, kirby_player_move) .add_systems(FixedUpdate, kirby_actions)
.add_systems(FixedUpdate, lana_move) .add_systems(FixedUpdate, bubble_move)
.add_systems(FixedPostUpdate, integrate) .add_systems(FixedPostUpdate, integrate)
.run(); .run();
} }