Quick answer: NavigationRegion nodes from different scenes do not connect because they are on different navigation layers, their boundary edges are outside the edge connection margin, or the navigation map has not updated after all scenes finish loading. Align layers, increase the margin, and force a map update after scene loading completes.
Here is how to fix Godot NavigationRegion not connecting between scenes. You load two scenes side by side, each with its own NavigationRegion3D, and your NavigationAgent cannot path from one to the other. The agent reaches the boundary of the first region and stops. The debug visualization shows two separate islands of navigation mesh with no connection between them, even though the edges visually overlap. The navigation server treats each region as completely isolated, and the pathfinding algorithm cannot find a route across the gap.
The Symptom
You have a world composed of multiple scenes (rooms, chunks, or tiles) that are instanced at runtime. Each scene contains a NavigationRegion2D or NavigationRegion3D with a baked navigation mesh. When you request a path from a point in Scene A to a point in Scene B using NavigationAgent.target_position, the agent either stops at the edge of Scene A, returns an empty path, or paths to the nearest reachable point within its own region and gives up.
If you enable Debug > Visible Navigation in the editor, you can see that the navigation polygons from each scene are drawn as separate colored groups with no connecting edges. Free (unconnected) boundary edges are shown in a distinct color, confirming that the navigation server does not recognize them as connected.
The confusing part is that this works fine if you put all the navigation geometry into a single scene and bake it as one region. The problem only manifests when navigation spans multiple independently loaded scenes.
What Causes This
Navigation layer mismatch. Every NavigationRegion has a navigation_layers bitmask. The navigation server only attempts to connect edges between regions that share at least one common layer. If Scene A’s region uses layer 1 and Scene B’s region uses layer 2, they will never connect, even if their edges perfectly overlap. This bitmask defaults to layer 1 for new regions, but it is easy to accidentally change it when setting up different navigation types (walking vs. flying, for example).
Edge connection margin too small. The navigation server connects two region edges only if the edge vertices are within the edge_connection_margin distance of each other. The default margin is quite small (often 1.0 unit in 3D). If your scenes are positioned with even a slight gap between them, or if the navigation mesh edges do not perfectly align at the boundary, the vertices fall outside the margin and no connection is made.
This is particularly common with procedurally placed tiles. Floating-point imprecision in tile positioning can introduce sub-pixel gaps that are invisible to the eye but large enough to exceed the connection margin. A tile at position (16.0, 0, 0) might actually be at (16.000001, 0, 0), and if the navigation mesh edge is exactly at the tile’s origin, that tiny offset breaks the connection.
Bake timing and map update order. When you instance a scene at runtime, its NavigationRegion bakes the navigation mesh (if not pre-baked) and registers with the navigation server. But the server only recalculates edge connections on its next map update, which happens once per physics frame. If you instance two scenes in the same frame, the second scene’s region might not be registered when the first scene’s connections are calculated. The server sees only one region, finds no neighbors, and marks all edges as free. On the next frame, the second region is present but the server does not automatically re-check the first region’s connections.
The Fix
Step 1: Verify navigation layers match. Check every NavigationRegion in every scene and confirm they use the same layer bitmask. For most games, all walkable regions should be on layer 1.
func verify_navigation_layers() -> void:
var regions = get_tree().get_nodes_in_group("nav_regions")
for region in regions:
if region is NavigationRegion3D:
if region.navigation_layers != 1:
push_warning(
"Region '%s' uses layer %d, expected 1"
% [region.name, region.navigation_layers]
)
# Ensure use_edge_connections is enabled
if !region.use_edge_connections:
push_warning(
"Region '%s' has edge connections disabled"
% region.name
)
region.use_edge_connections = true
Add each NavigationRegion to a group (e.g., nav_regions) so you can iterate over all of them easily. The use_edge_connections property must also be true for the server to consider connecting a region’s edges to other regions. It defaults to true, but double-check it.
Step 2: Increase the edge connection margin. Set the margin on the navigation map itself, not on individual regions. The map margin applies globally to all edge connection checks.
func configure_navigation_map() -> void:
var map_rid = get_world_3d().navigation_map
# Default is often 1.0 - increase for tiled worlds
NavigationServer3D.map_set_edge_connection_margin(map_rid, 2.0)
# For 2D, use NavigationServer2D instead
# var map_2d = get_world_2d().navigation_map
# NavigationServer2D.map_set_edge_connection_margin(
# map_2d, 4.0)
print("Nav map margin set to 2.0")
A margin of 2.0 units is a safe starting point for 3D tile-based worlds where tiles are 16 to 32 units wide. For 2D, you may need a larger value (4.0 to 8.0 pixels) depending on your pixel scale. The margin should be larger than any floating-point imprecision in your tile placement but smaller than the width of walls or obstacles, otherwise the server might connect edges through thin walls.
Force a Map Update After Loading
After all scenes are loaded and positioned, force the navigation server to recalculate all connections. This ensures every region is considered in the connection pass, regardless of load order.
Call NavigationServer3D.map_force_update(map_rid) after the last scene is added. This triggers an immediate recalculation of the entire navigation map, including edge connections between all registered regions. Without this call, you are at the mercy of the physics frame timing, and regions loaded in the same frame may miss each other.
For streaming worlds where scenes are loaded and unloaded dynamically, call map_force_update() each time a new chunk is added or removed. The cost is proportional to the number of regions on the map. For a handful of regions it is negligible; for hundreds of regions, consider batching loads and updating once per batch rather than per region.
Debugging Connections
Use the debug visualization to verify connections are working. In the editor, Debug > Visible Navigation draws navigation meshes with connected edges in blue and free edges in red (colors may vary by Godot version). At runtime, you can query the navigation server directly to inspect connections.
Call NavigationServer3D.map_get_regions(map_rid) to get all registered region RIDs. For each region, call NavigationServer3D.region_get_connections_count(region_rid) to see how many external connections it has. A region with zero connections is an island. Check its layer, its edge connection setting, and the positions of its boundary vertices relative to neighboring regions.
If edges appear close enough visually but still do not connect, print the exact vertex positions of the boundary edges from both regions and compare them. A gap of 0.001 units is enough to break connections with the default margin. This is the telltale sign that floating-point tile placement is the culprit.
“Navigation connection is geometry-based, not scene-based. The server does not know or care which scene a region came from. It only sees edges, distances, and layers. If the numbers do not match, there is no path.”
Related Issues
If your NavigationAgent finds a path but jitters at region boundaries, see NavigationAgent Jittering at Waypoints for path smoothing and avoidance radius tuning. If navigation meshes are not baking correctly from collision shapes, check Navigation Mesh Not Baking from Collision Shapes for source geometry settings.
Call map_force_update() after every batch of scene loads — don’t rely on the automatic per-frame update.