Quick answer: Either enable Auto Traverse Off Mesh Link on the agent, or implement a coroutine that animates across the link and calls agent.CompleteOffMeshLink() at the end. Without one of these, the agent waits forever.
An enemy patrols a level with a NavMeshLink connecting a balcony to the ground floor. The enemy reaches the balcony edge and stops. No animation, no fall, no error. agent.isOnOffMeshLink returns true, velocity is zero, and the agent never advances.
The Two Traversal Modes
NavMeshAgent has an Auto Traverse Off Mesh Link boolean. When on (the default in Unity 6), the agent teleports the moment it touches the link — useful for instantaneous transitions like a doorway. When off, the agent halts and the developer is expected to handle the cross-link movement manually. The mode that works for one game breaks another.
If a developer turned off Auto Traverse intending to add a jump animation later, then forgot to wire the manual traversal, the agent halts every time.
Fix 1: Re-enable Auto Traverse
Quickest fix when you don’t need custom animation:
var agent = GetComponent<NavMeshAgent>();
agent.autoTraverseOffMeshLink = true;
The agent now teleports across the link. The visual is abrupt but functional — appropriate for prototypes or for short links that read as a single step.
Fix 2: Manual Traversal with Coroutine
For a proper jump or climb:
using UnityEngine;
using UnityEngine.AI;
using System.Collections;
[RequireComponent(typeof(NavMeshAgent))]
public class AgentTraverser : MonoBehaviour
{
NavMeshAgent agent;
void Awake()
{
agent = GetComponent<NavMeshAgent>();
agent.autoTraverseOffMeshLink = false;
}
void Update()
{
if (agent.isOnOffMeshLink && !traversing)
StartCoroutine(Traverse());
}
bool traversing;
IEnumerator Traverse()
{
traversing = true;
var data = agent.currentOffMeshLinkData;
float duration = 0.5f;
float t = 0;
Vector3 start = transform.position;
Vector3 end = data.endPos + Vector3.up * agent.baseOffset;
while (t < duration)
{
t += Time.deltaTime;
float u = t / duration;
Vector3 pos = Vector3.Lerp(start, end, u);
pos.y += Mathf.Sin(u * Mathf.PI) * 1.5f; // arc
transform.position = pos;
yield return null;
}
agent.CompleteOffMeshLink();
traversing = false;
}
}
The agent.transform is moved manually along a parabolic arc. CompleteOffMeshLink returns the agent to normal navigation at the end. Trigger a jump animation by setting a parameter on Animator at the start of the coroutine.
Fix 3: NavMeshLink Cost Tuning
If the agent never even attempts a link — it walks the long way around — the link’s cost is too high. Open the NavMeshLink component, find the Cost Override, and set a positive but small value (e.g., 1.5). If left at -1, the agent uses the area cost; a high area cost discourages the link entirely.
Fix 4: NavMeshLink Width and Endpoints
NavMeshLink endpoints must sit on the NavMesh. If endPos is slightly above the navmesh, the agent reaches the link but can’t place its destination snapping point. Snap endpoints to the navmesh in the editor: select the link, use the “Auto-Update Positions” checkbox.
Verifying
Add a log at the start of the link state and at CompleteOffMeshLink:
Debug.Log($"Enter link at {Time.time}");
// ... traversal ...
Debug.Log($"Complete link at {Time.time}");
Before the fix, only the entry log fires. After, both fire with a duration matching your coroutine length. Visually, the agent moves smoothly along its arc and resumes pathing on the other side.
“Auto Traverse is the convenience setting. Manual traversal is where animation lives. Pick deliberately.”
If you ever turn Auto Traverse off, write the coroutine the same commit — never leave a project in “manual but not yet wired” state.