NavMesh steering: from decision to smooth motion (Unity 6 / C#)
Companion code for unity-navmesh (“From decision to motion” and “Smooth movement and retargeting”). It shows how a decision layer (a behaviour tree here; utility AI or a state machine work the same way) hands a destination to navigation, and how to get smooth, non-stuttering movement out of the result.
Files
| Script | Responsibility |
|---|---|
NavMover.cs | Wraps NavMeshAgent. MoveTo(goal) returns Running / Arrived / Failed and only re-paths when the goal has really moved. Optional custom steering: updatePosition = false, reads desiredVelocity, adds separation, moves the transform, writes nextPosition back |
WaypointPathFollower.cs | Plain C# class that follows any list of points (NavMesh corners or your own A*): line-of-sight smoothing, progress tracking, lookahead, Arrive on the last point only |
CalculatePathMover.cs | The hybrid route with no agent at all: NavMesh.CalculatePath + WaypointPathFollower + CharacterController |
SoldierAI.cs | Behaviour tree demo: take cover when under fire (tactical query with commitment) > chase when seen > patrol |
SoldierAI.cs uses BehaviourTree.cs from ../behaviour-tree/;
copy both folders into your project’s Assets.
Scene setup (about 15 minutes)
- Unity 6 project. Make sure the AI Navigation package
(
com.unity.ai.navigation) is installed (Window > Package Manager). It provides the NavMesh Surface component;NavMeshAgentitself is built into the engine. - Add a Plane (scale 3) and a few Cubes as walls. Put the walls on a
new layer called
Walls. - Create an empty GameObject
Navigation, add NavMesh Surface, and click Bake. - Player: a Capsule at one corner. No script: drag it in the Scene view during Play mode.
- Cover points: empty GameObjects placed just behind walls. Patrol points: three or four empties in the open.
- Soldier: a Capsule with NavMeshAgent, NavMover and
SoldierAI. Assign the player, cover points, patrol points and set
Wall Mask to
Walls. - Add two or three more soldiers so separation and avoidance have something to do.
- Optional: a Capsule with a CharacterController and CalculatePathMover, with the player as Goal.
What to try
- Retargeting: in
NavMover, set Repath Interval to 0 and Repath Distance to 0, then drag the player around. Compare the profiler and the motion with the defaults. - Auto braking: in
SoldierAI.Chase, setagent.autoBraking = true. Watch the soldier ease off every time the destination updates. - Stutter on purpose: add
mover.Stop()at the top ofChase(). This is what callingResetPath()between targets looks like. - Commitment: set Cover Recheck Interval to 0 and place two cover points at almost equal distances. Does the soldier ever hesitate between them? Why does a re-check interval plus “keep it unless compromised” fix it?
- Custom steering: tick Custom Steering on every soldier and send them through a doorway together. Raise Separation Weight until they stop bunching.
- Smoothing: on
CalculatePathMover, untick Smooth Path. NavMesh corners are already string-pulled, so the difference is small; the same follower on a grid A* path makes a much bigger difference.
Extension ideas
- Replace
NavMesh.CalculatePathinCalculatePathMoverwith the grid A* from pathfinding-algorithms and pass a grid line-of-sight test toSetPath. - Score cover with more than path length: distance from the player, facing, and whether another soldier already claimed it (see tactical-position-selection and combat-coordinator-pattern).
- Drive an Animator from
agent.velocity(agent leads) or from root motion withupdatePosition = false(animation leads); never both.