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

ScriptResponsibility
NavMover.csWraps 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.csPlain 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.csThe hybrid route with no agent at all: NavMesh.CalculatePath + WaypointPathFollower + CharacterController
SoldierAI.csBehaviour 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)

  1. Unity 6 project. Make sure the AI Navigation package (com.unity.ai.navigation) is installed (Window > Package Manager). It provides the NavMesh Surface component; NavMeshAgent itself is built into the engine.
  2. Add a Plane (scale 3) and a few Cubes as walls. Put the walls on a new layer called Walls.
  3. Create an empty GameObject Navigation, add NavMesh Surface, and click Bake.
  4. Player: a Capsule at one corner. No script: drag it in the Scene view during Play mode.
  5. Cover points: empty GameObjects placed just behind walls. Patrol points: three or four empties in the open.
  6. Soldier: a Capsule with NavMeshAgent, NavMover and SoldierAI. Assign the player, cover points, patrol points and set Wall Mask to Walls.
  7. Add two or three more soldiers so separation and avoidance have something to do.
  8. 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, set agent.autoBraking = true. Watch the soldier ease off every time the destination updates.
  • Stutter on purpose: add mover.Stop() at the top of Chase(). This is what calling ResetPath() 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

  1. Replace NavMesh.CalculatePath in CalculatePathMover with the grid A* from pathfinding-algorithms and pass a grid line-of-sight test to SetPath.
  2. 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).
  3. Drive an Animator from agent.velocity (agent leads) or from root motion with updatePosition = false (animation leads); never both.

Source files

4 items under this folder.