GPU Particle Fireworks.

A fireworks show built from one-shot GPUParticles2D bursts, cycling through the velocity patterns that give each burst its silhouette.

▶ Run in browser

Tags: 2d particles gpu effects fireworks

spread_pattern decides how a grain’s launch velocity is sampled:

  • "star": every direction, with speed following a star curve of spread_points points.

  • "ring": every direction at the same speed, a clean circle.

  • "gaussian": a soft normal-distributed spray around direction.

  • "disc": a uniform cone of half-angle spread around direction.

star and ring ignore direction; gaussian and disc aim along it, here straight up. With one_shot=True an emitter runs for one lifetime, stops itself and emits finished; with explosiveness=1.0 every grain spawns on the same frame, so the burst is one crisp shell that falls under gravity and fades from start_colour to end_colour. speed is in pixels per second and gravity in pixels per second squared. restart() re-arms a spent emitter, so a shell is a reusable object: the scene keeps a pool of spent shells, returns each one to it on finished, and builds a new shell only when every one is still in the air.

A repeating Timer launches a shell every 0.45 seconds at a random spot in the upper part of the window. Press Space for an extra burst, or click or tap to fire one where you pressed.

Source

  1# /// simvx
  2# tags = ["particles", "gpu", "effects", "fireworks"]
  3# web = { width = 800, height = 600, responsive = true }
  4# ///
  5"""GPU Particle Fireworks.
  6
  7A fireworks show built from one-shot `GPUParticles2D` bursts, cycling through the
  8velocity patterns that give each burst its silhouette.
  9
 10`spread_pattern` decides how a grain's launch velocity is sampled:
 11
 12- `"star"`: every direction, with speed following a star curve of
 13  `spread_points` points.
 14- `"ring"`: every direction at the same speed, a clean circle.
 15- `"gaussian"`: a soft normal-distributed spray around `direction`.
 16- `"disc"`: a uniform cone of half-angle `spread` around `direction`.
 17
 18`star` and `ring` ignore `direction`; `gaussian` and `disc` aim along it, here
 19straight up. With `one_shot=True` an emitter runs for one `lifetime`, stops
 20itself and emits `finished`; with `explosiveness=1.0` every grain spawns on the
 21same frame, so the burst is one crisp shell that falls under `gravity` and fades
 22from `start_colour` to `end_colour`. `speed` is in pixels per second and
 23`gravity` in pixels per second squared. `restart()` re-arms a spent emitter, so
 24a shell is a reusable object: the scene keeps a pool of spent shells, returns
 25each one to it on `finished`, and builds a new shell only when every one is
 26still in the air.
 27
 28A repeating `Timer` launches a shell every 0.45 seconds at a random spot in the
 29upper part of the window. Press Space for an extra burst, or click or tap to
 30fire one where you pressed.
 31"""
 32
 33import random
 34from itertools import cycle
 35
 36from simvx.core import (
 37    Anchor,
 38    CanvasLayer,
 39    GPUParticles2D,
 40    Key,
 41    Label,
 42    Node2D,
 43    Pointer,
 44    Timer,
 45    Vec2,
 46    on_input,
 47)
 48from simvx.graphics import App
 49
 50# (spread_pattern, start_colour, end_colour), launched in this order, round and round.
 51SHELLS = [
 52    ("star", (1.0, 0.85, 0.30, 1.0), (1.0, 0.30, 0.10, 0.0)),
 53    ("ring", (0.45, 0.95, 1.0, 1.0), (0.10, 0.35, 0.85, 0.0)),
 54    ("gaussian", (1.0, 0.45, 0.85, 1.0), (0.55, 0.10, 0.55, 0.0)),
 55    ("star", (0.65, 1.0, 0.45, 1.0), (0.15, 0.55, 0.20, 0.0)),
 56    ("disc", (1.0, 0.75, 0.30, 1.0), (0.8, 0.25, 0.05, 0.0)),
 57    ("ring", (0.9, 0.6, 1.0, 1.0), (0.4, 0.2, 0.7, 0.0)),
 58]
 59
 60
 61class FireworksScene(Node2D):
 62    input_context = {"fire": [Key.SPACE]}
 63
 64    def on_ready(self):
 65        self._shells = cycle(SHELLS)
 66        self._spent: list[GPUParticles2D] = []
 67
 68        launcher = self.add_child(Timer(duration=0.45, one_shot=False, autostart=True))
 69        launcher.timeout.connect(self._fire)
 70
 71        hud = self.add_child(CanvasLayer())
 72        hud.add_child(Label("GPU Particle Fireworks", font_size=24, anchor=Anchor.TOP_LEFT, inset=10))
 73        self._pattern = hud.add_child(Label("", anchor=Anchor.TOP_LEFT, inset=(10, 44)))
 74        hud.add_child(Label("Space, click or tap: extra burst", anchor=Anchor.BOTTOM_LEFT, inset=10))
 75        self._fire()
 76
 77    def _new_shell(self) -> GPUParticles2D:
 78        shell = self.add_child(
 79            GPUParticles2D(
 80                amount=1300,
 81                lifetime=1.0,
 82                speed=300.0,
 83                speed_variance=35.0,
 84                spread=0.5,
 85                direction=(0.0, -1.0, 0.0),
 86                gravity=(0.0, 340.0, 0.0),
 87                emission_radius=3.0,
 88                start_scale=3.0,
 89                end_scale=0.4,
 90                one_shot=True,
 91                explosiveness=1.0,
 92                emitting=False,
 93            )
 94        )
 95        shell.finished.connect(lambda: self._spent.append(shell))
 96        return shell
 97
 98    def _fire(self, at: Vec2 | None = None):
 99        """Launch a burst at `at`, or at a random spot in the sky."""
100        if at is None:
101            w, h = self.viewport_size
102            at = Vec2(random.uniform(0.2, 0.8) * w, random.uniform(0.25, 0.55) * h)
103        pattern, start, end = next(self._shells)
104        shell = self._spent.pop() if self._spent else self._new_shell()
105        shell.position = at
106        shell.spread_pattern = pattern
107        shell.spread_points = random.choice((5, 6))
108        shell.start_colour = start
109        shell.end_colour = end
110        shell.restart()
111        self._pattern.text = f"pattern: {pattern}"
112
113    @on_input("fire")
114    def _on_fire(self, event):
115        self._fire()
116
117    @on_input(Pointer.PRIMARY)
118    def _on_press(self, event):
119        self._fire(at=Vec2(event.position))
120
121
122if __name__ == "__main__":
123    App(width=800, height=600, title="GPU Particle Fireworks").run(FireworksScene())