Source file unity-csharp/game-ai/navmesh-steering/CalculatePathMover.cs from the GDnD code examples. Download raw file
// GDnD wiki example
// Demonstrates: the "hybrid" route: NavMesh.CalculatePath for the route, your own code for the movement.
// No NavMeshAgent at all: the path's corners go to WaypointPathFollower, and a CharacterController
// moves the body. Swap CalculatePath for your own grid A* and nothing else changes.
// Related pages: [[unity-navmesh]], [[pathfinding-algorithms]], [[steering-behaviours]]
// Requires: Unity 6, a baked NavMesh (AI Navigation package, NavMesh Surface component).
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(CharacterController))]
public class CalculatePathMover : MonoBehaviour
{
[SerializeField] private Transform goal;
[SerializeField] private float maxSpeed = 4f;
[SerializeField] private float acceleration = 12f;
[SerializeField] private float repathDistance = 1f; // re-plan when the goal moves this far
[SerializeField] private bool smoothPath = true;
private readonly WaypointPathFollower follower = new WaypointPathFollower();
private NavMeshPath path; // created in Awake, reused every re-plan
private CharacterController body;
private Vector3 plannedGoal = new Vector3(float.MaxValue, 0f, 0f);
private Vector3 velocity;
private void Awake()
{
body = GetComponent<CharacterController>();
path = new NavMeshPath();
follower.MaxSpeed = maxSpeed;
}
private void Update()
{
if (goal != null && (goal.position - plannedGoal).sqrMagnitude > repathDistance * repathDistance)
Replan(goal.position);
Vector3 desired = follower.DesiredVelocity(transform.position);
velocity = Vector3.MoveTowards(velocity, desired, acceleration * Time.deltaTime);
body.Move((velocity + Physics.gravity) * Time.deltaTime); // crude gravity keeps it grounded
if (velocity.sqrMagnitude > 0.01f)
transform.rotation = Quaternion.LookRotation(velocity);
}
private void Replan(Vector3 target)
{
plannedGoal = target;
// Synchronous: fine for one character re-planning occasionally; budget it for crowds.
if (NavMesh.CalculatePath(transform.position, target, NavMesh.AllAreas, path)
&& path.status != NavMeshPathStatus.PathInvalid)
{
follower.SetPath(path.corners,
smoothPath ? (a, b) => !NavMesh.Raycast(a, b, out _, NavMesh.AllAreas) : null);
}
}
private void OnDrawGizmosSelected()
{
if (path == null) return;
Gizmos.color = Color.cyan;
for (int i = 1; i < path.corners.Length; i++)
Gizmos.DrawLine(path.corners[i - 1], path.corners[i]);
}
}