terminalCode Example
NavMesh Links: Custom Jump and Ladder Traversal for AI Agents
NavMesh Links connect disconnected NavMesh regions (jumps, ladders, teleports). By default agents linearly interpolate between endpoints, but you can implement custom traversal for realistic movement.
Basic Link Setup
Add a NavMeshLink component to a GameObject. Edit start/end positions via inspector or editor tools.
Custom Traversal: Parabolic Jump
Disable auto-traversal and drive movement manually:
CSHARP
public sealed class JumpLink : NavMeshLink
{
protected override void OnLinkEntered( NavMeshAgent agent )
{
ParabolicJump( agent );
}
private async void ParabolicJump( NavMeshAgent agent )
{
var start = agent.CurrentLinkTraversal.Value.AgentInitialPosition;
var end = agent.CurrentLinkTraversal.Value.LinkExitPosition;
var heightDifference = end.z - start.z;
var peakHeight = MathF.Abs( heightDifference ) + 25f;
var mid = (start + end) / 2f;
var startToMid = mid.WithZ( peakHeight ) - start;
var midToEnd = end - mid.WithZ( peakHeight );
var duration = MathF.Max( 0.75f, (startToMid + midToEnd).Length / agent.MaxSpeed );
TimeSince timeSinceStart = 0;
while ( timeSinceStart < duration )
{
var t = timeSinceStart / duration;
var newPosition = Vector3.Lerp( start, end, t );
var yOffset = 4f * peakHeight * t * (1f - t);
newPosition.z = MathX.Lerp( start.z, end.z, t ) + yOffset;
agent.SetAgentPosition( newPosition );
await Task.Frame();
}
agent.SetAgentPosition( end );
agent.CompleteLinkTraversal();
}
}Custom Traversal: Ladder Climb
Three-phase movement: align → climb vertically → walk off:
CSHARP
public sealed class LadderLink : NavMeshLink
{
protected override void OnLinkEntered( NavMeshAgent agent )
{
ClimbLadder( agent );
}
private async void ClimbLadder( NavMeshAgent agent )
{
var initialPos = agent.CurrentLinkTraversal.Value.AgentInitialPosition;
var start = agent.CurrentLinkTraversal.Value.LinkEnterPosition;
var endVertical = start.WithZ( agent.CurrentLinkTraversal.Value.LinkExitPosition.z );
var end = agent.CurrentLinkTraversal.Value.LinkExitPosition;
var climbSpeed = 100f;
var startDuration = (start - initialPos).Length / climbSpeed;
var climbDuration = (endVertical - start).Length / climbSpeed;
var endDuration = (end - endVertical).Length / climbSpeed;
var totalTime = startDuration + climbDuration + endDuration;
TimeSince timeSinceStart = 0;
while ( timeSinceStart < totalTime )
{
Vector3 pos;
if ( timeSinceStart < startDuration )
pos = Vector3.Lerp( initialPos, start, timeSinceStart / startDuration );
else if ( timeSinceStart < startDuration + climbDuration )
pos = Vector3.Lerp( start, endVertical, (timeSinceStart - startDuration) / climbDuration );
else
pos = Vector3.Lerp( endVertical, end, (timeSinceStart - startDuration - climbDuration) / endDuration );
agent.SetAgentPosition( pos );
await Task.Frame();
}
agent.SetAgentPosition( end );
agent.CompleteLinkTraversal();
}
}Key API
- Agent.AutoTraverseLinks = false — disable default linear interpolation
- Agent.CurrentLinkTraversal — provides LinkEnterPosition, LinkExitPosition, AgentInitialPosition
- Agent.SetAgentPosition( pos ) — manually update agent position during traversal
- Agent.CompleteLinkTraversal() — signal traversal complete, resume normal navigation
- NavMeshLink.LinkEntered / LinkExited — events for subscription-based handling
Was this helpful?