GPU Particles 2D¶
A GPU-driven particle fountain with gravity and colour fade.
▶ Run in browserTags: 2d particles gpu effects
What it demonstrates¶
GPUParticles2D: particle state lives on the GPU, simulated by a compute shader
Emission tuning: amount, lifetime, speed, speed_variance, spread, direction
gravity pulling particles back down to form a fountain arc
start_colour / end_colour fade over each particle’s lifetime
Toggling emitting on/off and switching emission_shape at runtime
In 2D the Y axis points down, so direction (0, -1, 0) emits upward and a positive-Y gravity (0, 200, 0) pulls particles back down: a classic fountain.
Source¶
1"""GPU Particles 2D: A GPU-driven particle fountain with gravity and colour fade.
2
3# /// simvx
4# tags = ["particles", "gpu", "effects"]
5# web = { root = "ParticlesScene", width = 800, height = 600, responsive = true }
6# ///
7
8## What it demonstrates
9 - GPUParticles2D: particle state lives on the GPU, simulated by a compute shader
10 - Emission tuning: amount, lifetime, speed, speed_variance, spread, direction
11 - gravity pulling particles back down to form a fountain arc
12 - start_colour / end_colour fade over each particle's lifetime
13 - Toggling emitting on/off and switching emission_shape at runtime
14
15In 2D the Y axis points down, so direction (0, -1, 0) emits upward and a
16positive-Y gravity (0, 200, 0) pulls particles back down: a classic fountain.
17"""
18
19from simvx.core import GPUParticles2D, Input, InputMap, Key, Node2D, Text2D, Vec2
20from simvx.graphics import App
21
22WIDTH, HEIGHT = 800, 600
23
24
25class ParticlesScene(Node2D):
26 """A fountain emitter plus a steady spark burst, both GPU-simulated."""
27
28 def on_ready(self):
29 InputMap.add_action("toggle", [Key.SPACE])
30 InputMap.add_action("shape", [Key.S])
31 InputMap.add_action("quit", [Key.ESCAPE])
32
33 # Golden fountain: emits upward, gravity arcs the stream back down.
34 self.fountain = self.add_child(GPUParticles2D(
35 amount=4000,
36 lifetime=1.6,
37 speed=260.0,
38 speed_variance=60.0,
39 spread=0.5, # velocity randomisation cone
40 direction=(0.0, -1.0, 0.0), # upward (Y is down in 2D)
41 gravity=(0.0, 380.0, 0.0), # pull back down
42 start_colour=(1.0, 0.85, 0.25, 1.0),
43 end_colour=(1.0, 0.15, 0.0, 0.0),
44 start_scale=6.0,
45 end_scale=1.0,
46 position=Vec2(WIDTH * 0.5, HEIGHT * 0.75),
47 name="Fountain",
48 ))
49
50 # Cyan sparks: omnidirectional spray from a small sphere, light gravity.
51 self.sparks = self.add_child(GPUParticles2D(
52 amount=1500,
53 lifetime=0.9,
54 speed=140.0,
55 speed_variance=80.0,
56 spread=3.14, # wide spread for an all-directions burst
57 direction=(0.0, -1.0, 0.0),
58 gravity=(0.0, 120.0, 0.0),
59 emission_shape="sphere",
60 emission_radius=12.0,
61 start_colour=(0.4, 0.9, 1.0, 1.0),
62 end_colour=(0.1, 0.2, 0.6, 0.0),
63 start_scale=4.0,
64 end_scale=0.0,
65 position=Vec2(WIDTH * 0.5, HEIGHT * 0.4),
66 name="Sparks",
67 ))
68
69 self.add_child(Text2D(text="GPUParticles2D Fountain", position=(10, 10), font_scale=1.5, name="Title"))
70 self.hud = self.add_child(Text2D(
71 text="Space = toggle emit | S = cycle spark shape | Esc = quit",
72 position=(10, 40), name="Hud",
73 ))
74 self._shapes = ["sphere", "point", "box"]
75 self._shape_index = 0
76
77 def on_update(self, dt: float):
78 if Input.is_action_just_pressed("toggle"):
79 self.fountain.emitting = not self.fountain.emitting
80 self.sparks.emitting = not self.sparks.emitting
81 if Input.is_action_just_pressed("shape"):
82 self._shape_index = (self._shape_index + 1) % len(self._shapes)
83 self.sparks.emission_shape = self._shapes[self._shape_index]
84 if Input.is_action_just_pressed("quit"):
85 self.app.quit()
86
87
88if __name__ == "__main__":
89 App(width=WIDTH, height=HEIGHT, title="GPUParticles2D Demo").run(ParticlesScene())