2
votes

So I've ran into something weird, when using a co-routine in Unity to simulate a NPC (walks towards a target, idles for x seconds, walks to a target --repeat--).

I've found that starting the co-routine with a variable that holds the IEnumerator will not run twice, while starting the co routine with the method passed in directly runs as expected, repeatable.

Why does this work this way? Whats happening 'under the hood'? I cant wrap my head around why this is, and it bugs me.

Below my IEnumerator method that simulates idle time.

private IEnumerator sitIdle()
{
    var timeToWait = GetIdleTime();
    _isIdle = true;
    yield return new WaitForSeconds(timeToWait);
    _isIdle = false;
} 

If this gets called a second time per Scenario #1 (below), it runs as expected when called multiple times. It just repeats the process over and over.

If however, if it gets called per Scenario #2 (below) as a variable, it will kick off once, but refuse to enter a second time and just plain 'skip' it in code.

void LateUpdate()
    {
        _idleRoutine = sitIdle; //this is not actually in the late update, just moved here for reference.

        if (_agent.hasPath)
        {
            if (isTouchingTarget())
            {
                StartCoroutine(sitIdle2()); //Scenario #1

                StartCoroutine(_idleRoutine); //Scenario #2

                _currentTarget = null; 
                _agent.ResetPath();
            }
        }

Tl;dr: StartCoroutine(variable to IEnumerator) is not repeatable, while StartCoroutine(IEnumerator()) works fine, why cant I pass the IEnumerator as a variable?

1
The code shown in the question won't compile. Do you mean _idleRoutine = sitIdle();? - Ruzihm
Is StartCoroutine your method, or something Unity provides? My guess would be that if it's taking an instance of IEnumerator that it's iterating through the IEnumerator by calling MoveNext in a while loop. When you pass StartCoroutine the result of sitIdle you're passing a new instance of IEnumerator. However, if you put the result of sitIdle in a variable then once you call StartCoroutine once, you've already iterated past the end of the IEnumerator. So the next time you pass it to StartCoroutine, MoveNext just returns false. - Joshua Robinson
@JoshuaRobinson Yes, see StartCoroutine - Ruzihm

1 Answers

3
votes

The return value of SitIdle() is an IEnumerator. IEnumerators do not repeat once they are completed and have no iterations remaining, which is what StartCoroutine tells Unity to do. Each time the coroutine is resumed at a yield is another iteration Unity tells the coroutine to perform.

If you really would like to store the coroutine as a variable, so you can choose between one or another, you could store the method you're interested in as a delegate, then call that to get your fresh IEnumerator:

IEnumerator sitIdle()
{
    var timeToWait = GetIdleTime();
    _isIdle = true;
    yield return new WaitForSeconds(timeToWait);
    _isIdle = false;
}  

IEnumerator sitIdleAlternative()
{
    var timeToWait = GetIdleTime() + 2f;
    _isIdle = true;
    yield return new WaitForSeconds(timeToWait);
    _isIdle = false;
}

delegate IEnumerator IdleDelegate ();

IdleDelegate _idleRoutine;

void LateUpdate()
{
    _idleRoutine = new IdleDelegate(sitIdleAlternative); //this is not actually in the late update, just moved here for reference.

    _idleRoutine = new IdleDelegate(sitIdle);

    if (_agent.hasPath)
    {
        if (isTouchingTarget())
        {
            StartCoroutine(sitIdle2()); //Scenario #1

            StartCoroutine(_idleRoutine()); //Scenario #2

            _currentTarget = null; 
            _agent.ResetPath();
        }
    }
}