2D Path Follow¶
A circle follows a figure-8 bezier curve.
▶ Run in browserTags: 2d
Demonstrates:
Curve2D with bezier control points
Path2D / PathFollow2D for automatic motion along a curve
on_draw() callback for rendering the path and follower
Speed control via input actions
Controls: Up / Down - Increase / decrease speed Escape - Quit
Run: uv run python examples/features/2d/path_follow.py
Source¶
1"""2D Path Follow: A circle follows a figure-8 bezier curve.
2
3# /// simvx
4# web = { width = 1024, height = 640 }
5# ///
6
7Demonstrates:
8 - Curve2D with bezier control points
9 - Path2D / PathFollow2D for automatic motion along a curve
10 - on_draw() callback for rendering the path and follower
11 - Speed control via input actions
12
13Controls:
14 Up / Down - Increase / decrease speed
15 Escape - Quit
16
17Run: uv run python examples/features/2d/path_follow.py
18"""
19
20
21from simvx.core import Curve2D, Input, InputMap, Key, Node2D, Path2D, PathFollow2D, Property, Text2D, Vec2
22from simvx.graphics import App
23
24WIDTH, HEIGHT = 1024, 640
25CX, CY = WIDTH / 2, HEIGHT / 2
26
27
28class PathDemo(Node2D):
29 speed = Property(200.0, range=(50, 500))
30
31 def on_ready(self):
32 InputMap.add_action("speed_up", [Key.UP])
33 InputMap.add_action("speed_down", [Key.DOWN])
34 InputMap.add_action("quit", [Key.ESCAPE])
35
36 # Build a figure-8 curve centred on the window
37 curve = Curve2D(bake_interval=5.0)
38 # Right loop
39 curve.add_point(Vec2(CX, CY), handle_in=Vec2(0, -120), handle_out=Vec2(0, 120))
40 curve.add_point(Vec2(CX + 200, CY + 150), handle_in=Vec2(-80, 0), handle_out=Vec2(80, 0))
41 curve.add_point(Vec2(CX, CY), handle_in=Vec2(0, 120), handle_out=Vec2(0, -120))
42 # Left loop (mirrors the right)
43 curve.add_point(Vec2(CX - 200, CY - 150), handle_in=Vec2(80, 0), handle_out=Vec2(-80, 0))
44 curve.add_point(Vec2(CX, CY), handle_in=Vec2(0, -120), handle_out=Vec2(0, 120))
45
46 self._path = self.add_child(Path2D(name="Path"))
47 self._path.curve = curve
48
49 self._follower = self._path.add_child(PathFollow2D(name="Follower"))
50 self._follower.loop = True
51 self._follower.rotates = True
52 self._follower.loop_completed.connect(self._on_loop)
53 self._loops = 0
54
55 self._hud = self.add_child(Text2D(name="HUD", text="", x=10, y=10, font_scale=1.5))
56
57 def _on_loop(self):
58 self._loops += 1
59
60 def on_process(self, dt: float):
61 # Speed adjustment
62 if Input.is_action_pressed("speed_up"):
63 self.speed = min(500.0, self.speed + 150.0 * dt)
64 if Input.is_action_pressed("speed_down"):
65 self.speed = max(50.0, self.speed - 150.0 * dt)
66 if Input.is_action_just_pressed("quit"):
67 self.app.quit()
68 return
69
70 self._follower.progress += self.speed * dt
71
72 ratio = self._follower.progress_ratio
73 length = self._path.curve.get_baked_length()
74 self._hud.text = (
75 f"Speed: {self.speed:.0f} (Up/Down) "
76 f"Progress: {ratio:.1%} Length: {length:.0f} Loops: {self._loops}"
77 )
78
79 def on_draw(self, renderer):
80 # Draw the baked curve as connected line segments
81 points = self._path.curve.get_baked_points()
82 if len(points) >= 2:
83 renderer.draw_lines(points, closed=False, colour=(0.31, 0.31, 0.55))
84
85 # Draw follower as a filled circle
86 pos = self._follower.world_position
87 renderer.draw_circle(pos, 10, colour=(1.0, 0.4, 0.2, 1.0), filled=True)
88 renderer.draw_circle(pos, 3, colour=(1.0, 1.0, 0.8, 1.0), filled=True)
89
90
91if __name__ == "__main__":
92 App(title="2D Path Follow", width=WIDTH, height=HEIGHT).run(PathDemo())