Quick answer: Only move the agent using the velocity from the velocity_computed signal. Tune time_horizon up and max_neighbors down to smooth crowding.
A crowd of NavigationAgent3D NPCs jitter back and forth when they cluster. They’re applying both desired and avoidance velocity, fighting each other.
Correct Avoidance Flow
func _physics_process(delta):
var next = nav_agent.get_next_path_position()
var desired = (next - global_position).normalized() * speed
nav_agent.velocity = desired # feed desired into avoidance
func _on_velocity_computed(safe_velocity):
velocity = safe_velocity # apply ONLY the safe velocity
move_and_slide()
Set desired velocity into the agent; the avoidance system returns safe_velocity via signal; apply that. Don’t apply desired directly.
Tune Parameters
- time_horizon: how far ahead to predict collisions. Higher (2–3s) = smoother, slower reactions.
- max_neighbors: cap considered agents. Lower (5–8) = less jitter in dense crowds.
- neighbor_distance: radius of awareness. Match to actual agent spacing.
- radius: agent’s physical size. Too large = constant avoidance.
Avoidance Priority
Set avoidance_priority so important agents (player-followers) push through, decoration NPCs yield. Reduces deadlock jitter.
Verifying
Spawn 50 agents converging on one point. They flow around each other smoothly, no vibration. Profiler shows avoidance cost within budget.
“Feed desired in, apply safe out. Mixing the two causes the fight that looks like jitter.”
For RTS-scale crowds (hundreds of units), consider flow-field pathfinding instead — per-agent RVO doesn’t scale past a few hundred.