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 ?

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.

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.

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;
                      }
    }

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");
    }
}

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.