use bevy::{prelude::*, sprite::Mesh2dHandle}; use rand::Rng; use rand_distr::{Distribution, UnitCircle}; #[cfg(not(target_arch = "wasm32"))] pub const POOL_COUNT: usize = 100; #[cfg(target_arch = "wasm32")] pub const POOL_COUNT: usize = 50; pub const MIN_VELOCITY: f32 = 10.0; pub const MAX_VELOCITY: f32 = 100.0; pub const _VELOCITY_SCALE: f32 = 0.1; pub const PARTICLE_EFFECT_RADIUS: f32 = 70.0; pub struct ParticleEffectPlugin; impl Plugin for ParticleEffectPlugin { fn build(&self, app: &mut App) { app.init_resource::<ParticleMesh>() .insert_resource(ParticleEffectResource::new(Vec3::new( 10000.0, 10000.0, 0.0, ))) .add_startup_system(particle_effect_startup) .add_system(particle_effect_system); } } pub struct ParticleMesh { square: Mesh2dHandle, } impl FromWorld for ParticleMesh { fn from_world(world: &mut World) -> Self { let mut meshes = world.get_resource_mut::<Assets<Mesh>>().unwrap(); Self { square: meshes .add(Mesh::from(shape::Quad { size: Vec2 { x: 4.0, y: 4.0 }, flip: false, })) .into(), } } } #[derive(bevy_inspector_egui::Inspectable)] pub struct ParticleEffectResource { pub translation: Vec3, pub prev_translation: Vec3, pub radius_squared: f32, pub color: Color, } impl ParticleEffectResource { fn new(init_translation: Vec3) -> Self { Self { translation: init_translation, prev_translation: init_translation, radius_squared: PARTICLE_EFFECT_RADIUS * PARTICLE_EFFECT_RADIUS, color: Color::WHITE, } } } #[derive(Default, Component)] pub struct ParticleComponent { pub velocity: Vec3, } impl ParticleComponent { pub fn new() -> Self { let mut particle_component: Self = Self::default(); particle_component.randomize_velocity(MIN_VELOCITY, MAX_VELOCITY); particle_component } pub fn randomize_velocity(&mut self, min_velocity: f32, max_velocity: f32) { let random_direction: [f32; 2] = UnitCircle.sample(&mut rand::thread_rng()); let random_magnitude: f32 = rand::thread_rng().gen_range(min_velocity..max_velocity); self.velocity = Vec3::new(random_direction[0], random_direction[1], 0.0) * random_magnitude; } } fn particle_effect_startup( mut commands: Commands, particle_mesh: Res<ParticleMesh>, mut materials: ResMut<Assets<ColorMaterial>>, ) { for _p in 0..POOL_COUNT { let color_mesh = ColorMesh2dBundle { mesh: particle_mesh.square.clone(), material: materials.add(ColorMaterial::from(Color::WHITE)), ..default() }; commands .spawn_bundle(color_mesh) .insert(ParticleComponent::new()); } } fn particle_effect_system( mut materials: ResMut<Assets<ColorMaterial>>, mut query: Query<( &mut Transform, &mut ParticleComponent, &Handle<ColorMaterial>, )>, mut particle_effect: ResMut<ParticleEffectResource>, time: Res<Time>, ) { let delta_seconds: f32 = f32::max(0.001, time.delta_seconds()); let delta_position: Vec3 = particle_effect.translation - particle_effect.prev_translation; // let overall_velocity: Vec3 = delta_position / delta_seconds; particle_effect.prev_translation = particle_effect.translation; for (mut transform, mut particle_component, color_material) in query.iter_mut() { // particle_component.velocity -= overall_velocity * VELOCITY_SCALE; transform.translation += particle_component.velocity * delta_seconds + delta_position; let squared_distance: f32 = transform .translation .distance_squared(particle_effect.translation); if squared_distance > particle_effect.radius_squared { transform.translation = particle_effect.translation; particle_component.randomize_velocity(MIN_VELOCITY, MAX_VELOCITY); } if let Some(material) = materials.get_mut(color_material) { material.color = particle_effect.color; material.color.set_a( (particle_effect.radius_squared - squared_distance) / particle_effect.radius_squared, ); } } }