Object Avoidance
🛡️ Navigation Object Avoidance in Godot (RVO2)
In Intro Into Navigation In Godot, we looked at how an agent calculates a path from A to B across a static Navigation Region.
However, pathfinding alone only knows about baked, static level geometry. If five enemies all chase the player down the same narrow hallway, pathfinding will cause them to bunch up into a single point and clip inside each other's models.
Obstacle Avoidance solves this. Godot uses the RVO2 (Reciprocal Velocity Obstacles) algorithm on the NavigationServer to calculate collision-free movement between dynamic agents in real time.
⚖️ Pathfinding vs Avoidance: The Core Gap
| Feature | Pathfinding (NavigationMesh) | Avoidance (RVO2) |
|---|---|---|
| Scope | Global (entire map) | Local (immediate surroundings) |
| Geometry | Static walls, floors, ramps | Moving agents, temporary barricades |
| Frequency | Computed when target changes | Recalculated every physics frame |
| Cost | A* search on polygon graph | Geometric velocity cone clipping |
⚙️ The Avoidance Workflow (The Callback Loop)
Avoidance cannot be evaluated instantly on the same line of code because calculations are dispatched to the NavigationServer3D / NavigationServer2D.
Instead, avoidance requires a safe velocity callback loop:
- Calculate Intended Direction: Use the agent's pathfinding to find where it wants to go.
- Feed Intended Velocity: Call
nav_agent.set_velocity(intended_velocity). - Wait for Server Signal: The
NavigationServerprocesses all agents and emits the signalvelocity_computed(safe_velocity). - Move the Character: Apply
safe_velocitytomove_and_slide()inside that signal handler!
💻 GDScript Implementation Example
Here is a clean implementation for a CharacterBody3D enemy with avoidance enabled:
extends CharacterBody3D
class_name AvoidanceEnemy
@export var move_speed: float = 5.0
@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
func _ready() -> void:
# 1. Enable avoidance on the agent
nav_agent.avoidance_enabled = true
# 2. Connect the safe velocity callback
nav_agent.velocity_computed.connect(_on_velocity_computed)
func set_destination(target_pos: Vector3) -> void:
nav_agent.target_position = target_pos
func _physics_process(delta: float) -> void:
if nav_agent.is_navigation_finished():
return
# Get the next waypoint on the baked navigation mesh
var next_path_pos: Vector3 = nav_agent.get_next_path_position()
var move_dir: Vector3 = global_position.direction_to(next_path_pos)
var intended_velocity: Vector3 = move_dir * move_speed
if nav_agent.avoidance_enabled:
# Send intended velocity to NavigationServer for RVO avoidance
nav_agent.set_velocity(intended_velocity)
else:
# Direct movement if avoidance is disabled
_on_velocity_computed(intended_velocity)
# 3. Apply the calculated safe velocity here!
func _on_velocity_computed(safe_velocity: Vector3) -> void:
velocity = safe_velocity
move_and_slide()
If you call move_and_slide() directly inside _physics_process() using your intended velocity while avoidance_enabled = true, avoidance will fail because you are bypassing the server's computed safe_velocity!
🚧 Dynamic Obstacles (NavigationObstacle3D)
What if you have a wooden crate that players can push, or a blast door that closes?
You don't want to rebake the entire 3D NavigationMesh in real-time (baking is slow and CPU-heavy).
Instead, place a NavigationObstacle3D node on the dynamic object:
- Static/Stationary Mode: Carves a temporary 2D outline into the avoidance field.
- Dynamic/Moving Mode: Has its own velocity and radius, pushing agents out of the way as it moves.
- Cost: Virtually free compared to runtime mesh rebaking.
🎛️ Avoidance Layers & Masks
Just like physics layers, avoidance uses Layers and Masks:
- Avoidance Layers: Which groups this agent belongs to (e.g. Layer 1 = "Standard Foot Soldiers", Layer 2 = "Heavy Tanks").
- Avoidance Masks: Which groups this agent will actively swerve to avoid.
Tip: A heavy boss enemy can set its mask to ignore light foot soldiers, forcing the foot soldiers to swerve around the boss while the boss marches straight forward unhindered.
🔗 Related Notes
- Intro Into Navigation In Godot — Foundation of NavigationServer3D and NavigationAgent3D.
- Navigation Regions — Baking 2D and 3D walkable meshes.
- Intro Into Gdscript — Modern GDScript syntax, signals, and lifecycle.
- Godot Basics & Architecture — SceneTree and low-level servers.
- Game Development Hub — Main index for engine systems.