In this lesson, you will learn how to bridge your NavMeshAgent movement logic with your modular state machine architecture so your AI only navigates when its current state requires it.
The Problem of Decentralization
Previously, you learned to build autonomous movement using NavMeshAgent and modular state machines separately. If you simply keep agent.SetDestination() inside an Update loop on your main character controller, the agent will fight against the logic of your states. If the agent is told to move in an "Idle" state, your architecture becomes brittle.
Integrating via State Behaviors
To maintain clean, decoupled code, treat the NavMeshAgent as a component that the state machine "borrows" only when active. By passing a reference of the NavMeshAgent into your state classes during initialization, each state can define exactly how—or if—the agent should move.
Consider a ChaseState class:
public class ChaseState : IState
{
private NavMeshAgent _agent;
private Transform _target;
public ChaseState(NavMeshAgent agent, Transform target)
{
_agent = agent;
_target = target;
}
public void OnEnter() => _agent.isStopped = false;
public void Tick()
{
// Only command movement when in this specific state
_agent.SetDestination(_target.position);
}
public void OnExit() => _agent.isStopped = true;
}
Strategic Control
By encapsulating the movement call within Tick(), you ensure that navigation logic is bound to the state's lifecycle. When the state machine transitions to an "Idle" or "Attack" state, you call OnExit(), which halts the agent. This prevents "state bleeding," where an AI continues to calculate paths or move toward a target even after the game logic dictates it should have stopped.
This approach keeps your MonoBehaviour thin, as it acts only as a container for the agent and the state machine, while the state objects handle the "when" and "where" of your pathfinding.
Key Takeaway: Encapsulate NavMeshAgent commands within state behaviors to ensure movement logic is inherently synchronized with your AI’s active state.