Quick answer: NavigationAgent overshooting is usually caused by the target_desired_distance being too small relative to the agent's movement speed per frame. The agent moves past the target point before the next physics frame can detect arrival.
Here is how to fix Godot NavigationAgent overshooting target. Your NavigationAgent reaches the destination and then keeps going, jittering back and forth around the target or sliding straight past it. The agent oscillates, overshoots, or never triggers navigation_finished. This is one of the most common navigation bugs in Godot 4.x, and it comes down to how distance thresholds interact with your movement speed.
The Symptom
You set a target position on a NavigationAgent2D or NavigationAgent3D, and the agent moves toward it. But instead of stopping cleanly at the destination, one of these things happens:
- The agent slides past the target point and then turns around, creating a jittery oscillation loop.
- The agent gets close to the target but
is_navigation_finished()never returnstrue, so your movement code keeps running. - The agent reaches the general area but the
navigation_finishedsignal never fires, leaving the character in a perpetual walking state. - The agent reaches the target on slow speeds but overshoots consistently at higher movement speeds.
This is especially noticeable when your character has a high movement speed or when the physics tick rate is lower than expected. The problem scales with speed—a slow-moving NPC might work fine while a fast-moving one overshoots every time.
What Causes This
The root cause is a mismatch between the agent’s per-frame movement distance and the distance thresholds configured on the NavigationAgent node. Godot’s navigation system uses two key properties to decide when the agent has “arrived” at a point:
- path_desired_distance — How close the agent must be to an intermediate waypoint before advancing to the next one in the path. Default is 20 pixels (2D) or 1.0 meters (3D).
- target_desired_distance — How close the agent must be to the final destination before the navigation is considered finished. Default is 10 pixels (2D) or 1.0 meters (3D).
If your agent moves 300 pixels per frame at high speed, but target_desired_distance is set to the default 10 pixels, the agent will jump from 15 pixels away to 285 pixels past the target in a single frame. It never occupies the 10-pixel arrival zone, so arrival is never detected.
The same issue applies to path_desired_distance for intermediate waypoints. If the agent skips over the waypoint threshold, it may loop back, creating the classic oscillation pattern where the character wobbles around a corner or waypoint.
A secondary cause is not using the velocity_computed signal when avoidance is enabled. If you set the velocity on the agent but then ignore the computed safe velocity, the agent moves at full speed without any deceleration near the target.
The Fix
The fix involves adjusting the distance thresholds to account for your agent’s movement speed, and optionally adding deceleration logic near the target. Here is a complete working example:
extends CharacterBody2D
@export var move_speed: float = 200.0
@export var arrival_threshold: float = 10.0
@export var deceleration_radius: float = 50.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
func _ready():
# Set thresholds relative to movement speed
# Rule of thumb: threshold >= speed * delta * 2
nav_agent.path_desired_distance = 20.0
nav_agent.target_desired_distance = 20.0
# Connect the velocity_computed signal for avoidance
nav_agent.velocity_computed.connect(_on_velocity_computed)
func set_target(target_pos: Vector2):
nav_agent.target_position = target_pos
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)
# Decelerate near the final target
var distance_to_target = global_position.distance_to(
nav_agent.target_position
)
var speed = move_speed
if distance_to_target < deceleration_radius:
speed = lerp(0.0, move_speed,
distance_to_target / deceleration_radius)
speed = max(speed, 20.0) # minimum speed to avoid stalling
var desired_velocity = direction * speed
nav_agent.velocity = desired_velocity
func _on_velocity_computed(safe_velocity: Vector2):
velocity = safe_velocity
move_and_slide()
The critical changes are:
- Increase
target_desired_distance— Setting this to 20.0 means the agent considers itself “arrived” when within 20 pixels. For a character moving at 200 pixels/second at 60 FPS, each frame covers about 3.3 pixels, so 20 pixels gives a comfortable margin. - Increase
path_desired_distance— Same logic for intermediate waypoints. This prevents oscillation at corners. - Add deceleration near the target — The
deceleration_radiuscreates a zone where the agent slows down as it approaches, making it nearly impossible to overshoot. - Use
velocity_computed— By settingnav_agent.velocityand responding to thevelocity_computedsignal, the avoidance system can adjust the velocity smoothly.
For 3D projects, the same principles apply with NavigationAgent3D. Replace Vector2 with Vector3 and use meters instead of pixels for your thresholds:
# 3D equivalent thresholds
nav_agent.path_desired_distance = 1.5
nav_agent.target_desired_distance = 1.5
Why This Works
The NavigationAgent checks arrival by comparing the distance between the agent’s current position and the target against the threshold value. This check happens once per physics frame. If the agent covers more distance in a single frame than the threshold radius, it jumps clean over the detection zone.
By increasing the threshold, you widen the detection zone so the agent cannot skip over it in a single frame. The deceleration radius adds a second layer of safety—even if the threshold were slightly too small, the agent is moving slowly enough near the target that it will be caught by the check.
The velocity_computed signal is important because it ties into Godot’s built-in avoidance system. When you set nav_agent.velocity, the avoidance system processes it and emits velocity_computed with a safe velocity that accounts for nearby agents and obstacles. This creates smoother movement overall and prevents the sudden velocity changes that contribute to overshooting.
A good rule of thumb: set your distance thresholds to at least twice the maximum distance your agent can travel in a single physics frame. For an agent at 200 px/s and 60 FPS, that’s about 6.6 px per frame, so a threshold of 15–20 px is safe.
Related Issues
If your navigation agent is reaching its target but the pathfinding itself seems wrong—walking through walls or ignoring obstacles—that is a different problem related to avoidance layers and obstacle configuration. See Fix: Godot Pathfinding Ignoring Obstacles or Navigation Modifiers.
For performance issues with navigation on large tile-based maps, check Fix: Godot TileMap Performance Drops with Large Maps, which covers chunk-based loading strategies that can also affect navigation mesh baking times.
Widen the arrival zone. Slow down on approach. No more jitter.