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