Choking on WaitForSeconds coroutine (solved)

Gents - I have a Navmesh Agent roaming around a map. When the player approaches within a certain distance from the agent, the agent goes into a “suspicious” mode and pauses for 3 seconds. Within that 3-second window, if the player goes outside the bound, then the agent moves on. Otherwise, the agent comes after the player.

Here’s the corresponding code:

void Update() {
    distanceToPlayer = CheckDistance();   // simply returning distance between the agent & player
    if (distanceToPlayer < suspicionDistance) {
        agent.Stop();    // make agent appear to be thinking...
        feelingSuspicious = true;

        StartCoroutine(ResolveSuspicion(3.0f)); // make agent stand still for 3 seconds until next move
        if (chasingPlayer) {
            agent.destination = player.transform.position; // get the intruder!
        } else {
            agent.Resume(); // hmm, must've been a rat...
        }
        ....
    }
    ....
}

IEnumerator ResolveSuspicion(float duration) {
    yield return new WaitForSeconds(duration);
    float distanceToPlayer = CheckDistance();
    if (distanceToPlayer < suspicionDistance) {
        chasingPlayer = true;
    }
}

Everything works as intended, except the agent is supposed to pause for 3 seconds as soon as the player steps inside the ‘suspicionDistance’ but it doesn’t – it just comes straight to the player without a pause. I double checked the values for the various distance variables and they are all correct.

I’m thinking I must be misusing the WaitForSeconds method, but can’t seem to figure out how to go about making it work.

===== (UPDATE) =====

For anyone who’s interested, here’s my solution that seems to work:

IEnumerator ResolveSuspicion(float duration) {

    agent.Stop();
    feelingSuspicious = true;

    yield return new WaitForSeconds(duration);

    float distanceToPlayer = CheckDistance();
    if (distanceToPlayer < suspicionDistance) {
        chasingPlayer = true;
    } else {
        agent.Resume();
    }
}

void Update() {
    distanceToPlayer = CheckDistance();

    if (distanceToPlayer < suspicionDistance) {
        if (!chasingPlayer) {
            StartCoroutine(ResolveSuspicion(3.0f));
        } else {
            agent.destination = player.transform.position;
            agent.Resume();
        }
        ....
    } else {
        if (feelingSuspicious) {
            feelingSuspicious = false;
            agent.Resume();
        }
    }
}

You’re starting a new coroutine every frame in Update as long as distanceToPlayer < suspicionDistance. In general it’s not a good idea to mix Update with coroutines; it’s technically possible, but requires what usually ends up being a lot of convoluted code. I would recommend removing Update and using only coroutines.

In my experience, adding a bool to check whether or not your coroutine is already running is the best way to go.

Try this.

bool runningCoroutine;

 void Update() {
     distanceToPlayer = CheckDistance();   // simply returning distance between the agent & player
     if (distanceToPlayer < suspicionDistance) {
         agent.Stop();    // make agent appear to be thinking...
         feelingSuspicious = true;
 if(!runningCoroutine)
         StartCoroutine(ResolveSuspicion(3.0f)); // make agent stand still for 3 seconds until next move
         if (chasingPlayer) {
             agent.destination = player.transform.position; // get the intruder!
         } else {
             agent.Resume(); // hmm, must've been a rat...
         }
         ....
     }
     ....
 }
 
 IEnumerator ResolveSuspicion(float duration) {
     yield return new WaitForSeconds(duration);
     float distanceToPlayer = CheckDistance();
     if (distanceToPlayer < suspicionDistance) {
         chasingPlayer = true;
     }
     runningCoroutine = false;
 }

The problem is that a coroutine is another bit of code (routine) that runs at the same time as the code following the call to it (co). It is NOT a wait or a timer, but rather a second set of code running in parallel.

So take the following code of yours:

   StartCoroutine(ResolveSuspicion(3.0f)); // make agent stand still for 3 seconds until next move
         if (chasingPlayer) {
             agent.destination = player.transform.position; // get the intruder!
         } else {
             agent.Resume(); // hmm, must've been a rat...
         }

What this actually does is continue to the if / else statement immediately, regardless of the coroutine’s code. So it will either automatically go straight to the destination or run agent.Resume(), depending on whatever value was already set for chasingPlayer.

Try putting that if / else statement into the coroutine itself, located after the yield - that way it will act as a timer and not be fired off until after duration seconds.