- Home /
Update SetDestination only every 0.2s with coroutines ?
Hello, I am trying to have a more smoother game by not calculating a new AI path every update frame. I am trying to use coroutines because I heard it is more efficient/effective than InvokeRepeating(). At the same time I want to make the agent stop calculating a path when it is in a certain distance, or out of sight, but it seems to work with Agent.isStopped . Heres the shortened code:
void Start() {
navAgent = GetComponent<NavMeshAgent> ();
StartCoroutine(recalcPath());
}
IEnumerator recalcPath ()
{
while(true){ //makes unity freeze
if (updatePath == true) {
yield return new WaitForSeconds (2f);
navAgent.SetDestination (target.position);
Debug.Log ("NavAgent called");
}
//do not use StartCoroutine (recalcPath ()); it makes unity also freeze
}
}
By using it as I did, the unity editor just freezes and I need to force close it. How is it normally made ?
Answer by Chik3r · Feb 28, 2018 at 02:41 PM
Hi @Slastraf
Your code but with what you want:
void Start() {
navAgent = GetComponent<NavMeshAgent> ();
StartCoroutine(recalcPath());
}
IEnumerator recalcPath ()
{
while(true){ //makes unity freeze
// updatePath = Vector3.Distance(transform.position, target.position) < .3f) ? false : true // Uncomment this line if you want it to stop at a certain distance (.3f).
if (updatePath == true) {
yield return new WaitForSeconds (.2f); // Wait for .2 seconds or 200 miliseconds
navAgent.SetDestination (target.position);
Debug.Log ("NavAgent called");
}
yield return null; //So that unity doesn't crash
}
}
Also, Unity was freezing with the coroutine because whenever "updatePath" was false there would be no yield return, so it would wait forever.
this also worked, but was closest to the original script. tough I would add what Harinezumi added with the memory efficient use of wait
Answer by Bunny83 · Feb 28, 2018 at 02:52 PM
Just think logically. It "updatePath" is false the if statement will not be executed and you end up with an empty while loop:
while(true){
}
The most important thing you have to keep in mind is that when you use an infinite loop in a coroutine that you always need to yield every iteration (or at least every other iteration). Otherwise your main thread will be caught inside the loop and it can never get out of it.
So one solution is to do something like this:
while(true)
{
if (updatePath == true)
{
navAgent.SetDestination (target.position);
Debug.Log ("NavAgent called");
yield return new WaitForSeconds (2f);
}
else
yield return null;
}
When updatePath is false we just yield null so the coroutine does check updatePath every frame. If updatePath is true it will set the destination and yield for 2 seconds.
If you don't need the SetDirection to react immediately to a change of updatePath you can also do
while(true)
{
if (updatePath == true)
{
navAgent.SetDestination (target.position);
Debug.Log ("NavAgent called");
}
yield return new WaitForSeconds (2f);
}
Here we simply yield outside the if statement. However that means when updatePath is set to true it can take up to 2 seconds until the first SetDestination will be called.
Another way is to implement a blocking loop inside the while loop like this:
while(true)
{
while(!updatePath)
yield return null;
navAgent.SetDestination (target.position);
Debug.Log ("NavAgent called");
yield return new WaitForSeconds (2f);
}
When updatePath is true the inner while loop will be skipped and every 2 seconds we will call SetDestination. If updatePath is false the inner while loop will block the coroutine by simply yielding null. So the inner while loop will run once every frame until updatePath turns true.
Answer by dishant27 · Feb 28, 2018 at 02:37 PM
Unity is freezing because for while(true) condition which always true and your code runs into never ending infinite loop. You can change your recalcPath method:
IEnumerator recalcPath ()
{
yield return new WaitForSeconds (0.2f);
navAgent.SetDestination (target.position);
Debug.Log ("NavAgent called");
if(updatePath)
{
StartCoroutine(recalcPath());
yield return null;
}
}
But when it reaches some distance it would continue recalculating. And Slastraf wants it to stop at a distance.
When the updatePath variable is false, it'll end. So, when he wants to stop, he can set updatePath = false anywhere in the script.
true, but you would need to call recalcPath () somewhere again, if the player is in range agian
This is generally a bad implementation. Constantly starting a new coroutine is not a good idea. Just use a loop. A coroutine is not a method but a statemachine. Each time you start a new coroutine you will create a new instance of that statemachine. Also the yield return null;
after your StartCoroutine is pretty pointless as nothing happens after it. You just keep the coroutine alive for one additional frame.
Apart from that he starts the coroutine in Start. So he wants the coroutine to keep running. In your case he has to actually start a new coroutine when he wants it to continue,
Answer by Harinezumi · Feb 28, 2018 at 02:42 PM
It seems updatePath
is not true when you enter the while loop, so it will never get updated, and so you will never call the yield instruction, which results in an infinite loop on the same frame, so Unity freezes.
You can rewrite it the following way:
IEnumerator recalcPath () {
YieldInstruction wait = new WaitForSeconds(0.2f); // more memory-efficient to reuse the wait
while (true) {
yield return wait;
navAgent.SetDestination(target.position); // probably you should check that target is not null
Debug.Log ("NavAgent.SetDestination() called");
}
}
Answer by Slastraf · Feb 28, 2018 at 03:29 PM
Most efficient and easiest to use version would be this:
IEnumerator recalcPath ()
{
YieldInstruction wait = new WaitForSeconds(0.2f); // more memory-efficient to reuse the wait
while(true)
{
if (updatePath)
navAgent.SetDestination (target.position);
yield return wait;
}
}
updatePath should be false at the start, and change the boolean if a new path should be calculated or not. If the agent needs to stop use Agent.isStopped = true but make sure to set the latter false again.
The problem with "yield return null;" is that the function or method will be stopped and therefore cannot be handled in update, because it needs to be called just once. In update you can se tthe booleans.