From b4c80b919e62c869ebe7f40eb1a62d4a4cbb9a02 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 29 Jul 2023 20:29:29 +0100 Subject: [PATCH] delayed particles --- src/main.rs | 2 +- src/particle_system.rs | 120 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 112 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 07de74e..508b000 100644 --- a/src/main.rs +++ b/src/main.rs @@ -251,7 +251,7 @@ impl GameState for State { } // Clear screen ctx.cls(); - particle_system::cull_dead_particles(&mut self.ecs, ctx); + particle_system::particle_ticker(&mut self.ecs, ctx); match new_runstate { RunState::MainMenu { .. } => {} diff --git a/src/particle_system.rs b/src/particle_system.rs index 0afcbab..48f5811 100644 --- a/src/particle_system.rs +++ b/src/particle_system.rs @@ -14,7 +14,12 @@ pub const LONG_PARTICLE_LIFETIME: f32 = 300.0; // running through a list and removing the frame_time_ms from the // delay. When delay is <= 0, make a particle_builder.request for // the particle. -pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) { +pub fn particle_ticker(ecs: &mut World, ctx: &Rltk) { + cull_dead_particles(ecs, ctx); + create_delayed_particles(ecs, ctx); +} + +fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) { let mut dead_particles: Vec = Vec::new(); { // Age out particles @@ -32,7 +37,51 @@ pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) { } } -struct ParticleRequest { +fn create_delayed_particles(ecs: &mut World, ctx: &Rltk) { + let mut particle_builder = ecs.write_resource::(); + let mut handled_particles: Vec = Vec::new(); + for delayed_particle in particle_builder.delayed_requests.iter_mut() { + rltk::console::log(delayed_particle.delay); + delayed_particle.delay -= ctx.frame_time_ms; + if delayed_particle.delay < 0.0 { + handled_particles.push(ParticleRequest { + x: delayed_particle.particle.x, + y: delayed_particle.particle.y, + fg: delayed_particle.particle.fg, + bg: delayed_particle.particle.bg, + glyph: delayed_particle.particle.glyph, + lifetime: delayed_particle.particle.lifetime, + }); + } + } + if handled_particles.is_empty() { + return; + } + // This is repeated code from the ticking system. It could probably be put into a function of its own, + // but that'd mean having to work around borrow-checker issues, which I'm not convinced is actually + // cleaner than just repeating the code twice - here and in the ParticleSpawnSystem. + // + // We're running separately from the system so we can have it called every single tick, without having + // to do the same for every system. Later, this will probably all be refactored so every system can run + // at a higher tickrate. + let entities = ecs.entities(); + let mut positions = ecs.write_storage::(); + let mut renderables = ecs.write_storage::(); + let mut particles = ecs.write_storage::(); + for handled in handled_particles { + let index = particle_builder.delayed_requests.iter().position(|x| x.particle == handled).unwrap(); + particle_builder.delayed_requests.remove(index); + let p = entities.create(); + positions.insert(p, Position { x: handled.x, y: handled.y }).expect("Could not insert position"); + renderables + .insert(p, Renderable { fg: handled.fg, bg: handled.bg, glyph: handled.glyph, render_order: 0 }) + .expect("Could not insert renderables"); + particles.insert(p, ParticleLifetime { lifetime_ms: handled.lifetime }).expect("Could not insert lifetime"); + } +} + +#[derive(PartialEq)] +pub struct ParticleRequest { x: i32, y: i32, fg: RGB, @@ -41,14 +90,21 @@ struct ParticleRequest { lifetime: f32, } +#[derive(PartialEq)] +pub struct DelayedParticleRequest { + pub delay: f32, + pub particle: ParticleRequest, +} + pub struct ParticleBuilder { requests: Vec, + delayed_requests: Vec, } impl ParticleBuilder { #[allow(clippy::new_without_default)] pub fn new() -> ParticleBuilder { - ParticleBuilder { requests: Vec::new() } + ParticleBuilder { requests: Vec::new(), delayed_requests: Vec::new() } } /// Makes a single particle request. @@ -56,6 +112,11 @@ impl ParticleBuilder { self.requests.push(ParticleRequest { x, y, fg, bg, glyph, lifetime }); } + pub fn delay(&mut self, x: i32, y: i32, fg: RGB, bg: RGB, glyph: rltk::FontCharType, lifetime: f32, delay: f32) { + self.delayed_requests + .push(DelayedParticleRequest { delay: delay, particle: ParticleRequest { x, y, fg, bg, glyph, lifetime } }); + } + pub fn damage_taken(&mut self, x: i32, y: i32) { self.request( x, @@ -112,12 +173,53 @@ impl ParticleBuilder { } // Makes a particle request in the shape of an 'x'. Sort of. - pub fn request_star(&mut self, x: i32, y: i32, fg: RGB, bg: RGB, glyph: rltk::FontCharType, lifetime: f32) { - self.request(x, y, fg, bg, glyph, lifetime * 2.0); - self.request(x + 1, y + 1, fg, bg, rltk::to_cp437('/'), lifetime); - self.request(x + 1, y - 1, fg, bg, rltk::to_cp437('\\'), lifetime); - self.request(x - 1, y + 1, fg, bg, rltk::to_cp437('\\'), lifetime); - self.request(x - 1, y - 1, fg, bg, rltk::to_cp437('/'), lifetime); + #[allow(dead_code)] + pub fn request_star( + &mut self, + x: i32, + y: i32, + fg: RGB, + bg: RGB, + glyph: rltk::FontCharType, + lifetime: f32, + secondary_fg: RGB, + ) { + let eighth_l = lifetime / 8.0; + let quarter_l = eighth_l * 2.0; + self.request(x, y, fg, bg, glyph, lifetime); + self.delay(x + 1, y + 1, secondary_fg.lerp(bg, 0.8), bg, rltk::to_cp437('/'), quarter_l, eighth_l); + self.delay(x + 1, y - 1, secondary_fg.lerp(bg, 0.6), bg, rltk::to_cp437('\\'), quarter_l, quarter_l); + self.delay(x - 1, y - 1, secondary_fg.lerp(bg, 0.2), bg, rltk::to_cp437('/'), quarter_l, eighth_l * 3.0); + self.delay(x - 1, y + 1, secondary_fg.lerp(bg, 0.4), bg, rltk::to_cp437('\\'), quarter_l, lifetime); + } + + // Makes a rainbow particle request in the shape of an 'x'. Sort of. + pub fn request_rainbow_star(&mut self, x: i32, y: i32, glyph: rltk::FontCharType, lifetime: f32) { + let bg = RGB::named(rltk::BLACK); + let eighth_l = lifetime / 8.0; + let quarter_l = eighth_l * 2.0; + let half_l = quarter_l * 2.0; + + self.request(x, y, RGB::named(rltk::CYAN), bg, glyph, lifetime); + self.delay(x + 1, y + 1, RGB::named(rltk::RED), bg, rltk::to_cp437('\\'), half_l, eighth_l); + self.delay(x + 1, y - 1, RGB::named(rltk::ORANGE), bg, rltk::to_cp437('/'), half_l, quarter_l); + self.delay(x - 1, y - 1, RGB::named(rltk::GREEN), bg, rltk::to_cp437('\\'), half_l, eighth_l * 3.0); + self.delay(x - 1, y + 1, RGB::named(rltk::YELLOW), bg, rltk::to_cp437('/'), half_l, half_l); + } + + // Makes a rainbow particle request. Sort of. + #[allow(dead_code)] + pub fn request_rainbow(&mut self, x: i32, y: i32, glyph: rltk::FontCharType, lifetime: f32) { + let bg = RGB::named(rltk::BLACK); + let eighth_l = lifetime / 8.0; + + self.request(x, y, RGB::named(rltk::RED), bg, glyph, eighth_l); + self.delay(x, y, RGB::named(rltk::ORANGE), bg, glyph, eighth_l, eighth_l); + self.delay(x, y, RGB::named(rltk::YELLOW), bg, glyph, eighth_l, eighth_l * 2.0); + self.delay(x, y, RGB::named(rltk::GREEN), bg, glyph, eighth_l, eighth_l * 3.0); + self.delay(x, y, RGB::named(rltk::BLUE), bg, glyph, eighth_l, eighth_l * 4.0); + self.delay(x, y, RGB::named(rltk::INDIGO), bg, glyph, eighth_l, eighth_l * 5.0); + self.delay(x, y, RGB::named(rltk::VIOLET), bg, glyph, eighth_l, eighth_l * 6.0); } /// Makes a particle request in the shape of a +.