From 218021c8d564ff644cb4fe4518e8e4ce3f398d2d Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:01:18 -0300 Subject: [PATCH] feat: Add Particles (#41) * Added particles --- src/actors.rs | 9 -- src/atom.rs | 50 +++++---- src/chunk_manager.rs | 50 +++++---- src/consts.rs | 15 ++- src/debug.rs | 43 +++----- src/main.rs | 24 +++-- src/manager_api.rs | 78 +++++++------- src/particles.rs | 241 +++++++++++++++++++++++++++++++++++++++++++ src/player.rs | 141 ++++++++++++++----------- 9 files changed, 450 insertions(+), 201 deletions(-) create mode 100644 src/particles.rs diff --git a/src/actors.rs b/src/actors.rs index 86bdff1..da8302c 100644 --- a/src/actors.rs +++ b/src/actors.rs @@ -21,15 +21,6 @@ pub fn add_actors( if let Some(atom) = chunk_manager.get_mut_atom(pos) { if atom.state == State::Void { *atom = Atom::object(); - } else if atom.state == State::Liquid { - let rand_angle = fastrand::f32() - 0.5; - let vel = actor.vel * -4. * vec2(rand_angle.cos(), rand_angle.sin()); - //Water splashes - atom.velocity = ( - (vel.x).clamp(-126.0, 126.) as i8, - (vel.y).clamp(-126.0, 126.) as i8, - ); - atom.automata_mode = false; } } update_dirty_rects_3x3(&mut dirty_rects.current, pos); diff --git a/src/atom.rs b/src/atom.rs index dd9ef4e..0c58eb6 100644 --- a/src/atom.rs +++ b/src/atom.rs @@ -1,5 +1,6 @@ use rand::Rng; -use std::{collections::HashSet, f32::consts::PI}; +use std::collections::HashSet; +use std::f32::consts::PI; use crate::prelude::*; @@ -10,21 +11,19 @@ pub struct Atom { pub state: State, #[serde(skip)] - pub updated_at: u8, - #[serde(skip)] - pub automata_mode: bool, - // Used when thrown up, etc - #[serde(skip)] - pub velocity: (i8, i8), + pub speed: (i8, i8), // Frames idle #[serde(skip)] pub f_idle: u8, + #[serde(skip)] + pub updated_at: u8, } impl Atom { pub fn object() -> Self { Atom { state: State::Object, + color: [255, 255, 255, 255], ..Default::default() } } @@ -50,14 +49,14 @@ pub fn update_powder(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashS let mut cur_pos = pos; - // Get fall speed - let mut fall_speed = get_fspeed(chunks, cur_pos); - if fall_speed < TERM_VEL { - fall_speed += GRAVITY; - set_fspeed(chunks, cur_pos, fall_speed); + // Get atom speed + let mut speed = get_speed(chunks, cur_pos); + if speed < TERM_VEL { + speed += GRAVITY; + set_speed(chunks, cur_pos, speed); } - for _ in 0..fall_speed { + for _ in 0..speed { let neigh = down_neigh(chunks, cur_pos, &[(State::Liquid, 0.2)], dt); let mut swapped = false; for neigh in neigh { @@ -73,18 +72,16 @@ pub fn update_powder(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashS } if !swapped { - let new_vel = Vec2::new(0.0, -(fall_speed as f32)); + let vel = Vec2::new(0.0, -(speed as f32)); set_vel( chunks, cur_pos, Vec2::from_angle(rand::thread_rng().gen_range(-PI / 2.0..PI / 2.)) - .rotate(new_vel * 0.3) + .rotate(vel * 0.3) .as_ivec2(), ); - set_fspeed(chunks, cur_pos, 0); - break; } } @@ -98,14 +95,14 @@ pub fn update_liquid(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashS let mut cur_pos = pos; // Get fall speed - let mut fall_speed = get_fspeed(chunks, pos); - if fall_speed < TERM_VEL { - fall_speed += GRAVITY; - set_fspeed(chunks, pos, fall_speed); + let mut speed = get_speed(chunks, pos); + if speed < TERM_VEL { + speed += GRAVITY; + set_speed(chunks, pos, speed); } let mut swapped = false; - for _ in 0..fall_speed { + for _ in 0..speed { let neigh = down_neigh(chunks, cur_pos, &[], dt); for neigh in neigh { if neigh.0 { @@ -121,7 +118,7 @@ pub fn update_liquid(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashS } if !swapped { - set_fspeed(chunks, cur_pos, 0); + set_speed(chunks, cur_pos, 0); let neigh = side_neigh(chunks, cur_pos, &[], dt); let side = if neigh[0].0 { Some(neigh[0].1.x) @@ -149,13 +146,13 @@ pub fn update_liquid(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashS awakened } -/// Updates particle and returns atoms awakened -pub fn update_particle(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashSet { +/// This updates the atom with a vector based velocity, not a automata like one +pub fn update_atom(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashSet { let mut awakened = HashSet::new(); let mut cur_pos = pos; // Add gravity - let mut vel = get_vel(chunks, cur_pos).unwrap_or(IVec2::ZERO); + let mut vel = get_vel(chunks, cur_pos); if vel.y < TERM_VEL as i32 { vel += GRAVITY as i32 * IVec2::Y; set_vel(chunks, cur_pos, vel); @@ -185,7 +182,6 @@ pub fn update_particle(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> Has ); } else if !swapable(chunks, cur_pos + IVec2::Y, &[], state, dt) { set_vel(chunks, cur_pos, IVec2::ZERO); - set_mode(chunks, cur_pos, true); } break; } diff --git a/src/chunk_manager.rs b/src/chunk_manager.rs index 9a27e0f..b34a2e9 100644 --- a/src/chunk_manager.rs +++ b/src/chunk_manager.rs @@ -9,7 +9,7 @@ use smallvec::SmallVec; use crate::prelude::*; /// Updates and do the chunks logic -#[derive(Default, Resource)] +#[derive(Default, Resource, Clone)] pub struct ChunkManager { pub chunks: HashMap, pub pos: IVec2, @@ -199,7 +199,11 @@ pub fn manager_setup( .spawn(( Name::new("Chunks textures"), VisibilityBundle::default(), - TransformBundle::default(), + TransformBundle::from_transform(Transform::from_translation(vec3( + 0., + 0., + AUTOMATA_LAYER, + ))), ChunkTextures, )) .push_children(&images_vec); @@ -397,18 +401,11 @@ pub fn update_chunks(chunks: &mut UpdateChunksType, dt: u8, dirty_rect: &URect) let mut awake_self = false; let state; - let automata_mode; + let speed; { let atom = &mut chunks.group[local_pos]; state = atom.state; - - if atom.velocity == (0, 0) { - atom.automata_mode = true; - } - automata_mode = atom.automata_mode; - if automata_mode { - atom.velocity = (0, atom.velocity.1.abs()); - } + speed = atom.speed; if atom.f_idle < FRAMES_SLEEP && state != State::Void && state != State::Solid { atom.f_idle += 1; @@ -416,24 +413,27 @@ pub fn update_chunks(chunks: &mut UpdateChunksType, dt: u8, dirty_rect: &URect) } } - let mut awakened = if automata_mode { - match state { - State::Powder => update_powder(chunks, pos, dt), - State::Liquid => update_liquid(chunks, pos, dt), - _ => HashSet::new(), - } + let (vector, mut awakened) = if speed.0 == 0 && speed.1 >= 0 { + ( + false, + match state { + State::Powder => update_powder(chunks, pos, dt), + State::Liquid => update_liquid(chunks, pos, dt), + _ => HashSet::new(), + }, + ) } else { - update_particle(chunks, pos, dt) + (true, update_atom(chunks, pos, dt)) }; let mut self_awakened = HashSet::new(); if awakened.contains(&pos) { let atom = &mut chunks.group[local_pos]; atom.f_idle = 0; - } else if !automata_mode { - awakened.insert(pos); + } else if vector { let atom = &mut chunks.group[local_pos]; atom.f_idle = 0; + awakened.insert(pos); } else if awake_self { awakened.insert(pos); self_awakened.insert(pos); @@ -463,8 +463,8 @@ pub fn add_chunk( index: IVec2, ) -> Entity { let pos = Vec2::new( - index.x as f32 * SIDE_LENGHT, - (-index.y as f32) * SIDE_LENGHT, + index.x as f32 * CHUNK_LENGHT as f32, + (-index.y as f32) * CHUNK_LENGHT as f32, ); //Add texture @@ -484,11 +484,7 @@ pub fn add_chunk( anchor: Anchor::TopLeft, ..Default::default() }, - transform: Transform::from_xyz(pos.x, pos.y, 0.).with_scale(vec3( - ATOM_SIZE as f32, - ATOM_SIZE as f32, - 1., - )), + transform: Transform::from_xyz(pos.x, pos.y, 0.), ..Default::default() }) .id() diff --git a/src/consts.rs b/src/consts.rs index c2bb2da..59c3027 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -7,8 +7,6 @@ pub const CHUNK_LEN: usize = CHUNK_LENGHT * CHUNK_LENGHT; pub const HALF_CHUNK_LEN: usize = CHUNK_LEN / 2; pub const QUARTER_CHUNK_LEN: usize = CHUNK_LEN / 4; -pub const SIDE_LENGHT: f32 = (CHUNK_LENGHT * ATOM_SIZE) as f32; - // Actor consts pub const UP_WALK_HEIGHT: usize = 3; @@ -22,13 +20,17 @@ pub const JETPACK_FORCE: f32 = 1.5; pub const JETPACK_MAX: f32 = 3.; pub const JUMP_MAG: f32 = 13.; -pub const RUN_SPEED: f32 = 5.; +pub const RUN_SPEED: f32 = 3.5; pub const TOOL_DISTANCE: f32 = 32.; pub const TOOL_RANGE: f32 = 12.; // Engine consts -pub const ATOM_SIZE: usize = 3; + +//This was a "scale" const for the atoms, but we can just zoom in, so it was removed +//Made the code less verbose and simpler, we can readd if it makes sense +//pub const ATOM_SIZE: usize = 3; + pub const GRAVITY: u8 = 1; pub const TERM_VEL: u8 = 10; pub const FRAMES_SLEEP: u8 = 4; @@ -37,3 +39,8 @@ pub const LOAD_WIDTH: i32 = 20; pub const LOAD_HEIGHT: i32 = 12; pub const _CAMERA_SPEED: f32 = 10.; + +//Layers +pub const PLAYER_LAYER: f32 = 1.; +pub const PARTICLE_LAYER: f32 = 10.; +pub const AUTOMATA_LAYER: f32 = 100.; diff --git a/src/debug.rs b/src/debug.rs index 5822ca3..ea90e5c 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -12,7 +12,7 @@ fn brush( camera_q: Query<(&Camera, &GlobalTransform)>, mut chunk_manager: ResMut, mut dirty_rects: ResMut, - prev_mpos: Query<&PreviousMousePos>, + prev_mpos: Res, input: (Res>, Res>), ) { let (state, color); @@ -34,7 +34,7 @@ fn brush( (20 + rand::thread_rng().gen_range(-20_i16..20_i16)) as u8, (125 + rand::thread_rng().gen_range(-20_i16..20_i16)) as u8, (204 + rand::thread_rng().gen_range(-20_i16..20_i16)) as u8, - 255, + 150, ]; } else if input.1.pressed(KeyCode::ShiftLeft) { state = State::Solid; @@ -52,14 +52,12 @@ fn brush( .map(|ray| ray.origin.truncate()) { world_position.y *= -1.; - let prev_mpos = prev_mpos.single().0.unwrap(); for v in Line::new( - prev_mpos.as_ivec2(), - world_position.as_ivec2() - prev_mpos.as_ivec2(), + prev_mpos.0.unwrap().as_ivec2(), + world_position.as_ivec2() - prev_mpos.0.unwrap().as_ivec2(), ) { - let pos = v / ATOM_SIZE as i32; - let pos = global_to_chunk(pos); + let pos = global_to_chunk(v); //Checks if there is a atom at the pos if chunk_manager.get_atom(&pos).is_none() { @@ -97,13 +95,13 @@ fn brush( } } -#[derive(Component)] +#[derive(Resource, Default)] pub struct PreviousMousePos(pub Option); fn prev_mpos( window: Query<&Window>, camera_q: Query<(&Camera, &GlobalTransform)>, - mut prev_mpos: Query<&mut PreviousMousePos>, + mut prev_mpos: ResMut, ) { let (camera, camera_transform) = camera_q.single(); let window = window.single(); @@ -115,7 +113,7 @@ fn prev_mpos( { world_position.y *= -1.; - prev_mpos.single_mut().0 = Some(world_position); + prev_mpos.0 = Some(world_position); } } @@ -132,21 +130,16 @@ pub fn render_dirty_rects(mut commands: Commands, dirty_rects: Res) sprite: Sprite { color: Color::rgba(i, 0.25, if i == 0. { 1. } else { 0. }, 0.1), custom_size: Some( - UVec2::new( - (rect.max.x - rect.min.x + 1) * ATOM_SIZE as u32, - (rect.max.y - rect.min.y + 1) * ATOM_SIZE as u32, - ) - .as_vec2(), + UVec2::new(rect.max.x - rect.min.x + 1, rect.max.y - rect.min.y + 1) + .as_vec2(), ), anchor: Anchor::TopLeft, ..default() }, transform: Transform::from_translation( IVec3::new( - chunk_pos.x * (CHUNK_LENGHT * ATOM_SIZE) as i32 - + (rect.min.x as i32 * ATOM_SIZE as i32), - -(chunk_pos.y * (CHUNK_LENGHT * ATOM_SIZE) as i32) - - (rect.min.y as i32 * ATOM_SIZE as i32), + chunk_pos.x * CHUNK_LENGHT as i32 + rect.min.x as i32, + -(chunk_pos.y * CHUNK_LENGHT as i32) - rect.min.y as i32, 1, ) .as_vec3(), @@ -165,16 +158,13 @@ fn render_actors(mut commands: Commands, actors: Query<&Actor>) { .spawn(SpriteBundle { sprite: Sprite { color: Color::rgba(0.75, 0.25, 0.25, 0.2), - custom_size: Some(Vec2::new( - actor.width as f32 * ATOM_SIZE as f32, - actor.height as f32 * ATOM_SIZE as f32, - )), + custom_size: Some(Vec2::new(actor.width as f32, actor.height as f32)), anchor: Anchor::TopLeft, ..default() }, transform: Transform::from_translation(Vec3::new( - actor.pos.x as f32 * ATOM_SIZE as f32, - -actor.pos.y as f32 * ATOM_SIZE as f32, + actor.pos.x as f32, + -actor.pos.y as f32, 1., )), ..default() @@ -216,6 +206,7 @@ impl Plugin for DebugPlugin { .add_systems(PreUpdate, delete_image) .add_plugins(WorldInspectorPlugin::new()) //Frame on console - .add_plugins((LogDiagnosticsPlugin::default(), FrameTimeDiagnosticsPlugin)); + .add_plugins((LogDiagnosticsPlugin::default(), FrameTimeDiagnosticsPlugin)) + .init_resource::(); } } diff --git a/src/main.rs b/src/main.rs index 7978b09..a6220bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,22 @@ mod consts; mod debug; mod geom_tools; mod manager_api; +mod particles; mod player; mod prelude { pub use crate::atom::State; pub use crate::{ - actors::*, animation::*, atom::*, chunk::*, chunk_group::*, chunk_manager::*, consts::*, - debug::*, geom_tools::*, manager_api::*, player::*, + actors::*, + animation::*, + atom::*, + chunk::*, + chunk_group::*, + chunk_manager::*, + consts::*, + geom_tools::*, + manager_api::*, + particles::*, + player::*, //debug::*, }; pub use bevy::input::mouse::MouseScrollUnit; pub use bevy::input::mouse::MouseWheel; @@ -45,17 +55,17 @@ fn main() { ActorsPlugin, PlayerPlugin, animation::AnimationPlugin, + ParticlesPlugin, )) - .add_systems(Startup, setup) + .add_systems(Startup, setup_camera) .run(); } -fn setup(mut commands: Commands) { +fn setup_camera(mut commands: Commands) { let mut camera = Camera2dBundle::default(); camera.camera.hdr = true; - camera.transform.scale.x = 0.67; - camera.transform.scale.y = 0.67; + camera.transform.scale.x = 0.23; + camera.transform.scale.y = 0.23; commands.spawn(camera); - commands.spawn(PreviousMousePos(None)); } diff --git a/src/manager_api.rs b/src/manager_api.rs index c4b8f6f..15fbb3e 100644 --- a/src/manager_api.rs +++ b/src/manager_api.rs @@ -15,11 +15,20 @@ pub struct UpdateChunksType<'a> { /// Swap two atoms from global 3x3 chunks positions pub fn swap(chunks: &mut UpdateChunksType, pos1: IVec2, pos2: IVec2, dt: u8) { + let states = [get_state(chunks, pos1), get_state(chunks, pos2)]; let chunk_group = &mut chunks.group; { let temp = chunk_group[pos1]; - chunk_group[pos1] = chunk_group[pos2]; - chunk_group[pos2] = temp; + chunk_group[pos1] = if states[1] == State::Object { + Atom::default() + } else { + chunk_group[pos2] + }; + chunk_group[pos2] = if states[0] == State::Object { + Atom::default() + } else { + temp + }; chunk_group[pos1].updated_at = dt; chunk_group[pos2].updated_at = dt; @@ -41,6 +50,7 @@ pub fn swap(chunks: &mut UpdateChunksType, pos1: IVec2, pos2: IVec2, dt: u8) { } } +// If you are getting this from a transform, make sure to flip the y value /// Transforms a global manager pos to a chunk pos pub fn global_to_chunk(mut pos: IVec2) -> ChunkPos { // This makes sure we don't have double 0 chunks. @@ -82,15 +92,6 @@ pub fn global_to_chunk(mut pos: IVec2) -> ChunkPos { ChunkPos::new(uvec2(x, y), ivec2(chunk_x, chunk_y)) } -/// Transforms a chunk pos to a global manager pos -pub fn chunk_to_global(pos: ChunkPos) -> IVec2 { - let mut atom = pos.atom.as_ivec2(); - atom.x += pos.chunk.x * CHUNK_LENGHT as i32; - atom.y += pos.chunk.y * CHUNK_LENGHT as i32; - - atom -} - /// See if position is swapable, that means it sees if the position is a void /// or if it's a swapable state and has been not updated pub fn swapable( @@ -155,34 +156,28 @@ pub fn side_neigh( neigh } -/// Gets velocity from a global pos -pub fn get_vel(chunks: &UpdateChunksType, pos: IVec2) -> Option { - let vel = chunks.group[pos].velocity; - - if vel == (0, 0) { - None - } else { - Some(ivec2(vel.0 as i32, vel.1 as i32)) - } +/// Gets speed from a global pos +pub fn get_speed(chunks: &UpdateChunksType, pos: IVec2) -> u8 { + chunks.group[pos].speed.1 as u8 } -/// Sets velocity from a global pos -pub fn set_vel(chunks: &mut UpdateChunksType, pos: IVec2, velocity: IVec2) { - chunks.group[pos].velocity = if velocity == IVec2::ZERO { - (0, 0) - } else { - (velocity.x as i8, velocity.y as i8) - } +/// Sets speed from a global pos +pub fn set_speed(chunks: &mut UpdateChunksType, pos: IVec2, speed: u8) { + chunks.group[pos].speed.1 = speed as i8 } -/// Sets mode from a global pos -pub fn set_mode(chunks: &mut UpdateChunksType, pos: IVec2, mode: bool) { - chunks.group[pos].automata_mode = mode +/// Gets velocity from a global pos +pub fn get_vel(chunks: &UpdateChunksType, pos: IVec2) -> IVec2 { + ivec2( + chunks.group[pos].speed.0 as i32, + chunks.group[pos].speed.1 as i32, + ) } -/// Gets fall speed from a global pos -pub fn get_fspeed(chunks: &UpdateChunksType, pos: IVec2) -> u8 { - chunks.group[pos].velocity.1.try_into().unwrap() +/// Sets speed from a global pos +pub fn set_vel(chunks: &mut UpdateChunksType, pos: IVec2, vel: IVec2) { + chunks.group[pos].speed.0 = vel.x as i8; + chunks.group[pos].speed.1 = vel.y as i8; } /// Gets state from a global pos @@ -190,11 +185,6 @@ pub fn get_state(chunks: &UpdateChunksType, pos: IVec2) -> State { chunks.group[pos].state } -/// Sets fall speed from a global pos -pub fn set_fspeed(chunks: &mut UpdateChunksType, pos: IVec2, fall_speed: u8) { - chunks.group[pos].velocity.1 = fall_speed as i8 -} - /// Checks if atom is able to update this frame from a global pos pub fn dt_updatable(chunks: &UpdateChunksType, pos: IVec2, dt: u8) -> bool { if let Some(atom) = chunks.group.get_global(pos) { @@ -343,7 +333,7 @@ pub fn update_dirty_rects_3x3(dirty_rects: &mut HashMap, pos: Chun } else { // Case where the 3x3 position is in the corner of a chunk for (x, y) in (-1..=1).cartesian_product(-1..=1) { - let mut global = chunk_to_global(pos); + let mut global = pos.to_global(); global += ivec2(x, y); let pos = global_to_chunk(global); @@ -359,7 +349,7 @@ pub fn update_dirty_rects_3x3(dirty_rects: &mut HashMap, pos: Chun } } -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct ChunkPos { pub atom: UVec2, pub chunk: IVec2, @@ -369,6 +359,14 @@ impl ChunkPos { pub fn new(atom: UVec2, chunk: IVec2) -> Self { Self { atom, chunk } } + + pub fn to_global(self) -> IVec2 { + let mut atom = self.atom.as_ivec2(); + atom.x += self.chunk.x * CHUNK_LENGHT as i32; + atom.y += self.chunk.y * CHUNK_LENGHT as i32; + + atom + } } /// A deferred update message. diff --git a/src/particles.rs b/src/particles.rs new file mode 100644 index 0000000..a9cde9d --- /dev/null +++ b/src/particles.rs @@ -0,0 +1,241 @@ +use crate::prelude::*; + +//To spawn a particle just add a entity with this component +#[derive(Component, Debug, Default)] +pub struct Particle { + pub atom: Atom, + pub velocity: Vec2, + //Used when initiating particle + pub pos: Vec2, + pub state: PartState, +} + +/// Particle State +#[derive(Default, Debug, PartialEq)] +pub enum PartState { + //Used when the particle is looking for a place to put itself + Looking, + #[default] + Normal, + //Used when following a entity transform + Follow(Entity), +} + +#[derive(Component)] +pub struct Hydrated; + +pub fn hydrate_particles( + mut commands: Commands, + particles: Query<(&Particle, Entity), Without>, +) { + //Spawn particle sprite + for (particle, ent) in particles.iter() { + commands + .entity(ent) + .insert(SpriteBundle { + sprite: Sprite { + color: Color::rgba( + particle.atom.color[0] as f32 / 255., + particle.atom.color[1] as f32 / 255., + particle.atom.color[2] as f32 / 255., + particle.atom.color[3] as f32 / 255., + ), + custom_size: Some(Vec2::new(1.0, 1.0)), + ..default() + }, + //TODO figure out layers + transform: Transform::from_translation(Vec3::new( + particle.pos.x, + -particle.pos.y, + PARTICLE_LAYER, + )), + ..default() + }) + .insert(Hydrated); + } +} + +pub fn update_particles( + mut commands: Commands, + mut particles: Query<(&mut Particle, &mut Transform, Entity), With>, + entities: Query<&GlobalTransform, Without>, + mut chunk_manager: ResMut, + mut dirty_rects: ResMut, +) { + let compute_pool = ComputeTaskPool::get(); + + compute_pool.scope(|deferred_scope| { + let chunks = &chunk_manager.chunks.clone(); + let manager_pos = &chunk_manager.pos.clone(); + + let (particles_send, particles_recv) = async_channel::unbounded::(); + let particle_send = &particles_send; + + deferred_scope.spawn(async move { + while let Ok(update) = particles_recv.recv().await { + if update.remove { + commands.entity(update.ent).despawn(); + continue; + } + + if let Some(atom) = chunk_manager.get_mut_atom(update.chunk_pos) { + if atom.state == State::Void { + *atom = update.atom; + commands.entity(update.ent).despawn(); + + update_dirty_rects(&mut dirty_rects.render, update.chunk_pos); + update_dirty_rects_3x3(&mut dirty_rects.current, update.chunk_pos); + } + } + } + }); + + particles + .iter_mut() + .for_each(|(mut particle, mut transform, ent)| { + let mut dest_pos = transform.translation.xy(); + dest_pos.y *= -1.; + dest_pos += particle.velocity; + + let mut cur_pos = transform.translation.xy(); + cur_pos.y *= -1.; + + let x_bound = (manager_pos.x * CHUNK_LENGHT as i32) + ..((manager_pos.x + LOAD_WIDTH) * CHUNK_LENGHT as i32); + let y_bound = (manager_pos.y * CHUNK_LENGHT as i32) + ..((manager_pos.y + LOAD_HEIGHT) * CHUNK_LENGHT as i32); + //If not on bounds, remove particle + if !x_bound.contains(&(dest_pos.x as i32)) + || !y_bound.contains(&(dest_pos.y as i32)) + || !x_bound.contains(&(cur_pos.x as i32)) + || !y_bound.contains(&(cur_pos.y as i32)) + { + particle_send + .try_send(DeferredParticleUpdate { + remove: true, + atom: Atom::default(), + chunk_pos: ChunkPos::default(), + ent, + }) + .unwrap(); + } else { + match particle.state { + PartState::Looking | PartState::Normal => { + if particle.state == PartState::Normal { + particle.velocity += GRAVITY as f32 * Vec2::Y; + } + + let mut prev_pos = cur_pos.as_ivec2(); + for pos in + Line::new(cur_pos.as_ivec2(), (dest_pos - cur_pos).as_ivec2()) + { + let chunk_pos = global_to_chunk(pos); + let prev_chunk_pos = global_to_chunk(prev_pos); + + let atom = chunks.get(&chunk_pos.chunk).unwrap().atoms + [chunk_pos.atom.d1()]; + let prev_atom = chunks.get(&prev_chunk_pos.chunk).unwrap().atoms + [prev_chunk_pos.atom.d1()]; + + if particle.state == PartState::Normal + && atom.state != State::Void + && atom.state != State::Object + { + //Hit something! + //If our previous pos is free + if prev_atom.state == State::Void + || prev_atom.state == State::Object + { + particle_send + .try_send(DeferredParticleUpdate { + chunk_pos: global_to_chunk(prev_pos), + atom: particle.atom, + ent, + remove: false, + }) + .unwrap(); + } else if particle.state != PartState::Looking { + //Upward warp if can't find a place to put + particle.velocity.y = -2.; + particle.velocity.x = 0.; + particle.state = PartState::Looking; + } + + break; + } else if particle.state == PartState::Looking + && atom.state == State::Void + || atom.state == State::Object + { + particle_send + .try_send(DeferredParticleUpdate { + chunk_pos: global_to_chunk(pos), + atom: particle.atom, + ent, + remove: false, + }) + .unwrap(); + break; + } + + prev_pos = pos; + } + transform.translation.x = dest_pos.x; + transform.translation.y = -dest_pos.y; + } + PartState::Follow(follow_ent) => { + let follow_transform = entities.get(follow_ent).unwrap(); + let mut follow_pos = + follow_transform.compute_transform().translation.xy(); + follow_pos.y *= -1.; + + let mag = (particle.velocity.length()).clamp(0., 6.); + let angle = follow_pos - cur_pos; + let angle = angle.y.atan2(angle.x); + + particle.velocity = vec2(angle.cos(), angle.sin()) * (mag + 0.5); + + let mut part_vel = particle.velocity; + part_vel.y *= -1.; + transform.translation += part_vel.extend(0.); + + if transform + .translation + .xy() + .distance(follow_transform.compute_transform().translation.xy()) + < 3. + { + particle_send + .try_send(DeferredParticleUpdate { + remove: true, + atom: Atom::default(), + chunk_pos: ChunkPos::default(), + ent, + }) + .unwrap(); + } + } + } + } + }); + }); +} + +pub struct DeferredParticleUpdate { + pub chunk_pos: ChunkPos, + pub atom: Atom, + pub ent: Entity, + pub remove: bool, +} + +pub struct ParticlesPlugin; +impl Plugin for ParticlesPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + ( + hydrate_particles.before(update_particles), + update_particles.after(chunk_manager_update), + ), + ); + } +} diff --git a/src/player.rs b/src/player.rs index 5824ee3..c638234 100644 --- a/src/player.rs +++ b/src/player.rs @@ -22,6 +22,8 @@ impl Default for Player { pub struct Tool { atoms: Vec, } +#[derive(Component)] +pub struct ToolFront; pub fn player_setup( mut commands: Commands, @@ -40,8 +42,7 @@ pub fn player_setup( TextureAtlas::from_grid(player_handle, Vec2::new(24.0, 24.0), 8, 5, None, None); let player_atlas_handle = texture_atlases.add(player_atlas); let animation_indices = AnimationIndices { first: 0, last: 1 }; - let mut player_transform = Transform::from_scale(Vec3::splat(ATOM_SIZE as f32)); - player_transform.translation = vec2(5. * 3., -8. * 3.).extend(2.); + let player_transform = GlobalTransform::from_xyz(5. * 3., -8. * 3., PLAYER_LAYER); let tool_handle = asset_server.load("player/player_tool.png"); let tool_bundle = SpriteBundle { @@ -50,10 +51,20 @@ pub fn player_setup( anchor: Anchor::CenterLeft, ..Default::default() }, - transform: Transform::from_translation(Vec3::new(-3., -3.5, 1.)), + transform: Transform::from_translation(Vec3::new(-3., -3.5, 0.1)), ..Default::default() }; - let tool_ent = commands.spawn(tool_bundle).insert(Tool::default()).id(); + let tool_front_ent = commands + .spawn(( + TransformBundle::from_transform(Transform::from_translation(vec3(8., 0., 0.))), + ToolFront, + )) + .id(); + let tool_ent = commands + .spawn(tool_bundle) + .insert(Tool::default()) + .insert_children(0, &[tool_front_ent]) + .id(); commands .spawn(( @@ -62,7 +73,7 @@ pub fn player_setup( SpriteSheetBundle { texture_atlas: player_atlas_handle, sprite: TextureAtlasSprite::new(animation_indices.first), - transform: player_transform, + global_transform: player_transform, ..default() }, animation_indices, @@ -73,26 +84,14 @@ pub fn player_setup( /// Updates player pub fn update_player( - input: ( - Res>, - ResMut>, - EventReader, - ), - window: Query<&Window>, - mut player: Query<( - &mut Actor, - &mut Player, - &mut TextureAtlasSprite, - &mut AnimationIndices, - )>, - mut tool: Query<(&mut Transform, &GlobalTransform, &mut Sprite, &mut Tool)>, - mut camera_q: Query<(&Camera, &GlobalTransform, &mut Transform), Without>, - mut chunk_manager: ResMut, - mut dirty_rects: ResMut, + input: (ResMut>, EventReader), + mut player: Query<(&mut Actor, &mut Player, &mut AnimationIndices)>, + chunk_manager: ResMut, + mut camera: Query<&mut Transform, (Without, With)>, ) { - let (mut actor, mut player, mut textatlas_sprite, mut anim_idxs) = player.single_mut(); - let (mut tool_transform, tool_gtransform, mut tool_sprite, mut tool) = tool.single_mut(); - let (mouse, keys, mut scroll_evr) = input; + let (mut actor, mut player, mut anim_idxs) = player.single_mut(); + let (keys, mut scroll_evr) = input; + let mut camera_transform = camera.single_mut(); // Gravity if actor.vel.y < TERM_VEL as f32 { @@ -148,9 +147,32 @@ pub fn update_player( } } - // Tool - let (camera, camera_gtransform, mut camera_transform) = camera_q.single_mut(); + for ev in scroll_evr.read() { + if ev.unit == MouseScrollUnit::Line { + camera_transform.scale *= 0.9_f32.powi(ev.y as i32); + } + } +} + +pub fn tool_system( + mut commands: Commands, + mut tool: Query<(&mut Transform, &GlobalTransform, &mut Sprite, &mut Tool)>, + window: Query<&Window>, + mut camera: Query<(&Camera, &GlobalTransform), Without>, + tool_front_ent: Query>, + mut player: Query<&mut TextureAtlasSprite, With>, + resources: ( + ResMut, + ResMut, + Res>, + ), +) { + let (mut tool_transform, tool_gtransform, mut tool_sprite, mut tool) = tool.single_mut(); + let (camera, camera_gtransform) = camera.single_mut(); let window = window.single(); + let mut textatlas_sprite = player.single_mut(); + let (mut chunk_manager, mut dirty_rects, mouse) = resources; + if let Some(world_position) = window .cursor_position() .and_then(|cursor| camera.viewport_to_world(camera_gtransform, cursor)) @@ -171,7 +193,6 @@ pub fn update_player( //Tool shooting and sucking atoms let mut center_vec_y_flipped = center_vec; center_vec_y_flipped.y *= -1.; - center_vec_y_flipped /= ATOM_SIZE as f32; let tool_slope = Vec2::new(angle.cos(), -angle.sin()); let bound_slope = Vec2::new((angle + std::f32::consts::FRAC_PI_2).cos(), -(angle).cos()); @@ -179,24 +200,29 @@ pub fn update_player( let mut pos_to_update = vec![]; if mouse.pressed(MouseButton::Right) { - let new_tool_front = tool_front + tool_slope * 6.; - for i in 0..3 { - for vec in Line::new( - (new_tool_front - bound_slope * 3. + tool_slope * i as f32 * 2.).as_ivec2(), - (bound_slope * 3.).as_ivec2(), - ) { - let chunk_pos = global_to_chunk(vec); - if let (Some(atom), Some(mut tool_atom)) = - (chunk_manager.get_mut_atom(chunk_pos), tool.atoms.pop()) - { - if atom.state == State::Void { - let vel = tool_slope * 10. * (fastrand::f32() * 0.2 + 0.8); + let new_tool_front = tool_front + tool_slope * 2.; + let n = 12; - tool_atom.velocity = (vel.x as i8, vel.y as i8); - chunk_manager[chunk_pos] = tool_atom; + for i in 0..=n { + let angle = fastrand::f32() * std::f32::consts::TAU; - pos_to_update.push(chunk_pos); - } + let vec = new_tool_front - bound_slope * 2. + + bound_slope * 2.5 * i as f32 / n as f32 + + vec2(angle.cos(), angle.sin()); + let chunk_pos = global_to_chunk(vec.as_ivec2()); + if let (Some(atom), Some(tool_atom)) = + (chunk_manager.get_mut_atom(chunk_pos), tool.atoms.pop()) + { + if atom.state == State::Void || atom.state == State::Object { + let angle = fastrand::f32() * 0.5 - 0.25; + let vel = (tool_slope * 10. * (fastrand::f32() * 0.2 + 0.8)) + .rotate(vec2(angle.cos(), angle.sin())); + commands.spawn(Particle { + atom: tool_atom, + velocity: vel, + pos: vec, + ..Default::default() + }); } } } @@ -211,9 +237,16 @@ pub fn update_player( let chunk_pos = global_to_chunk(vec); if let Some(atom) = chunk_manager.get_mut_atom(chunk_pos) { if atom.state != State::Void && atom.state != State::Object { + commands.spawn(Particle { + atom: *atom, + pos: chunk_pos.to_global().as_vec2(), + state: PartState::Follow(tool_front_ent.single()), + ..Default::default() + }); + + pos_to_update.push(chunk_pos); tool.atoms.push(*atom); *atom = Atom::default(); - pos_to_update.push(chunk_pos); break; } } @@ -226,12 +259,6 @@ pub fn update_player( update_dirty_rects(&mut dirty_rects.render, pos); } } - - for ev in scroll_evr.read() { - if ev.unit == MouseScrollUnit::Line { - camera_transform.scale *= 0.9_f32.powi(ev.y as i32); - } - } } pub fn update_player_sprite( @@ -241,17 +268,8 @@ pub fn update_player_sprite( let (mut transform, actor) = query.single_mut(); let mut camera_transform = camera_q.single_mut(); - let top_corner_vec = vec3( - actor.pos.x as f32 * ATOM_SIZE as f32, - -actor.pos.y as f32 * ATOM_SIZE as f32, - 2., - ); - let center_vec = top_corner_vec - + vec3( - actor.width as f32 / 2. * ATOM_SIZE as f32, - -(8. * ATOM_SIZE as f32), - 0., - ); + let top_corner_vec = vec3(actor.pos.x as f32, -actor.pos.y as f32, 2.); + let center_vec = top_corner_vec + vec3(actor.width as f32 / 2., -8., 0.); transform.translation = center_vec; camera_transform.translation = center_vec; } @@ -266,6 +284,7 @@ impl Plugin for PlayerPlugin { Update, ( update_player.after(chunk_manager_update), + tool_system.after(update_player), update_player_sprite.after(update_actors), ), )