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 shadow_engine_2d::prelude::*;
use winit::keyboard::KeyCode;

fn main() {
    env_logger::init();

    let mut engine = Engine::builder()
        .title("Shadow Engine 2D - Particles & Camera Demo")
        .size(1280, 720)
        .build();

    let mut particle_system = ParticleSystem::new();
    let physics = PhysicsSystem::new();

    // Create camera
    let camera_entity = engine.world_mut().spawn();
    if let Some(entity) = engine.world_mut().entity_mut(camera_entity) {
        entity.add(Camera::new(1280.0, 720.0).with_zoom(1.0));
    }

    // Spawn player
    let player = engine.world_mut().spawn();
    if let Some(entity) = engine.world_mut().entity_mut(player) {
        entity.add(Transform::new(0.0, -200.0));
        entity.add(Sprite::new(50.0, 50.0).with_color(Color::CYAN));
        entity.add(RigidBody::dynamic().with_drag(0.5));
        entity.add(Collider::box_collider(50.0, 50.0));
    }

    // Spawn platforms
    let platforms = vec![
        (-400.0, 250.0, 200.0, 30.0),
        (-100.0, 150.0, 200.0, 30.0),
        (200.0, 50.0, 200.0, 30.0),
        (-200.0, -50.0, 600.0, 30.0),
    ];

    for (x, y, w, h) in platforms {
        let platform = engine.world_mut().spawn();
        if let Some(entity) = engine.world_mut().entity_mut(platform) {
            entity.add(Transform::new(x, y));
            entity.add(Sprite::new(w, h).with_color(Color::GREEN));
            entity.add(Collider::box_collider(w, h));
        }
    }

    // Spawn walls
    let walls = vec![
        (-640.0, 0.0, 20.0, 720.0),
        (640.0, 0.0, 20.0, 720.0),
        (0.0, 360.0, 1280.0, 20.0),
    ];

    for (x, y, w, h) in walls {
        let wall = engine.world_mut().spawn();
        if let Some(entity) = engine.world_mut().entity_mut(wall) {
            entity.add(Transform::new(x, y));
            entity.add(Sprite::new(w, h).with_color(Color::rgb(0.3, 0.3, 0.3)));
            entity.add(Collider::box_collider(w, h));
        }
    }

    // Create particle emitters
    let fire_emitter_entity = engine.world_mut().spawn();
    if let Some(entity) = engine.world_mut().entity_mut(fire_emitter_entity) {
        entity.add(Transform::new(-300.0, 200.0));
        entity.add(ParticleEmitter::fire(Vec2::new(-300.0, 200.0)));
    }

    let sparkle_emitter_entity = engine.world_mut().spawn();
    if let Some(entity) = engine.world_mut().entity_mut(sparkle_emitter_entity) {
        entity.add(Transform::new(300.0, 100.0));
        entity.add(ParticleEmitter::sparkles(Vec2::new(300.0, 100.0)));
    }

    let mut explosion_timer = 0.0;
    let mut player_trail_emitter = ParticleEmitter::trail(Vec2::ZERO);

    engine.run(move |world, input, time| {
        let dt = time.delta();
        let move_force = 5000.0;
        let jump_impulse = 400.0;

        // Get player position for camera and trail
        let mut player_pos = Vec2::ZERO;
        if let Some(player_entity) = world.entity(player) {
            if let Some(transform) = player_entity.get::<Transform>() {
                player_pos = transform.position;
            }
        }

        // Update camera to follow player
        if let Some(camera_entity) = world.entity_mut(camera_entity) {
            if let Some(camera) = camera_entity.get_mut::<Camera>() {
                camera.follow(player_pos, 5.0, dt);

                // Zoom controls
                if input.key_pressed(KeyCode::Equal) || input.key_pressed(KeyCode::NumpadAdd) {
                    camera.zoom = (camera.zoom + dt * 2.0).min(3.0);
                }
                if input.key_pressed(KeyCode::Minus) || input.key_pressed(KeyCode::NumpadSubtract) {
                    camera.zoom = (camera.zoom - dt * 2.0).max(0.5);
                }
            }
        }

        // Player controls
        let mut should_spawn_explosion = false;
        for entity in world.entities_mut() {
            if let Some(sprite) = entity.get::<Sprite>() {
                if sprite.color.b > 0.9 && sprite.color.r < 0.1 {
                    if let Some(body) = entity.get_mut::<RigidBody>() {
                        if input.key_pressed(KeyCode::KeyA) || input.key_pressed(KeyCode::ArrowLeft) {
                            body.apply_force(Vec2::new(-move_force, 0.0));
                        }
                        if input.key_pressed(KeyCode::KeyD) || input.key_pressed(KeyCode::ArrowRight) {
                            body.apply_force(Vec2::new(move_force, 0.0));
                        }
                        if input.key_just_pressed(KeyCode::Space) || input.key_just_pressed(KeyCode::KeyW) {
                            body.apply_impulse(Vec2::new(0.0, -jump_impulse));
                        }

                        // Spawn explosion on E key
                        if input.key_just_pressed(KeyCode::KeyE) {
                            should_spawn_explosion = true;
                        }
                    }
                }
            }
        }

        // Spawn explosion at player position
        if should_spawn_explosion {
            let explosion = world.spawn();
            if let Some(entity) = world.entity_mut(explosion) {
                entity.add(Transform::new(player_pos.x, player_pos.y));
                entity.add(ParticleEmitter::explosion(player_pos));
            }
        }

        // Update player trail emitter position
        player_trail_emitter.position = player_pos;

        // Collect all emitters
        let mut emitters_vec: Vec<&mut ParticleEmitter> = Vec::new();
        for entity in world.entities_mut() {
            if let Some(emitter) = entity.get_mut::<ParticleEmitter>() {
                emitters_vec.push(emitter);
            }
        }
        emitters_vec.push(&mut player_trail_emitter);

        // Update particle system
        particle_system.update(&mut emitters_vec, dt);

        // Update physics
        physics.update(world, dt);
        let collisions = physics.detect_collisions(world);
        physics.resolve_collisions(world, &collisions);

        // Render particles as sprites (temporary visualization)
        // In a real implementation, you'd render these directly in the renderer
        // For now, we'll just track the count
        if time.elapsed() - explosion_timer > 0.1 {
            println!("Active particles: {}", particle_system.particle_count());
            explosion_timer = time.elapsed();
        }
    });
}