shadow_engine_2d 2.0.1

A modern, high-performance 2D game engine built in Rust with ECS, physics, particles, audio, and more
Documentation
use crate::ecs::Component;
use crate::math::{Color, Vec2};

#[derive(Clone, Debug)]
pub struct Particle {
    pub position: Vec2,
    pub velocity: Vec2,
    pub acceleration: Vec2,
    pub color: Color,
    pub size: f32,
    pub lifetime: f32,
    pub max_lifetime: f32,
    pub fade_out: bool,
}

impl Particle {
    pub fn new(position: Vec2, velocity: Vec2, color: Color, lifetime: f32) -> Self {
        Self {
            position,
            velocity,
            acceleration: Vec2::ZERO,
            color,
            size: 4.0,
            lifetime,
            max_lifetime: lifetime,
            fade_out: true,
        }
    }

    pub fn update(&mut self, dt: f32) -> bool {
        self.lifetime -= dt;
        if self.lifetime <= 0.0 {
            return false; // Particle is dead
        }

        self.velocity += self.acceleration * dt;
        self.position += self.velocity * dt;

        // Fade out
        if self.fade_out {
            let life_ratio = self.lifetime / self.max_lifetime;
            self.color.a = life_ratio;
        }

        true // Particle is alive
    }
}

#[derive(Clone, Debug)]
pub struct ParticleEmitter {
    pub position: Vec2,
    pub emission_rate: f32,
    pub particle_lifetime: f32,
    pub velocity_min: Vec2,
    pub velocity_max: Vec2,
    pub color_start: Color,
    pub color_end: Color,
    pub size_min: f32,
    pub size_max: f32,
    pub gravity: Vec2,
    pub spread_angle: f32,
    pub direction: Vec2,
    pub burst_count: u32,
    pub is_burst: bool,
    pub loop_emission: bool,
    pub active: bool,
    emission_timer: f32,
}

impl ParticleEmitter {
    pub fn new(position: Vec2) -> Self {
        Self {
            position,
            emission_rate: 10.0,
            particle_lifetime: 1.0,
            velocity_min: Vec2::new(-50.0, -50.0),
            velocity_max: Vec2::new(50.0, 50.0),
            color_start: Color::WHITE,
            color_end: Color::WHITE,
            size_min: 4.0,
            size_max: 8.0,
            gravity: Vec2::new(0.0, 100.0),
            spread_angle: std::f32::consts::PI * 2.0,
            direction: Vec2::new(0.0, -1.0),
            burst_count: 0,
            is_burst: false,
            loop_emission: true,
            active: true,
            emission_timer: 0.0,
        }
    }

    pub fn with_rate(mut self, rate: f32) -> Self {
        self.emission_rate = rate;
        self
    }

    pub fn with_lifetime(mut self, lifetime: f32) -> Self {
        self.particle_lifetime = lifetime;
        self
    }

    pub fn with_velocity(mut self, min: Vec2, max: Vec2) -> Self {
        self.velocity_min = min;
        self.velocity_max = max;
        self
    }

    pub fn with_color(mut self, start: Color, end: Color) -> Self {
        self.color_start = start;
        self.color_end = end;
        self
    }

    pub fn with_size(mut self, min: f32, max: f32) -> Self {
        self.size_min = min;
        self.size_max = max;
        self
    }

    pub fn with_gravity(mut self, gravity: Vec2) -> Self {
        self.gravity = gravity;
        self
    }

    pub fn with_direction(mut self, direction: Vec2, spread: f32) -> Self {
        self.direction = direction.normalize();
        self.spread_angle = spread;
        self
    }

    pub fn as_burst(mut self, count: u32) -> Self {
        self.is_burst = true;
        self.burst_count = count;
        self.loop_emission = false;
        self
    }

    pub fn emit_particle(&self) -> Particle {
        use rand::Rng;
        let mut rng = rand::thread_rng();

        // Random velocity within cone
        let angle = if self.spread_angle > 0.0 {
            let base_angle = self.direction.y.atan2(self.direction.x);
            base_angle + rng.gen_range(-self.spread_angle / 2.0..self.spread_angle / 2.0)
        } else {
            self.direction.y.atan2(self.direction.x)
        };

        let speed = rng.gen_range(
            self.velocity_min.length()..self.velocity_max.length()
        );
        let velocity = Vec2::new(angle.cos(), angle.sin()) * speed;

        let size = rng.gen_range(self.size_min..self.size_max);
        let color = self.color_start; // Can interpolate between start and end

        let mut particle = Particle::new(self.position, velocity, color, self.particle_lifetime);
        particle.size = size;
        particle.acceleration = self.gravity;

        particle
    }
}

