Quick answer: NavigationObstacle nodes affect avoidance behavior, not the navigation mesh itself. If your navigation mesh is baked over the obstacle area, the pathfinding algorithm will route through it.
Here is how to fix Godot pathfinding ignoring obstacles. You have placed NavigationObstacle2D or NavigationObstacle3D nodes in your scene, but your agents walk straight through them as if they do not exist. Alternatively, you have set up navigation links or modifiers but the pathfinding algorithm completely ignores them. This is a layer configuration issue, and it is one of the most frequently misunderstood parts of Godot 4’s navigation system.
The Symptom
You add a NavigationObstacle2D to your scene—maybe a barrel, a fence, or a dynamic enemy—and expect your NavigationAgent to route around it. But the agent plots a path straight through the obstacle. You see one or more of these behaviors:
- The agent’s path line (visible in debug mode) goes directly through the obstacle’s position.
- NavigationObstacle nodes are in the scene tree but have no visible effect on agent movement.
- Static obstacles that were supposed to carve holes in the navigation mesh are still walkable.
- Navigation links between disconnected regions are placed but never used by the pathfinding algorithm.
- Navigation modifiers (cost or travel type changes) have no effect on the chosen path.
The frustrating part is that everything looks correct in the inspector. The nodes are there, the properties are set, but the system behaves as if the obstacles are invisible.
What Causes This
There are three distinct causes, depending on which part of the navigation system you are working with:
Cause 1: Avoidance layers are mismatched. Godot 4’s navigation uses a layer/mask system similar to collision layers. Each NavigationObstacle has avoidance_layers (which layers it occupies), and each NavigationAgent has avoidance_mask (which layers it responds to). If there is no overlap between the obstacle’s layers and the agent’s mask, the agent will completely ignore the obstacle. By default, both are set to layer 1, but if you have changed either one, they may no longer match.
Cause 2: The obstacle radius is zero or too small. NavigationObstacle nodes have a radius property that defines the avoidance area. If this is left at 0 (the default for newly created nodes in some Godot versions), the obstacle has no avoidance footprint and agents have nothing to avoid.
Cause 3: Confusing avoidance with pathfinding. This is the most common conceptual mistake. NavigationObstacle nodes affect the real-time avoidance system, not the pathfinding algorithm. The pathfinding algorithm only considers the navigation mesh. If your navigation mesh is baked over the area where the obstacle sits, the pathfinder will happily route through it. The avoidance system may push the agent aside at the last moment, but the path itself will still go through the obstacle area.
The Fix
The fix depends on whether you need dynamic avoidance or static pathfinding exclusion. Most projects need both.
Step 1: Fix avoidance layer configuration.
extends CharacterBody2D
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
func _ready():
# Enable avoidance on the agent
nav_agent.avoidance_enabled = true
nav_agent.radius = 16.0
# Set which avoidance layers this agent avoids
# Bit 1 = general obstacles, Bit 2 = enemies
nav_agent.avoidance_mask = 1 | 2 # avoid layers 1 and 2
nav_agent.avoidance_layers = 1 # this agent is on layer 1
# Connect the velocity_computed signal
nav_agent.velocity_computed.connect(_on_velocity_computed)
func _physics_process(delta):
if nav_agent.is_navigation_finished():
return
var next_pos = nav_agent.get_next_path_position()
var direction = global_position.direction_to(next_pos)
nav_agent.velocity = direction * 200.0
func _on_velocity_computed(safe_velocity: Vector2):
velocity = safe_velocity
move_and_slide()
Step 2: Configure the obstacle correctly.
# On the obstacle node (e.g., a barrel or crate)
extends Node2D
@onready var obstacle: NavigationObstacle2D = $NavigationObstacle2D
func _ready():
# Set a meaningful radius (in pixels for 2D)
obstacle.radius = 32.0
# Set which avoidance layer this obstacle occupies
obstacle.avoidance_layers = 1
# Enable the obstacle to affect avoidance
obstacle.avoidance_enabled = true
Step 3: For static obstacles, exclude them from the navigation mesh. If the obstacle is static and should never be walked through, the correct approach is to ensure the navigation mesh does not cover that area. Add the obstacle’s collision shape to the geometry that the NavigationRegion2D uses for baking:
# In the NavigationRegion2D setup
# 1. Set the NavigationPolygon source geometry mode
# to "Root Node Children"
# 2. Add your obstacle's StaticBody2D as a child
# of the NavigationRegion2D (or in parsed groups)
# 3. Set the obstacle's collision layer to match
# the parsed collision mask in NavigationPolygon
# 4. Re-bake the navigation mesh
# To bake at runtime after adding obstacles:
@onready var nav_region: NavigationRegion2D = $NavigationRegion2D
func rebake_navigation():
nav_region.bake_navigation_polygon()
Step 4: For navigation links, verify the connection. Navigation links connect two disconnected navigation mesh regions. Both ends of the link must be within a valid navigation mesh polygon, and the link must be enabled:
# Verify a NavigationLink2D is working
@onready var nav_link: NavigationLink2D = $NavigationLink2D
func _ready():
nav_link.enabled = true
nav_link.bidirectional = true
# Both start and end must be on valid nav mesh
var map_rid = nav_link.get_navigation_map()
var start_on_mesh = NavigationServer2D.map_get_closest_point(
map_rid, nav_link.start_position
)
var end_on_mesh = NavigationServer2D.map_get_closest_point(
map_rid, nav_link.end_position
)
print("Link start maps to: ", start_on_mesh)
print("Link end maps to: ", end_on_mesh)
Why This Works
Godot 4’s navigation system has two separate subsystems that are often confused:
- Pathfinding uses the navigation mesh to compute the shortest path from A to B. It only knows about the mesh geometry. If the mesh covers an area, pathfinding will route through it regardless of any obstacles.
- Avoidance is a real-time system that adjusts the agent’s velocity to steer around nearby obstacles and other agents. It operates on the layer/mask system and requires
avoidance_enabled = trueon the agent.
For a complete solution, static obstacles should be excluded from the navigation mesh (so pathfinding routes around them) while dynamic obstacles should use the avoidance system (so agents steer around them in real time). The layer/mask configuration ensures that agents only respond to relevant obstacles, which is important for performance when you have many agents and obstacles in a scene.
Think of it this way: pathfinding is the GPS that plans the route. Avoidance is the driver who swerves to avoid a pedestrian. You need both systems configured correctly for reliable navigation.
Related Issues
If your agent is reaching the target area but overshooting or jittering at the destination, that is a distance threshold issue rather than an obstacle problem. See Fix: Godot NavigationAgent Overshooting Target Point for the solution.
For tile-based games where navigation interacts with TileMap layers, rendering order issues can make it hard to tell where the navigation mesh actually is. Check Fix: Godot TileMap Layers Drawing in Wrong Order if your debug visualization seems off.
Avoidance is not pathfinding. Configure both.