Navigation¶
grid-based A* pathfinding with a click-to-move character.
▶ Run in browserTags: 2d
The window is a grid of cells, some marked as walls (red). Click any free cell and the blue character finds the shortest walkable route to it with A* search, then walks that route waypoint by waypoint, steering around the walls. The green trail shows the remaining path.
What it demonstrates¶
NavGrid2D: the engine’s grid-based A* pathfinder. Walls are marked with set_solid(), and find_path() returns the shortest route as a list of cells. corners=”never” restricts movement to the 4 orthogonal neighbours; the other policies (“strict”, the default, then “relaxed” and “always”) admit diagonals under progressively looser rules about squeezing past a wall corner.
Reading the mouse in on_update() and converting a pixel click into a grid cell to use as the search destination.
Smooth movement: following the returned cell path one waypoint at a time at a fixed speed, rather than snapping between cells.
Drawing the grid, walls, remaining path, and character in on_draw().
Click anywhere to move the character along the shortest path. Run: uv run python examples/features/2d/navigation.py
Source¶
1#!/usr/bin/env python3
2"""Navigation: grid-based A* pathfinding with a click-to-move character.
3
4The window is a grid of cells, some marked as walls (red). Click any free cell
5and the blue character finds the shortest walkable route to it with A* search,
6then walks that route waypoint by waypoint, steering around the walls. The green
7trail shows the remaining path.
8
9## What it demonstrates
10 - NavGrid2D: the engine's grid-based A* pathfinder. Walls are marked with
11 set_solid(), and find_path() returns the shortest route as a list of cells.
12 corners="never" restricts movement to the 4 orthogonal neighbours; the
13 other policies ("strict", the default, then "relaxed" and "always") admit
14 diagonals under progressively looser rules about squeezing past a wall
15 corner.
16 - Reading the mouse in on_update() and converting a pixel click into a grid
17 cell to use as the search destination.
18 - Smooth movement: following the returned cell path one waypoint at a time at
19 a fixed speed, rather than snapping between cells.
20 - Drawing the grid, walls, remaining path, and character in on_draw().
21
22Click anywhere to move the character along the shortest path.
23Run: uv run python examples/features/2d/navigation.py
24"""
25
26from simvx.core import Input, InputMap, MouseButton, NavGrid2D, Node2D, Vec2
27from simvx.graphics import App
28
29WIDTH, HEIGHT = 800, 600
30CELL = 40
31COLS, ROWS = WIDTH // CELL, HEIGHT // CELL
32
33
34class NavigationDemo(Node2D):
35 dynamic = True # character walks the path + remaining path shrinks each frame
36
37 def on_ready(self):
38 InputMap.add_action("click", [MouseButton.LEFT])
39
40 self.grid = NavGrid2D(COLS, ROWS, cell_size=CELL, corners="never")
41 self.player_cell = (1, 1)
42 self.player_pos = Vec2(1 * CELL + CELL / 2, 1 * CELL + CELL / 2)
43 self._nav_path: list[tuple[int, int]] = []
44 self.move_speed = 200.0
45 self.target_pos: Vec2 | None = None
46
47 # Add some walls
48 for x in range(5, 15):
49 self.grid.set_solid(x, 5)
50 for y in range(2, 10):
51 self.grid.set_solid(8, y)
52 for x in range(3, 8):
53 self.grid.set_solid(x, 10)
54
55 def on_update(self, dt: float):
56 # Click to set destination
57 if Input.is_action_just_pressed("click"):
58 mx, my = Input.mouse_position
59 cell = (int(mx // CELL), int(my // CELL))
60 if 0 <= cell[0] < COLS and 0 <= cell[1] < ROWS:
61 self._nav_path = self.grid.find_path(self.player_cell, cell)
62 if self._nav_path:
63 self._nav_path.pop(0) # Remove current cell
64 self._next_waypoint()
65
66 # Move toward current waypoint
67 if self.target_pos is not None:
68 diff = self.target_pos - self.player_pos
69 dist = diff.length()
70 if dist < 2.0:
71 self.player_pos = Vec2(self.target_pos.x, self.target_pos.y)
72 self._next_waypoint()
73 else:
74 direction = diff * (1.0 / dist)
75 self.player_pos += direction * self.move_speed * dt
76
77 def _next_waypoint(self):
78 if self._nav_path:
79 cell = self._nav_path.pop(0)
80 self.player_cell = cell
81 self.target_pos = Vec2(cell[0] * CELL + CELL / 2, cell[1] * CELL + CELL / 2)
82 else:
83 self.target_pos = None
84
85 def on_draw(self, renderer):
86 # Grid
87 for x in range(COLS):
88 for y in range(ROWS):
89 colour = (0.75, 0.22, 0.22) if self.grid.is_solid(x, y) else (0.12, 0.12, 0.16)
90 renderer.draw_rect((x * CELL, y * CELL), (CELL - 1, CELL - 1), colour=colour, filled=True)
91
92 # Path
93 for cell in self._nav_path:
94 renderer.draw_rect(
95 (cell[0] * CELL + 4, cell[1] * CELL + 4),
96 (CELL - 9, CELL - 9),
97 colour=(0.24, 0.68, 0.28),
98 filled=True,
99 )
100
101 # Player
102 px, py = self.player_pos.x, self.player_pos.y
103 renderer.draw_circle((px, py), CELL // 3, colour=(0.24, 0.71, 1.0), filled=True)
104
105 # HUD
106 renderer.draw_text("Navigation Demo -- Click to move", (10, 10), colour=(1.0, 1.0, 1.0), scale=2)
107
108
109if __name__ == "__main__":
110 App(title="Navigation Demo", width=WIDTH, height=HEIGHT).run(NavigationDemo())