GPU Particles 2D¶
a particle fountain simulated on the GPU.
▶ Run in browserTags: 2d particles gpu effects
A GPUParticles2D node keeps its particles in GPU memory and a compute shader
moves them, so thousands of grains cost the CPU nothing per particle. You
configure the emitter with properties and the renderer does the rest:
amountandlifetimeset how many particles live at once and for how long;randomnessvaries each lifetime so the stream stays continuous.speed,speed_variance,directionandspreadshape the launch.gravityaccelerates every particle;start_colour/end_colourandstart_scale/end_scalefade and shrink each one over its life.emission_shapeis where particles are born:"point","sphere"(emission_radius) or"box"(emission_boxhalf-extents).emittingstarts and stops spawning; live particles finish their lives.
In 2D, Y points down, so direction=(0, -1, 0) emits upward and a positive-Y
gravity pulls the grains back into an arc. Speeds and gravity are in pixels
per second, so the golden jet rises about speed^2 / (2 * gravity) pixels, roughly
260, before it turns over. Above it a cyan puff scatters sparks in every
direction.
Press Space or the Emit button to stop and restart both emitters, and S or the Shape button to move the sparks’ emission through sphere, point and box.
Source¶
1# /// simvx
2# tags = ["particles", "gpu", "effects"]
3# web = { width = 800, height = 600, responsive = true }
4# ///
5"""GPU Particles 2D: a particle fountain simulated on the GPU.
6
7A ``GPUParticles2D`` node keeps its particles in GPU memory and a compute shader
8moves them, so thousands of grains cost the CPU nothing per particle. You
9configure the emitter with properties and the renderer does the rest:
10
11 - ``amount`` and ``lifetime`` set how many particles live at once and for how long;
12 ``randomness`` varies each lifetime so the stream stays continuous.
13 - ``speed``, ``speed_variance``, ``direction`` and ``spread`` shape the launch.
14 - ``gravity`` accelerates every particle; ``start_colour``/``end_colour`` and
15 ``start_scale``/``end_scale`` fade and shrink each one over its life.
16 - ``emission_shape`` is where particles are born: ``"point"``, ``"sphere"``
17 (``emission_radius``) or ``"box"`` (``emission_box`` half-extents).
18 - ``emitting`` starts and stops spawning; live particles finish their lives.
19
20In 2D, Y points down, so ``direction=(0, -1, 0)`` emits upward and a positive-Y
21``gravity`` pulls the grains back into an arc. Speeds and gravity are in pixels
22per second, so the golden jet rises about speed^2 / (2 * gravity) pixels, roughly
23260, before it turns over. Above it a cyan puff scatters sparks in every
24direction.
25
26Press Space or the Emit button to stop and restart both emitters, and S or the
27Shape button to move the sparks' emission through sphere, point and box.
28"""
29
30from itertools import cycle
31
32from simvx.core import Anchor, Button, CanvasLayer, GPUParticles2D, HBoxContainer, Key, Node2D, on_input
33from simvx.graphics import App
34
35
36class ParticlesScene(Node2D):
37 input_context = {"emit": [Key.SPACE], "shape": [Key.S]}
38
39 def on_ready(self):
40 self.fountain = self.add_child(
41 GPUParticles2D(
42 amount=1400,
43 lifetime=2.2, # long enough for the whole up-and-over arc
44 randomness=0.35,
45 speed=540.0,
46 speed_variance=90.0,
47 spread=0.28, # a narrow jet that fans near the apex
48 direction=(0.0, -1.0, 0.0),
49 gravity=(0.0, 560.0, 0.0),
50 start_colour=(1.0, 0.85, 0.35, 1.0),
51 end_colour=(1.0, 0.4, 0.1, 0.0),
52 start_scale=5.0,
53 end_scale=1.5,
54 )
55 )
56 self.sparks = self.add_child(
57 GPUParticles2D(
58 amount=1200,
59 lifetime=1.1,
60 speed=130.0,
61 speed_variance=70.0,
62 spread=3.14, # wide enough to throw sparks every way
63 direction=(0.0, -1.0, 0.0),
64 gravity=(0.0, 180.0, 0.0),
65 emission_shape="sphere",
66 emission_radius=24.0,
67 emission_box=(60.0, 12.0, 0.0),
68 start_colour=(0.4, 0.9, 1.0, 1.0),
69 end_colour=(0.1, 0.3, 0.7, 0.0),
70 start_scale=2.5,
71 end_scale=0.0,
72 )
73 )
74 self.shapes = cycle(["point", "box", "sphere"]) # what follows the starting sphere
75
76 # Example help overlay: not part of the technique shown.
77 bar = HBoxContainer(anchor=Anchor.TOP_LEFT, inset=10, separation=8)
78 bar.add_child(Button("Emit", action="emit"))
79 self.shape_button = bar.add_child(Button("Shape: sphere", action="shape"))
80 self.add_child(CanvasLayer()).add_child(bar)
81
82 def on_update(self, dt):
83 w, h = self.viewport_size
84 self.fountain.position = (w * 0.5, h * 0.86)
85 self.sparks.position = (w * 0.5, h * 0.42)
86
87 @on_input("emit")
88 def toggle_emitting(self, event):
89 self.fountain.emitting = self.sparks.emitting = not self.fountain.emitting
90 return True
91
92 @on_input("shape")
93 def next_shape(self, event):
94 self.sparks.emission_shape = next(self.shapes)
95 self.shape_button.text = f"Shape: {self.sparks.emission_shape}"
96 return True
97
98
99if __name__ == "__main__":
100 App(width=800, height=600, title="GPU Particles 2D").run(ParticlesScene())