2D Trail¶
Colour-gradient trails behind moving objects.
▶ Run in browserTags: 2d
Demonstrates:
Trail2D as a child of a moving Node2D
Configurable length, width, colour gradient, and lifetime
Multiple trails with different visual settings
Toggle emission on/off with Space or a click/tap
Run: uv run python examples/features/2d/trail.py
Controls: Space / Click - Toggle trails on/off Escape - Quit
Source¶
1"""2D Trail: Colour-gradient trails behind moving objects.
2
3# /// simvx
4# web = { width = 1024, height = 768 }
5# ///
6
7Demonstrates:
8 - Trail2D as a child of a moving Node2D
9 - Configurable length, width, colour gradient, and lifetime
10 - Multiple trails with different visual settings
11 - Toggle emission on/off with Space or a click/tap
12
13Run: uv run python examples/features/2d/trail.py
14
15Controls:
16 Space / Click - Toggle trails on/off
17 Escape - Quit
18"""
19
20import math
21
22from simvx.core import Input, InputMap, Key, MouseButton, Node2D, Property, Trail2D, Vec2
23from simvx.graphics import App
24
25WIDTH, HEIGHT = 1024, 768
26
27
28class FigureEightMover(Node2D):
29 """Moves in a figure-8 (lemniscate) pattern sized to the live window.
30
31 Centre and radii are fractions of the window size, so the path stays
32 on screen at any resolution.
33 """
34
35 speed = Property(2.0)
36
37 def __init__(self, cx: float, cy: float, rx: float, ry: float, **kwargs):
38 super().__init__(**kwargs)
39 self._cx, self._cy = cx, cy
40 self._rx, self._ry = rx, ry
41 self._time = 0.0
42
43 def on_update(self, dt: float):
44 self._time += dt * self.speed
45 t = self._time
46 w, h = self.app.width, self.app.height
47 x = w * self._cx + math.sin(t) * w * self._rx
48 y = h * self._cy + math.sin(t * 2) * h * self._ry * 0.5
49 self.position = Vec2(x, y)
50
51
52class TrailDemo(Node2D):
53 """Root scene with two moving objects, each with a Trail2D child."""
54
55 dynamic = True # the dots + HUD read live positions every frame
56
57 def on_ready(self):
58 InputMap.add_action("toggle_trails", [Key.SPACE, MouseButton.LEFT])
59 InputMap.add_action("quit", [Key.ESCAPE])
60
61 # --- Cyan figure-8 mover with a thin, long trail ---
62 self._mover1 = self.add_child(
63 FigureEightMover(0.5, 0.35, 0.2, 0.23, name="Mover1")
64 )
65 trail1 = self._mover1.add_child(Trail2D(name="CyanTrail"))
66 trail1.length = 40
67 trail1.width = 8.0
68 trail1.colour = (0.2, 0.8, 1.0, 1.0)
69 trail1.colour_end = (0.2, 0.8, 1.0, 0.0)
70 trail1.lifetime = 1.0
71
72 # --- Red-to-yellow mover on a circular orbit ---
73 self._mover2 = self.add_child(Node2D(name="Mover2"))
74 trail2 = self._mover2.add_child(Trail2D(name="FireTrail"))
75 trail2.length = 25
76 trail2.width = 16.0
77 trail2.colour = (1.0, 0.2, 0.1, 1.0)
78 trail2.colour_end = (1.0, 0.9, 0.1, 0.0)
79 trail2.lifetime = 0.6
80
81 self._trails = [trail1, trail2]
82 self._time = 0.0
83
84 def on_update(self, dt: float):
85 self._time += dt
86
87 # Circular orbit for the second mover, sized to the live window
88 w, h = self.app.width, self.app.height
89 angle = self._time * 1.8
90 self._mover2.position = Vec2(
91 w * 0.5 + math.cos(angle) * w * 0.16,
92 h * 0.7 + math.sin(angle) * h * 0.1,
93 )
94
95 # Toggle trails with Space
96 if Input.is_action_just_pressed("toggle_trails"):
97 for trail in self._trails:
98 trail.emit = not trail.emit
99 if Input.is_action_just_pressed("quit"):
100 self.app.quit()
101
102 def on_draw(self, renderer):
103 # Each Trail2D renders its own ribbon via on_draw; the scene only
104 # decorates with mover dots and a HUD.
105 white = (1.0, 1.0, 1.0, 1.0)
106 renderer.draw_circle(self._mover1.position, 5, colour=white)
107 renderer.draw_circle(self._mover2.position, 5, colour=white)
108
109 # HUD
110 renderer.draw_text("TRAIL2D DEMO", (10, 10), colour=(0.78, 0.78, 0.78), scale=2)
111 state = "ON" if self._trails[0].emit else "OFF"
112 renderer.draw_text(
113 f"Space or click = toggle [{state}] | Cyan: figure-8 | Red: orbit",
114 (10, 35),
115 colour=(0.59, 0.59, 0.59),
116 )
117
118
119if __name__ == "__main__":
120 App(width=WIDTH, height=HEIGHT, title="Trail2D Demo").run(TrailDemo())