Navigation¶
SimVX provides 2D pathfinding through graph-based A* (PathFinder2D), grid-based A* (NavGrid2D), and a steering agent node (NavigationAgent2D).
Graph-Based A* (PathFinder2D)¶
Build an arbitrary waypoint graph and find shortest paths:
from simvx.core import PathFinder2D
astar = PathFinder2D()
# Add waypoints
astar.add_point(0, (0, 0))
astar.add_point(1, (100, 0))
astar.add_point(2, (100, 100))
astar.add_point(3, (50, 50), weight=2.0) # Costlier to traverse
# Connect them
astar.connect_points(0, 1) # Bidirectional by default
astar.connect_points(1, 2)
astar.connect_points(0, 3)
astar.connect_points(3, 2)
# Find path (returns world positions)
path = astar.get_point_path(0, 2) # [(0,0), (100,0), (100,100)]
# Or get point IDs
id_path = astar.get_id_path(0, 2) # [0, 1, 2]
Additional Operations¶
astar.set_point_disabled(3) # Exclude from pathfinding
astar.disconnect_points(0, 1) # Remove a connection
closest = astar.get_closest_point((45, 45)) # Nearest non-disabled point
Grid-Based A* (NavGrid2D)¶
For tile-based games, NavGrid2D manages a rectangular grid automatically:
from simvx.core import NavGrid2D
grid = NavGrid2D(width=20, height=20, cell_size=32.0)
# Mark obstacles
grid.set_solid(5, 5)
grid.set_solid(5, 6)
grid.set_solid(5, 7)
# Set terrain cost (swamp = 3x slower)
grid.set_weight(10, 10, 3.0)
# Find path in grid coordinates
path = grid.find_path((0, 0), (19, 19)) # [(0,0), (1,1), ...]
# Or use world positions (auto-converts via cell_size)
world_path = grid.find_path_world((16.0, 16.0), (624.0, 624.0))
Corner policy¶
A diagonal step passes between two cells. Whether it is allowed when one or
both of those cells are solid is the grid’s corner policy, chosen at
construction and readable afterwards as grid.corners:
grid = NavGrid2D(20, 20, corners="relaxed")
grid.corners # "relaxed"
|
A diagonal is allowed when |
Use it for |
|---|---|---|
|
never; routes are 4-connected |
tile games where diagonal movement is not a move |
|
both cells beside it are free |
anything with a body radius: characters, vehicles, monsters |
|
at least one cell beside it is free |
point-sized agents that may slip past a single corner, such as between diagonally placed fence posts |
|
unconditionally |
nothing physical: it will route through a sealed diagonal wall |
Any other value raises ValueError naming the four policies. True and
False raise too, pointing at "always" and "never" respectively.
Changing the default cannot disconnect a map. A diagonal admitted under
"strict" has both of its side cells free, so the same two cells are already
joined by a two-step cardinal route. A "strict" grid therefore has exactly
the same connected components as a "never" grid over the same solids: if a
4-connected search can reach a cell, so can "strict", and if it cannot,
neither can "strict". The same argument covers "relaxed", whose single free
side cell is itself a two-step route. Only "always" invents connectivity, and
what it invents is a gap of zero width that no agent can execute.
"strict" expands more nodes than "always" because it rejects the shortcuts
that hug obstacle corners. Measured over randomly obstructed 128x128 grids at
10-30% solid, a "strict" search cost between 1.5x and 5x an "always" one.
"relaxed" came out close to "always" throughout, so that is the policy to
reach for when search cost matters and point-sized agents suit the game.
"never" is not the cheap alternative: dropping diagonals lengthens every
route, and it cost more than "always" in every layout measured.
NavigationAgent2D¶
A node that follows computed paths with built-in steering. Assign a pathfinder, set a target, and the agent moves each physics frame:
from simvx.core import NavigationAgent2D, NavGrid2D, Vec2
grid = NavGrid2D(50, 50, cell_size=16.0)
class Enemy(NavigationAgent2D):
max_speed = 120.0
path_desired_distance = 4.0
def on_ready(self):
self.set_navigation(grid)
self.navigation_finished.connect(self._on_arrived)
self.target_position = Vec2(400, 300)
def _on_arrived(self):
print("Reached destination!")
Properties¶
Property |
Default |
Description |
|---|---|---|
|
|
Maximum movement speed in units/sec |
|
|
Distance to a waypoint before advancing to the next |
Properties and Signals¶
target_position: Assigning this recomputes the path immediately.velocity: Current velocity vector (read-only, computed each physics frame).is_navigation_finished:Truewhen the agent has reached its target.navigation_finished: Signal emitted when the target is reached.
API Reference¶
See simvx.core.navigation for the complete navigation API.