- Home /
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();
}
}
}
Answer by Addyarb · Apr 16, 2015 at 10:48 PM
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;
}
Per your suggestion, here's what I tried:
private bool feelingSuspicious = false;
void Update() {
distanceToPlayer = CheckDistance();
if (distanceToPlayer < distanceSuspicion) {
agent.Stop();
if (!feelingSuspicious) {
StartCoroutine(ResolveSuspicion(3f));
}
... // rest of the code is the same as before
}
IEnumerator ResolveSuspicion(float duration) {
feelingSuspicious = true;
yield return new WaitForSeconds(duration);
float distanceToPlayer = CheckDistance();
if (distanceToPlayer < distanceSuspicion) {
chasingPlayer = true;
}
feelingSuspicious = false;
}
I think this should take care of it, but it still produces the same result. :/
Can you confirm that you're not altering the "chasingPlayer" bool anywhere else in the script?
Yes, I confirm that 'chasingPlayer' is being altered only at line 19 and nowhere else in my entire code.
Is it the case that the first time around, your character waits 3 seconds, but then doesn't after the first time? I can't find any feasible way that he could just follow the target unless you're somehow using SetDestination() before you call your coroutine.
The agent doesn't pause at all - not even the first time around. What I also noticed is that even if I comment out the "chasingPlayer = true" part, the agent keeps on moving as if the player isn't there. I think the agent is continuing to stay on its predefined path, but I thought the "agent.Stop()" line was supposed to stop it.
Answer by Eric5h5 · Apr 16, 2015 at 10:40 PM
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.
While what you explained makes perfect sense, I'm not sure how I can refactor my code in a way that I can remove Update altogether. Don't I need Update to at least check on the distance of the agent and player on a real-time basis?
Answer by Zild · Apr 17, 2015 at 06:50 AM
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.
The rest of the answer looks okay, but coroutines are not run in parallel. (I promise).
Have a look here to confirm: http://docs.unity3d.com/$$anonymous$$anual/ExecutionOrder.html
The only way to create code running in parallel involves worker threads.
I think the last code snippet I added to the comment section does essentially what you suggested here. I currently have the "if (chasingPlayer) { ..." block commented out, and the ResolveSuspicion function contains the modified if-then-else block that should take care of agent's next movement. Or at least that's my understanding.