In this lesson, you will learn how to trigger asynchronous, time-delayed actions within your State Pattern architecture without freezing your main game loop.
The Problem with Blocking Logic
In our current state machine architecture, a state might need to perform an action over time—like a "Charging" state that waits three seconds before firing a projectile, or a "Stunned" state that automatically reverts to "Idle" after five seconds. If we used Thread.Sleep or complex Update timers, we would block the main thread or clutter our state logic with messy flag-checking.
Leveraging Coroutines in States
By integrating Coroutines into our State objects, we can offload timing logic to Unity’s engine, keeping our State transition logic clean and non-blocking. Because our state machine controller is responsible for the state lifecycle, we can initiate the coroutine when Enter() is called and, crucially, ensure it is stopped if the state exits prematurely.
Consider this implementation:
public class ChargingState : IState
{
private Coroutine _chargeCoroutine;
private StateMachine _controller;
public void Enter(StateMachine controller)
{
_controller = controller;
// Start the coroutine via the controller monobehaviour
_chargeCoroutine = _controller.StartCoroutine(ChargeRoutine());
}
private IEnumerator ChargeRoutine()
{
yield return new WaitForSeconds(3.0f);
_controller.TransitionTo(new FiringState());
}
public void Exit()
{
// Stop the routine if we leave state before 3 seconds
if (_chargeCoroutine != null)
_controller.StopCoroutine(_chargeCoroutine);
}
}
Why This Works
Using StartCoroutine allows the game to continue processing input and rendering frames while waiting for our state-specific duration to pass. By wrapping the coroutine in the Exit() method, we prevent "orphaned" logic—where a state fires a transition after it has already been destroyed or replaced. This keeps our states encapsulated, modular, and responsive to abrupt interruptions.
Key Takeaway: Use Coroutines within your states to manage time-based transitions, ensuring you always store a reference to the coroutine to safely clean it up during the Exit() transition.