Skip to main content

particles/
particles.rs

1//! Particle system example.
2//!
3//! Run with: `cargo run --example particles`
4
5use game_gem::prelude::*;
6
7struct ParticlesExample {
8    emitters: Vec<ParticleEmitter>,
9}
10
11impl GameState for ParticlesExample {
12    fn on_enter(&mut self, _ctx: &mut Context) {
13        // Fire emitter at bottom center
14        let mut fire = ParticleEmitter::new(vec2(400.0, 500.0));
15        fire.configure(|cfg| {
16            cfg
17                .rate(80.0)
18                .shape(EmitterShape::Line { x1: 370.0, y1: 500.0, x2: 430.0, y2: 500.0 })
19                .speed(80.0, 200.0)
20                .angle(std::f32::consts::PI * 1.1, std::f32::consts::PI * 1.9)
21                .size(3.0, 10.0)
22                .lifetime(0.3, 1.0)
23                .gravity(0.0, -80.0)
24                .color_start(Color::from_hex("#FF6600").unwrap())
25                .color_end(Color::from_hex("#FFFF00").unwrap())
26                .size_end(0.0)
27                .drag(1.5);
28        });
29        self.emitters.push(fire);
30
31        // Sparkle emitter
32        let mut sparkles = ParticleEmitter::new(vec2(400.0, 300.0));
33        sparkles.configure(|cfg| {
34            cfg
35                .rate(30.0)
36                .shape(EmitterShape::Circle { cx: 400.0, cy: 300.0, radius: 150.0 })
37                .speed(10.0, 50.0)
38                .angle(0.0, std::f32::consts::TAU)
39                .size(2.0, 6.0)
40                .lifetime(0.5, 1.5)
41                .gravity(0.0, 20.0)
42                .color_start(Color::from_hex("#FFFFFF").unwrap())
43                .color_end(Color::TRANSPARENT)
44                .size_end(0.0);
45        });
46        self.emitters.push(sparkles);
47    }
48
49    fn update(&mut self, ctx: &mut Context) {
50        let dt = ctx.time.delta() as f32;
51
52        // Move fire emitter to mouse X
53        self.emitters[0].position.x = ctx.input.mouse.position.x;
54        self.emitters[0].position.y = ctx.screen_height() - 50.0;
55
56        // Burst on click
57        if ctx.input.mouse.is_pressed(MouseButton::Left) {
58            self.emitters[1].position = ctx.input.mouse.position;
59            self.emitters[1].burst_now(50);
60        }
61
62        // Update all emitters
63        for emitter in &mut self.emitters {
64            emitter.update(dt);
65        }
66
67        if ctx.input.keyboard.is_pressed(KeyCode::Escape) {
68            ctx.quit();
69        }
70    }
71
72    fn render(&mut self, ctx: &mut Context) {
73        ctx.graphics.clear(Color::from_hex("#0A0A1A").unwrap());
74
75        // Draw particles
76        for emitter in &self.emitters {
77            for particle in emitter.particles() {
78                if !particle.alive {
79                    continue;
80                }
81                ctx.graphics.draw_circle(
82                    particle.position.x,
83                    particle.position.y,
84                    particle.size,
85                    particle.color,
86                );
87            }
88        }
89
90        // HUD
91        ctx.graphics.draw_text(
92            &format!("Particles: {}", self.emitters.iter().map(|e| e.alive_count()).sum::<usize>()),
93            10.0, 10.0, 18.0, Color::WHITE,
94        );
95        ctx.graphics.draw_text(
96            "Move mouse = fire | Click = sparkles | Esc = quit",
97            10.0, 35.0, 14.0, Color::LIGHT_GRAY,
98        );
99    }
100}
101
102fn main() {
103    let example = ParticlesExample {
104        emitters: Vec::new(),
105    };
106
107    Game::new()
108        .window_title("game-gem: Particles Example")
109        .window_size(800, 600)
110        .clear_color(Color::from_hex("#0A0A1A").unwrap())
111        .run(example);
112}