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
Run: uv run python examples/features/2d/trail.py
Controls: Space - 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
12
13Run: uv run python examples/features/2d/trail.py
14
15Controls:
16 Space - Toggle trails on/off
17 Escape - Quit
18"""
19
20import math
21
22from simvx.core import Input, InputMap, Key, 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."""
30
31 speed = Property(2.0)
32
33 def __init__(self, cx: float, cy: float, rx: float, ry: float, **kwargs):
34 super().__init__(**kwargs)
35 self._cx, self._cy = cx, cy
36 self._rx, self._ry = rx, ry
37 self._time = 0.0
38
39 def on_process(self, dt: float):
40 self._time += dt * self.speed
41 t = self._time
42 x = self._cx + math.sin(t) * self._rx
43 y = self._cy + math.sin(t * 2) * self._ry * 0.5
44 self.position = Vec2(x, y)
45
46
47class TrailDemo(Node2D):
48 """Root scene with two moving objects, each with a Trail2D child."""
49
50 def on_ready(self):
51 InputMap.add_action("toggle_trails", [Key.SPACE])
52 InputMap.add_action("quit", [Key.ESCAPE])
53
54 # --- Cyan figure-8 mover with a thin, long trail ---
55 self._mover1 = self.add_child(
56 FigureEightMover(WIDTH * 0.5, HEIGHT * 0.35, 200, 180, name="Mover1")
57 )
58 trail1 = self._mover1.add_child(Trail2D(name="CyanTrail"))
59 trail1.length = 40
60 trail1.width = 8.0
61 trail1.colour = (0.2, 0.8, 1.0, 1.0)
62 trail1.colour_end = (0.2, 0.8, 1.0, 0.0)
63 trail1.lifetime = 1.0
64
65 # --- Red-to-yellow mover on a circular orbit ---
66 self._mover2 = self.add_child(Node2D(name="Mover2"))
67 trail2 = self._mover2.add_child(Trail2D(name="FireTrail"))
68 trail2.length = 25
69 trail2.width = 16.0
70 trail2.colour = (1.0, 0.2, 0.1, 1.0)
71 trail2.colour_end = (1.0, 0.9, 0.1, 0.0)
72 trail2.lifetime = 0.6
73
74 self._trails = [trail1, trail2]
75 self._time = 0.0
76
77 def on_process(self, dt: float):
78 self._time += dt
79
80 # Circular orbit for the second mover
81 angle = self._time * 1.8
82 self._mover2.position = Vec2(
83 WIDTH * 0.5 + math.cos(angle) * 160,
84 HEIGHT * 0.7 + math.sin(angle) * 80,
85 )
86
87 # Toggle trails with Space
88 if Input.is_action_just_pressed("toggle_trails"):
89 for trail in self._trails:
90 trail.emit = not trail.emit
91 if Input.is_action_just_pressed("quit"):
92 self.app.quit()
93
94 def on_draw(self, renderer):
95 # Draw each trail as connected line segments with colour gradient
96 for trail in self._trails:
97 points = trail.trail_points
98 for i in range(len(points) - 1):
99 a, b = points[i], points[i + 1]
100 renderer.draw_line(a["position"], b["position"], colour=a["colour"])
101
102 # Draw mover positions as small dots
103 white = (1.0, 1.0, 1.0, 1.0)
104 renderer.draw_circle(self._mover1.position, 5, colour=white)
105 renderer.draw_circle(self._mover2.position, 5, colour=white)
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 = 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())