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(FigureEightMover(0.5, 0.35, 0.2, 0.23, name="Mover1"))
63 trail1 = self._mover1.add_child(Trail2D(name="CyanTrail"))
64 trail1.length = 40
65 trail1.width = 8.0
66 trail1.colour = (0.2, 0.8, 1.0, 1.0)
67 trail1.colour_end = (0.2, 0.8, 1.0, 0.0)
68 trail1.lifetime = 1.0
69
70 # --- Red-to-yellow mover on a circular orbit ---
71 self._mover2 = self.add_child(Node2D(name="Mover2"))
72 trail2 = self._mover2.add_child(Trail2D(name="FireTrail"))
73 trail2.length = 25
74 trail2.width = 16.0
75 trail2.colour = (1.0, 0.2, 0.1, 1.0)
76 trail2.colour_end = (1.0, 0.9, 0.1, 0.0)
77 trail2.lifetime = 0.6
78
79 self._trails = [trail1, trail2]
80 self._time = 0.0
81
82 def on_update(self, dt: float):
83 self._time += dt
84
85 # Circular orbit for the second mover, sized to the live window
86 w, h = self.app.width, self.app.height
87 angle = self._time * 1.8
88 self._mover2.position = Vec2(
89 w * 0.5 + math.cos(angle) * w * 0.16,
90 h * 0.7 + math.sin(angle) * h * 0.1,
91 )
92
93 # Toggle trails with Space
94 if Input.is_action_just_pressed("toggle_trails"):
95 for trail in self._trails:
96 trail.emit = not trail.emit
97 if Input.is_action_just_pressed("quit"):
98 self.app.quit()
99
100 def on_draw(self, renderer):
101 # Each Trail2D renders its own ribbon via on_draw; the scene only
102 # decorates with mover dots and a HUD.
103 white = (1.0, 1.0, 1.0, 1.0)
104 renderer.draw_circle(self._mover1.position, 5, colour=white, filled=True)
105 renderer.draw_circle(self._mover2.position, 5, colour=white, filled=True)
106
107 # HUD
108 renderer.draw_text("TRAIL2D DEMO", (10, 10), colour=(0.78, 0.78, 0.78), scale=2)
109 state = "ON" if self._trails[0].emit else "OFF"
110 renderer.draw_text(
111 f"Space or click = toggle [{state}] | Cyan: figure-8 | Red: orbit",
112 (10, 35),
113 colour=(0.59, 0.59, 0.59),
114 )
115
116
117if __name__ == "__main__":
118 App(width=WIDTH, height=HEIGHT, title="Trail2D Demo").run(TrailDemo())