diff --git a/content/effects.toml b/content/effects.toml index af341f8..19f9d23 100644 --- a/content/effects.toml +++ b/content/effects.toml @@ -3,32 +3,61 @@ sprite = "particle::explosion::small" lifetime = "inherit" inherit_velocity = "target" size = 8.0 +size_rng = 1.6 +angle_rng = 360 +velocity_scale_parent = 1.0 + [effect."large explosion"] sprite = "particle::explosion::large" lifetime = "inherit" inherit_velocity = "target" size = 25.0 +size_rng = 5.0 +angle_rng = 360 +velocity_scale_parent = 1.0 + [effect."huge explosion"] sprite = "particle::explosion::huge" lifetime = "inherit" inherit_velocity = "target" size = 50.0 +size_rng = 10.0 +angle_rng = 360 +velocity_scale_parent = 1.0 + +# Every effect has a parent, some effects have a target [effect."blaster impact"] sprite = "particle::blaster" -lifetime = "inherit" -inherit_velocity = "target" -size = 3.0 +lifetime = "inherit" # number in seconds or inherit from sprite +lifetime_rng = 0.0 # Random variation of lifetime (up to this value) + +size = 3.0 # sprite size, in game units +size_rng = 1.0 # random size variation + +angle = 0.0 # absolute starting angle. always added to parent angle. +angle_rng = 90.0 # Starting angle randomness (up to this value) + +# Does not affect velocity, only sprite angle +angvel_rng = 0.0 # Angvel randomness, applied to angvel +angvel = 0.0 # Angular velocity at creation + + +# Total velocity is sum of parent + target velocities with scale applied +velocity_scale_parent = 0.0 # Multiply velocity by this value +velocity_scale_parent_rng = 0.0 # random variation of scale +velocity_scale_target = 1.0 +velocity_scale_target_rng = 1.0 + +direction_rng = 1.0 # Random variation of travel direction, in degrees, applied to velocity vector (/2 each side?) # TODO: -# inherit velocity scale -# absolute velocity/angle (no inherit) -# random lifetime, velocity, angle, spin -# bullet bounce effect: inherit and change velocity # effect probabilities & variants # multiple particles in one effect # fade # document: effect vs particle +# sprite lifetime/fps variation (and effects inherit lifetime later) +# universal effect creator diff --git a/content/guns.toml b/content/guns.toml index 6dc6bb7..711fde5 100644 --- a/content/guns.toml +++ b/content/guns.toml @@ -33,5 +33,5 @@ projectile.impact_effect = "blaster impact" projectile.expire_effect.sprite = "particle::blaster" projectile.expire_effect.lifetime = "inherit" -projectile.expire_effect.inherit_velocity = "projectile" projectile.expire_effect.size = 3.0 +projectile.expire_effect.velocity_scale_parent = 1.0 diff --git a/crates/content/src/part/effect.rs b/crates/content/src/part/effect.rs index 1d9a078..6c550d2 100644 --- a/crates/content/src/part/effect.rs +++ b/crates/content/src/part/effect.rs @@ -1,11 +1,12 @@ use anyhow::{Context, Result}; -use serde::Deserialize; +use cgmath::Rad; use std::collections::HashMap; use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle}; pub(crate) mod syntax { use anyhow::{bail, Result}; + use cgmath::Deg; use serde::Deserialize; use crate::{Content, ContentBuildContext, EffectHandle}; @@ -15,9 +16,26 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] pub struct Effect { pub sprite: String, - pub lifetime: EffectLifetime, - pub inherit_velocity: super::ImpactInheritVelocity, pub size: f32, + pub size_rng: Option, + pub lifetime: TextOrFloat, + pub lifetime_rng: Option, + pub angle: Option, + pub angle_rng: Option, + pub angvel: Option, + pub angvel_rng: Option, + pub velocity_scale_parent: Option, + pub velocity_scale_parent_rng: Option, + pub velocity_scale_target: Option, + pub velocity_scale_target_rng: Option, + pub direction_rng: Option, + } + + #[derive(Debug, Deserialize)] + #[serde(untagged)] + pub enum TextOrFloat { + Text(String), + Float(f32), } // We implement building here instead of in super::Effect because @@ -34,8 +52,8 @@ pub(crate) mod syntax { }; let lifetime = match self.lifetime { - EffectLifetime::Seconds(s) => s, - EffectLifetime::Inherit(s) => { + TextOrFloat::Float(f) => f, + TextOrFloat::Text(s) => { if s == "inherit" { let sprite = content.get_sprite(sprite); sprite.fps * sprite.frames.len() as f32 @@ -49,24 +67,27 @@ pub(crate) mod syntax { index: content.effects.len(), }; content.effects.push(super::Effect { - sprite, - lifetime, handle, - inherit_velocity: self.inherit_velocity, + sprite, size: self.size, + size_rng: self.size_rng.unwrap_or(0.0), + lifetime, + lifetime_rng: self.lifetime_rng.unwrap_or(0.0), + angle: Deg(self.angle.unwrap_or(0.0) / 2.0).into(), + angle_rng: Deg(self.angle_rng.unwrap_or(0.0) / 2.0).into(), + angvel: Deg(self.angvel.unwrap_or(0.0)).into(), + angvel_rng: Deg(self.angle_rng.unwrap_or(0.0)).into(), + velocity_scale_parent: self.velocity_scale_parent.unwrap_or(0.0), + velocity_scale_parent_rng: self.velocity_scale_parent_rng.unwrap_or(0.0), + velocity_scale_target: self.velocity_scale_target.unwrap_or(0.0), + velocity_scale_target_rng: self.velocity_scale_target_rng.unwrap_or(0.0), + direction_rng: Deg(self.direction_rng.unwrap_or(0.0) / 2.0).into(), }); return Ok(handle); } } - #[derive(Debug, Deserialize)] - #[serde(untagged)] - pub enum EffectLifetime { - Inherit(String), - Seconds(f32), - } - // This isn't used here, but is pulled in by other content items. /// A reference to an effect by name, or an inline definition. #[derive(Debug, Deserialize)] @@ -95,43 +116,54 @@ pub(crate) mod syntax { } } -/// How we should set an effect's velocity -#[derive(Debug, Deserialize, Clone)] -pub enum ImpactInheritVelocity { - /// Don't inherit any velocity. - /// This impact particle will be still. - #[serde(rename = "don't")] - Dont, - - /// Inherit target velocity. - /// This impact particle will stick to the object it hits. - #[serde(rename = "target")] - Target, - - /// Inherit projectile velocity. - /// This impact particle will continue on its projectile's path. - #[serde(rename = "projectile")] - Projectile, -} - /// The particle a projectile will spawn when it hits something #[derive(Debug, Clone)] pub struct Effect { - /// The sprite to use for this particle. - /// This is most likely animated. - pub sprite: SpriteHandle, - /// This effect's handle pub handle: EffectHandle, + /// The sprite to use for this particle. + pub sprite: SpriteHandle, + + /// The height of this particle, in game units. + pub size: f32, + + /// Random size variation + pub size_rng: f32, + /// How many seconds this particle should live pub lifetime: f32, - /// How we should set this particle's velocity - pub inherit_velocity: ImpactInheritVelocity, + /// Random lifetime variation + pub lifetime_rng: f32, - /// The height of this particle, in game units. - pub size: f32, + /// The angle this particle points once spawned + pub angle: Rad, + + /// Random angle variation + pub angle_rng: Rad, + + /// How fast this particle spins + pub angvel: Rad, + + /// Random angvel variation + pub angvel_rng: Rad, + + /// The amount of this particle's parent's velocity to inherit + pub velocity_scale_parent: f32, + + /// Parent velocity random variation + pub velocity_scale_parent_rng: f32, + + /// The amount of this particle's parent's target velocity to inherit. + /// If there is no target, this is zero. + pub velocity_scale_target: f32, + + /// Target velocity random variation + pub velocity_scale_target_rng: f32, + + /// Travel direction random variation + pub direction_rng: Rad, } impl crate::Build for Effect { diff --git a/crates/content/src/part/mod.rs b/crates/content/src/part/mod.rs index 2debf4c..4c8a7c7 100644 --- a/crates/content/src/part/mod.rs +++ b/crates/content/src/part/mod.rs @@ -9,7 +9,7 @@ pub(crate) mod ship; pub(crate) mod sprite; pub(crate) mod system; -pub use effect::{Effect, ImpactInheritVelocity}; +pub use effect::Effect; pub use faction::{Faction, Relationship}; pub use gun::{Gun, Projectile, ProjectileCollider}; pub use outfit::Outfit; diff --git a/crates/world/src/objects/ship.rs b/crates/world/src/objects/ship.rs index 047fcda..ba572a6 100644 --- a/crates/world/src/objects/ship.rs +++ b/crates/world/src/objects/ship.rs @@ -100,16 +100,48 @@ impl ShipCollapseSequence { }; // Position, adjusted for ship rotation - let pos = - Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos; + let pos = ship_pos + + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos; + + let velocity = { + let a = self.rng.gen_range( + -effect.velocity_scale_parent_rng + ..=effect.velocity_scale_parent_rng, + ); + + let velocity = (effect.velocity_scale_parent + a) + * rigid_body.velocity_at_point(&point![pos.x, pos.y]); + + Matrix2::from_angle(Rad(self.rng.gen_range( + -effect.direction_rng.0..=effect.direction_rng.0, + ))) * Vector2 { + x: velocity.x, + y: velocity.y, + } + }; particles.push(ParticleBuilder { sprite: effect.sprite, - pos: ship_pos + pos, - velocity: Vector2::zero(), - angle: Deg::zero(), - lifetime: effect.lifetime, - size: effect.size, + pos, + velocity, + + angle: effect.angle + + Rad(self + .rng + .gen_range(-effect.angle_rng.0..=effect.angle_rng.0)), + + angvel: Rad(effect.angvel.0 + + self + .rng + .gen_range(-effect.angvel_rng.0..=effect.angvel_rng.0)), + + lifetime: effect.lifetime + + self + .rng + .gen_range(-effect.lifetime_rng..=effect.lifetime_rng), + + size: effect.size + + self.rng.gen_range(-effect.size_rng..=effect.size_rng), }); } } @@ -145,13 +177,15 @@ impl ShipCollapseSequence { }; // Position, adjusted for ship rotation - let pos = Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos; + let pos = ship_pos + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos; + let vel = rigid_body.velocity_at_point(&point![pos.x, pos.y]); particles.push(ParticleBuilder { sprite: effect.sprite, - pos: ship_pos + pos, - velocity: Vector2::zero(), - angle: Deg::zero(), + pos, + velocity: Vector2 { x: vel.x, y: vel.y }, + angle: Rad::zero(), + angvel: Rad::zero(), lifetime: effect.lifetime, size: effect.size, }); diff --git a/crates/world/src/world.rs b/crates/world/src/world.rs index 7f9bb0f..a20fd01 100644 --- a/crates/world/src/world.rs +++ b/crates/world/src/world.rs @@ -133,6 +133,7 @@ impl<'a> World { projectile_h: ColliderHandle, ship_h: ColliderHandle, ) { + let mut rng = rand::thread_rng(); let projectile = self.projectiles.get(&projectile_h); let ship = self.ships.get_mut(&ship_h); if projectile.is_none() || ship.is_none() { @@ -173,29 +174,38 @@ impl<'a> World { None => {} Some(x) => { let x = ct.get_effect(*x); - let velocity = match x.inherit_velocity { - content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 }, - content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(pr), - content::ImpactInheritVelocity::Target => { - // Match target ship velocity. - // Particles will fly off if the ship is spinning fast, but I - // haven't found a good way to fix that. - let (_, sr) = self.get_ship_body(s).unwrap(); - let velocity = - sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y)); - Vector2 { - x: velocity.x, - y: velocity.y, - } - } + + let velocity = { + let (_, sr) = self.get_ship_body(s).unwrap(); + let target_velocity = + sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y)); + let a = rng + .gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng); + let b = rng + .gen_range(-x.velocity_scale_target_rng..=x.velocity_scale_target_rng); + + let velocity = ((x.velocity_scale_parent + a) + * util::rigidbody_velocity(pr)) + + ((x.velocity_scale_target + b) + * Vector2 { + x: target_velocity.x, + y: target_velocity.y, + }); + + Matrix2::from_angle(Rad( + rng.gen_range(-x.direction_rng.0..=x.direction_rng.0) + )) * velocity }; + particles.push(ParticleBuilder { sprite: x.sprite, pos: Point2 { x: pos.x, y: pos.y }, velocity, - angle: -angle, - lifetime: x.lifetime, - size: x.size, + angle: Rad::from(-angle) + + Rad(rng.gen_range(-x.angle_rng.0..=x.angle_rng.0)), + angvel: Rad(x.angvel.0 + rng.gen_range(-x.angvel_rng.0..=x.angvel_rng.0)), + lifetime: x.lifetime + rng.gen_range(-x.lifetime_rng..=x.lifetime_rng), + size: x.size + rng.gen_range(-x.size_rng..=x.size_rng), }); } }; @@ -320,6 +330,8 @@ impl<'a> World { to_remove.push(*c); } } + + let mut rng = rand::thread_rng(); for c in to_remove { let (pr, p) = self.remove_projectile(c).unwrap(); @@ -328,22 +340,32 @@ impl<'a> World { Some(x) => { let x = ct.get_effect(*x); let pos = util::rigidbody_position(&pr); + let vel = util::rigidbody_velocity(&pr); let angle: Deg = util::rigidbody_rotation(&pr) .angle(Vector2 { x: 1.0, y: 0.0 }) .into(); - let velocity = match x.inherit_velocity { - content::ImpactInheritVelocity::Target - | content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 }, - content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(&pr), + let velocity = { + let a = rng + .gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng); + + let velocity = (x.velocity_scale_parent + a) * vel; + + velocity + //Matrix2::from_angle(Rad( + // rng.gen_range(-x.direction_rng.0..=x.direction_rng.0) + //)) * velocity }; + particles.push(ParticleBuilder { sprite: x.sprite, pos: Point2 { x: pos.x, y: pos.y }, velocity, - angle: -angle, - lifetime: x.lifetime, - size: x.size, + angle: Rad::from(-angle) + + x.angle + Rad(rng.gen_range(-x.angle_rng.0..=x.angle_rng.0)), + angvel: Rad(x.angvel.0 + rng.gen_range(-x.angvel_rng.0..=x.angvel_rng.0)), + lifetime: x.lifetime + rng.gen_range(-x.lifetime_rng..=x.lifetime_rng), + size: x.size + rng.gen_range(-x.size_rng..=x.size_rng), }); } };