Using WaitForSeconds versus a while loop with yield return in Coroutines.

Edit: I solved this issue. See the first reply.

I ran into a case which I really don’t understand. I have a workaround for it, but I’d like to understand why what I tried to do didn’t work as expected.

I have a Coroutine that looks as follows:

//This all works fine.
protected override IEnumerable DoTask()
{
            Actor.AnimationManager.ChangeAnimationState(animation);
            var completeTime = Actor.AnimationManager.GetAnimationLength(animation);
            yield return new WaitForSeconds(completeTime);
            Actor.AnimationManager.ChangeAnimationState(AnimationState.IDLE);
            Target.CompleteAction(Actor);
}

This code is part of the job system I have in my game. I’d like to let my character play an animation, and do something after he’s done playing the animation (by calling Target.CompleteAction). This all works fine, but when I change it to the following code, which to me seems equivalent, the animation will play longer than expected (about twice as long!).

//In this situation the animation plays too long.
protected override IEnumerable DoTask()
{
            Actor.AnimationManager.ChangeAnimationState(animation);
            var completeTime = Actor.AnimationManager.GetAnimationLength(animation);
            float time = 0f;
            while (time < completeTime)
            {
                yield return null;
                time += Time.deltaTime;
            }
            Actor.AnimationManager.ChangeAnimationState(AnimationState.IDLE);
            Target.CompleteAction(Actor);
}

I just don’t understand why using WaitForSeconds would cause different behaviour than using a while loop which yields null until the time has passed. For completeness, all AnimationManager.ChangeAnimationState does is call animator.Play(currentState, 0);
GetAnimationLength does this:

    public float GetAnimationLength(string animationState)
    {
        var clips = animator.runtimeAnimatorController.animationClips;
        var foundClip = clips.FirstOrDefault((clip) => clip.name.StartsWith(animationState));
        if(foundClip != null)
        {
            return foundClip.length;
        }
        return -1;
    }

Just for clarity. I’m not looking for a workaround (I already have one). Instead I’d like to understand what’s going on. I’d like to use this while loop constuction in more places in my code, so not understanding its behaviour is not optimal. I hope someone can help!

I found out what caused it. It’s a pretty complicated story. As you can see, DoTask returns an IEnumerable. I had some code that added extra instructions to the IEnumerable before turning it in an Enumerator used for the coroutine. It looked something like this:

            foreach(var instruction in instructions)
            {
                yield return EndIfShouldEnd();
                yield return instruction;
            }

The instruction here would come from DoTask()

I wanted to use EndIfShouldEnd() to abort the routine on specific conditions, but I didn’t realize its yield return added another frame of waiting to the routine, causing the routine to run longer than I expected.

1 Like