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. diagonal=False restricts movement to the 4 orthogonal neighbours.
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 diagonal=False restricts movement to the 4 orthogonal neighbours.
13 - Reading the mouse in on_update() and converting a pixel click into a grid
14 cell to use as the search destination.
15 - Smooth movement: following the returned cell path one waypoint at a time at
16 a fixed speed, rather than snapping between cells.
17 - Drawing the grid, walls, remaining path, and character in on_draw().
18
19Click anywhere to move the character along the shortest path.
20Run: uv run python examples/features/2d/navigation.py
21"""
22
23from simvx.core import Input, InputMap, MouseButton, NavGrid2D, Node2D, Vec2
24from simvx.graphics import App
25
26WIDTH, HEIGHT = 800, 600
27CELL = 40
28COLS, ROWS = WIDTH // CELL, HEIGHT // CELL
29
30
31class NavigationDemo(Node2D):
32 dynamic = True # character walks the path + remaining path shrinks each frame
33
34 def on_ready(self):
35 InputMap.add_action("click", [MouseButton.LEFT])
36
37 self.grid = NavGrid2D(COLS, ROWS, cell_size=CELL, diagonal=False)
38 self.player_cell = (1, 1)
39 self.player_pos = Vec2(1 * CELL + CELL / 2, 1 * CELL + CELL / 2)
40 self._nav_path: list[tuple[int, int]] = []
41 self.move_speed = 200.0
42 self.target_pos: Vec2 | None = None
43
44 # Add some walls
45 for x in range(5, 15):
46 self.grid.set_solid(x, 5)
47 for y in range(2, 10):
48 self.grid.set_solid(8, y)
49 for x in range(3, 8):
50 self.grid.set_solid(x, 10)
51
52 def on_update(self, dt: float):
53 # Click to set destination
54 if Input.is_action_just_pressed("click"):
55 mx, my = Input.mouse_position
56 cell = (int(mx // CELL), int(my // CELL))
57 if 0 <= cell[0] < COLS and 0 <= cell[1] < ROWS:
58 self._nav_path = self.grid.find_path(self.player_cell, cell)
59 if self._nav_path:
60 self._nav_path.pop(0) # Remove current cell
61 self._next_waypoint()
62
63 # Move toward current waypoint
64 if self.target_pos is not None:
65 diff = self.target_pos - self.player_pos
66 dist = diff.length()
67 if dist < 2.0:
68 self.player_pos = Vec2(self.target_pos.x, self.target_pos.y)
69 self._next_waypoint()
70 else:
71 direction = diff * (1.0 / dist)
72 self.player_pos += direction * self.move_speed * dt
73
74 def _next_waypoint(self):
75 if self._nav_path:
76 cell = self._nav_path.pop(0)
77 self.player_cell = cell
78 self.target_pos = Vec2(cell[0] * CELL + CELL / 2, cell[1] * CELL + CELL / 2)
79 else:
80 self.target_pos = None
81
82 def on_draw(self, renderer):
83 # Grid
84 for x in range(COLS):
85 for y in range(ROWS):
86 colour = (0.75, 0.22, 0.22) if self.grid.is_solid(x, y) else (0.12, 0.12, 0.16)
87 renderer.draw_rect((x * CELL, y * CELL), (CELL - 1, CELL - 1), colour=colour, filled=True)
88
89 # Path
90 for cell in self._nav_path:
91 renderer.draw_rect(
92 (cell[0] * CELL + 4, cell[1] * CELL + 4),
93 (CELL - 9, CELL - 9),
94 colour=(0.24, 0.68, 0.28),
95 filled=True,
96 )
97
98 # Player
99 px, py = self.player_pos.x, self.player_pos.y
100 renderer.draw_circle((px, py), CELL // 3, colour=(0.24, 0.71, 1.0), filled=True)
101
102 # HUD
103 renderer.draw_text("Navigation Demo -- Click to move", (10, 10), colour=(1.0, 1.0, 1.0), scale=2)
104
105
106if __name__ == "__main__":
107 App(title="Navigation Demo", width=WIDTH, height=HEIGHT).run(NavigationDemo())