diff --git a/README.md b/README.md index c512d8b..de89f0e 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ fn spawn_particle_system(mut commands: Commands, asset_server: Res) .spawn(ParticleSystemBundle { particle_system: ParticleSystem { max_particles: 10_000, - texture: ParticuleTexture::Sprite(asset_server.load("my_particle.png")), + texture: ParticleTexture::Sprite(asset_server.load("my_particle.png")), spawn_rate_per_second: 25.0.into(), initial_speed: JitteredValue::jittered(3.0, -1.0..1.0), lifetime: JitteredValue::jittered(8.0, -2.0..2.0), diff --git a/examples/directional.rs b/examples/directional.rs index 5e393c5..56525dc 100644 --- a/examples/directional.rs +++ b/examples/directional.rs @@ -7,8 +7,8 @@ use bevy::{ use bevy_app::PluginGroup; use bevy_asset::AssetServer; use bevy_particle_systems::{ - JitteredValue, ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, - Playing, + CircleSegment, JitteredValue, ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, + ParticleTexture, Playing, }; fn main() { @@ -38,11 +38,12 @@ fn startup_system(mut commands: Commands, asset_server: Res) { spawn_rate_per_second: 25.0.into(), initial_speed: JitteredValue::jittered(70.0, -3.0..3.0), lifetime: JitteredValue::jittered(5.0, -1.0..1.0), - emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment { + emitter_shape: CircleSegment { radius: 10.0.into(), opening_angle: std::f32::consts::PI, direction_angle: std::f32::consts::PI / 2.0, - }, + } + .into(), looping: true, scale: 0.07.into(), system_duration_seconds: 5.0, diff --git a/examples/local_space.rs b/examples/local_space.rs index 06e6224..7874330 100644 --- a/examples/local_space.rs +++ b/examples/local_space.rs @@ -12,8 +12,8 @@ use bevy_math::Quat; use bevy_time::Time; use bevy_particle_systems::{ - ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace, ParticleSystem, - ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing, + CircleSegment, ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace, + ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing, }; #[derive(Debug, Component)] @@ -39,11 +39,11 @@ fn startup_system(mut commands: Commands, asset_server: Res) { .spawn(ParticleSystemBundle { particle_system: ParticleSystem { max_particles: 500, - emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment { + emitter_shape: CircleSegment { opening_angle: std::f32::consts::PI * 0.25, - direction_angle: 0.0, - radius: 0.0.into(), - }, + ..Default::default() + } + .into(), texture: ParticleTexture::Sprite(asset_server.load("px.png")), spawn_rate_per_second: 35.0.into(), initial_speed: JitteredValue::jittered(25.0, 0.0..5.0), @@ -70,11 +70,12 @@ fn startup_system(mut commands: Commands, asset_server: Res) { .spawn(ParticleSystemBundle { particle_system: ParticleSystem { max_particles: 500, - emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment { + emitter_shape: CircleSegment { opening_angle: std::f32::consts::PI * 0.25, direction_angle: std::f32::consts::PI, - radius: 0.0.into(), - }, + ..Default::default() + } + .into(), texture: ParticleTexture::Sprite(asset_server.load("px.png")), spawn_rate_per_second: 35.0.into(), initial_speed: JitteredValue::jittered(25.0, 0.0..5.0), diff --git a/examples/shape_emitter.rs b/examples/shape_emitter.rs index 2326c6f..f85ab4a 100644 --- a/examples/shape_emitter.rs +++ b/examples/shape_emitter.rs @@ -8,8 +8,8 @@ use bevy_app::PluginGroup; use bevy_asset::AssetServer; use bevy_particle_systems::{ - ColorOverTime, ColorPoint, EmitterShape, Gradient, JitteredValue, ParticleSystem, - ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing, + CircleSegment, ColorOverTime, ColorPoint, EmitterShape, Gradient, JitteredValue, + ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing, }; fn main() { @@ -46,10 +46,7 @@ fn startup_system(mut commands: Commands, asset_server: Res) { ColorPoint::new(Color::RED, 0.5), ColorPoint::new(Color::rgba(0.0, 0.0, 1.0, 0.0), 1.0), ])), - emitter_shape: EmitterShape::Line { - length: 200.0, - angle: std::f32::consts::FRAC_PI_4.into(), - }, + emitter_shape: EmitterShape::line(200.0, std::f32::consts::FRAC_PI_4), looping: true, rotate_to_movement_direction: true, initial_rotation: (-90.0_f32).to_radians().into(), @@ -76,11 +73,12 @@ fn startup_system(mut commands: Commands, asset_server: Res) { ColorPoint::new(Color::RED, 0.5), ColorPoint::new(Color::rgba(0.0, 0.0, 1.0, 0.0), 1.0), ])), - emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment { + emitter_shape: CircleSegment { radius: 10.0.into(), opening_angle: std::f32::consts::PI, direction_angle: std::f32::consts::FRAC_PI_4, - }, + } + .into(), looping: true, rotate_to_movement_direction: true, initial_rotation: (-90.0_f32).to_radians().into(), diff --git a/examples/time_scaling.rs b/examples/time_scaling.rs index 513cc99..4062b6f 100644 --- a/examples/time_scaling.rs +++ b/examples/time_scaling.rs @@ -10,8 +10,8 @@ use bevy::{ }; use bevy_asset::AssetServer; use bevy_particle_systems::{ - ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace, ParticleSystem, - ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing, + CircleSegment, ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace, + ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing, }; use bevy_time::Time; fn main() { @@ -30,11 +30,12 @@ fn startup_system(mut commands: Commands, asset_server: Res) { .spawn(ParticleSystemBundle { particle_system: ParticleSystem { max_particles: 500, - emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment { + emitter_shape: CircleSegment { direction_angle: 0.0, opening_angle: std::f32::consts::PI * 0.25, radius: 0.0.into(), - }, + } + .into(), texture: ParticleTexture::Sprite(asset_server.load("px.png")), spawn_rate_per_second: 35.0.into(), initial_speed: JitteredValue::jittered(25.0, 0.0..5.0), @@ -61,11 +62,12 @@ fn startup_system(mut commands: Commands, asset_server: Res) { .spawn(ParticleSystemBundle { particle_system: ParticleSystem { max_particles: 500, - emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment { + emitter_shape: CircleSegment { opening_angle: std::f32::consts::PI * 0.25, direction_angle: std::f32::consts::PI, radius: 0.0.into(), - }, + } + .into(), texture: ParticleTexture::Sprite(asset_server.load("px.png")), spawn_rate_per_second: 35.0.into(), initial_speed: JitteredValue::jittered(25.0, 0.0..5.0), diff --git a/src/components.rs b/src/components.rs index a658faf..a4a3d7c 100644 --- a/src/components.rs +++ b/src/components.rs @@ -174,11 +174,7 @@ impl Default for ParticleSystem { texture: ParticleTexture::Sprite(Handle::default()), rescale_texture: None, spawn_rate_per_second: 5.0.into(), - emitter_shape: EmitterShape::CircleSegment { - opening_angle: std::f32::consts::TAU, - direction_angle: 0.0, - radius: 0.0.into(), - }, + emitter_shape: EmitterShape::default(), initial_speed: 1.0.into(), acceleration: 0.0.into(), lifetime: 5.0.into(), diff --git a/src/values.rs b/src/values.rs index 737440e..00c2aa2 100644 --- a/src/values.rs +++ b/src/values.rs @@ -8,43 +8,124 @@ use bevy_transform::prelude::Transform; use rand::seq::SliceRandom; use rand::{prelude::ThreadRng, Rng}; +/// Describes an oriented segment of a circle with a given radius. +#[derive(Debug, Clone, Reflect, FromReflect)] +pub struct CircleSegment { + /// The shape of the emitter, defined in radians. + /// + /// The default is `2 * PI`, which results particles going in all directions in a circle. + /// Reducing the value reduces the possible emitting directions. [`std::f32::consts::PI`] will emit particles + /// in a semi-circle. + pub opening_angle: f32, + + /// The rotation angle of the emitter, defined in radian. + /// + /// Zero indicates straight to the right in the X direction. [`std::f32::consts::PI`] indicates straight left in the X direction. + pub direction_angle: f32, + + /// The radius around the particle systems location that particles will spawn in. + /// + /// Setting this to zero will make all particles start at the same position. + /// Setting this to a non-jittered constant will make particles spawn exactly that distance away from the + /// center position. Jitter will allow particles to spawn in a range. + pub radius: JitteredValue, +} + +impl Default for CircleSegment { + fn default() -> Self { + Self { + opening_angle: std::f32::consts::TAU, + direction_angle: 0.0, + radius: 0.0.into(), + } + } +} + +impl From for EmitterShape { + fn from(segment: CircleSegment) -> EmitterShape { + EmitterShape::CircleSegment(segment) + } +} + +/// Defines a line along which particles will be spawned. +#[derive(Debug, Clone, Reflect, FromReflect)] +pub struct Line { + /// The lenth of the line + pub length: f32, + + /// The rotation angle of the emitter, defined in radian. + /// + /// Zero indicates straight to the right in the +X direction. [`std::f32::consts::PI`] indicates straight left in the -X direction. + pub angle: JitteredValue, +} + +impl Default for Line { + fn default() -> Self { + Self { + length: 1.0, + angle: 0.0.into(), + } + } +} + +impl From for EmitterShape { + fn from(line: Line) -> EmitterShape { + EmitterShape::Line(line) + } +} + /// Describes the shape on which new particles get spawned +/// +/// For convenience, these can also be created directly from +/// [`CircleSegment`] and [`Line`] instances, or using [`EmitterShape::line`] or +/// [`EmitterShape::circle`] +/// +/// # Examples +/// +/// ```rust +/// # use bevy_particle_systems::values::{CircleSegment, EmitterShape, Line}; +/// # use bevy_particle_systems::ParticleSystem; +/// let particle_system = ParticleSystem { +/// emitter_shape: CircleSegment::default().into(), +/// // ... +/// ..Default::default() +/// }; +/// ``` #[derive(Debug, Clone, Reflect, FromReflect)] pub enum EmitterShape { - /// A oriented segment of a circle at a given radius - CircleSegment { - /// The shape of the emitter, defined in radian. - /// - /// The default is [`std::f32::consts::TAU`], which results particles going in all directions in a circle. - /// Reducing the value reduces the possible emitting directions. [`std::f32::consts::PI`] will emit particles - /// in a semi-circle. - opening_angle: f32, - - /// The rotation angle of the emitter, defined in radian. - /// - /// Zero indicates straight to the right in the X direction. [`std::f32::consts::PI`] indicates straight left in the X direction. - direction_angle: f32, - - /// The radius around the particle systems location that particles will spawn in. - /// - /// Setting this to zero will make all particles start at the same position. - /// Setting this to a non-jittered constant will make particles spawn exactly that distance away from the - /// center position. Jitter will allow particles to spawn in a range. - radius: JitteredValue, - }, + /// An oriented segment of a circle with a given radius + CircleSegment(CircleSegment), /// Emit particles from a 2d line at an angle - Line { - /// The lenth of the line - length: f32, - - /// The rotation angle of the emitter, defined in radian. - /// - /// Zero indicates straight to the right in the +X direction. [`std::f32::consts::PI`] indicates straight left in the -X direction. - angle: JitteredValue, - }, + Line(Line), } impl EmitterShape { + /// Defines a circular emitter with the specified radius. + /// + /// See [`CircleSegment`] for more details. + pub fn circle(radius: T) -> Self + where + T: Into, + { + Self::CircleSegment(CircleSegment { + radius: radius.into(), + ..Default::default() + }) + } + + /// Creates a new Line emitter with the specified length and angle in radian. + /// + /// See [`Line`] for more details. + pub fn line(length: f32, angle: T) -> Self + where + T: Into, + { + Self::Line(Line { + length, + angle: angle.into(), + }) + } + /// Samples a random starting transform from the Emitter shape /// /// The returned transform describes the position and direction of movement of the newly spawned particle. @@ -52,18 +133,18 @@ impl EmitterShape { /// `rotate_to_movement_direction` is false.) pub fn sample(&self, rng: &mut ThreadRng) -> Transform { match self { - EmitterShape::CircleSegment { + EmitterShape::CircleSegment(CircleSegment { opening_angle, radius, direction_angle, - } => { + }) => { let radian: f32 = rng.gen_range(-0.5..0.5) * opening_angle + direction_angle; let direction = Vec3::new(radian.cos(), radian.sin(), 0.0); let delta = direction * radius.get_value(rng); Transform::from_translation(delta).with_rotation(Quat::from_rotation_z(radian)) } - EmitterShape::Line { length, angle } => { + EmitterShape::Line(Line { length, angle }) => { let angle = angle.get_value(rng); let distance: f32 = rng.gen_range(-0.5..0.5) * length; @@ -76,6 +157,12 @@ impl EmitterShape { } } +impl Default for EmitterShape { + fn default() -> Self { + Self::CircleSegment(CircleSegment::default()) + } +} + /// A value that will be chosen from a set of possible values when read. /// /// ## Examples