impl Component for ParticleEmitter {}

pub struct ParticleSystem {
    particles: Vec<Particle>,
}

impl ParticleSystem {
    pub fn new() -> Self {
        Self {
            particles: Vec::new(),
        }
    }

    pub fn update(&mut self, emitters: &mut [&mut ParticleEmitter], dt: f32) {
        // Update existing particles
        self.particles.retain_mut(|p| p.update(dt));

        // Emit new particles from emitters
        for emitter in emitters {
            if !emitter.active {
                continue;
            }

            if emitter.is_burst {
                // Burst emission
                if emitter.burst_count > 0 {
                    for _ in 0..emitter.burst_count {
                        self.particles.push(emitter.emit_particle());
                    }
                    emitter.burst_count = 0;
                    if !emitter.loop_emission {
                        emitter.active = false;
                    }
                }
            } else {
                // Continuous emission
                emitter.emission_timer += dt;
                let emission_interval = 1.0 / emitter.emission_rate;

                while emitter.emission_timer >= emission_interval {
                    self.particles.push(emitter.emit_particle());
                    emitter.emission_timer -= emission_interval;
                }
            }
        }
    }

    pub fn particles(&self) -> &[Particle] {
        &self.particles
    }

    pub fn particle_count(&self) -> usize {
        self.particles.len()
    }

    pub fn clear(&mut self) {
        self.particles.clear();
    }
}

impl Default for ParticleSystem {
    fn default() -> Self {
        Self::new()
    }
}

// Preset particle effects
impl ParticleEmitter {
    pub fn explosion(position: Vec2) -> Self {
        Self::new(position)
            .with_rate(100.0)
            .with_lifetime(0.5)
            .with_velocity(Vec2::new(100.0, 100.0), Vec2::new(300.0, 300.0))
            .with_color(Color::rgb(1.0, 0.5, 0.0), Color::rgb(1.0, 0.0, 0.0))
            .with_size(6.0, 12.0)
            .with_gravity(Vec2::new(0.0, 200.0))
            .as_burst(50)
    }

    pub fn fire(position: Vec2) -> Self {
        Self::new(position)
            .with_rate(30.0)
            .with_lifetime(1.0)
            .with_direction(Vec2::new(0.0, -1.0), 0.5)
            .with_velocity(Vec2::new(20.0, 50.0), Vec2::new(40.0, 100.0))
            .with_color(Color::rgb(1.0, 0.8, 0.0), Color::rgb(1.0, 0.2, 0.0))
            .with_size(4.0, 8.0)
            .with_gravity(Vec2::new(0.0, -50.0))
    }

    pub fn smoke(position: Vec2) -> Self {
        Self::new(position)
            .with_rate(15.0)
            .with_lifetime(2.0)
            .with_direction(Vec2::new(0.0, -1.0), 0.3)
            .with_velocity(Vec2::new(10.0, 30.0), Vec2::new(20.0, 60.0))
            .with_color(Color::rgb(0.5, 0.5, 0.5), Color::rgb(0.3, 0.3, 0.3))
            .with_size(6.0, 12.0)
            .with_gravity(Vec2::new(0.0, -20.0))
    }

    pub fn sparkles(position: Vec2) -> Self {
        Self::new(position)
            .with_rate(20.0)
            .with_lifetime(0.8)
            .with_velocity(Vec2::new(50.0, 50.0), Vec2::new(150.0, 150.0))
            .with_color(Color::rgb(1.0, 1.0, 0.5), Color::WHITE)
            .with_size(2.0, 4.0)
            .with_gravity(Vec2::new(0.0, 100.0))
    }

    pub fn trail(position: Vec2) -> Self {
        Self::new(position)
            .with_rate(50.0)
            .with_lifetime(0.3)
            .with_velocity(Vec2::ZERO, Vec2::new(10.0, 10.0))
            .with_color(Color::CYAN, Color::BLUE)
            .with_size(3.0, 6.0)
            .with_gravity(Vec2::ZERO)
    }
}