Nested Coroutines: last coroutine quits early

Greetings,

I am trying to nest Coroutines. It seems to work OK, except that the last nested coroutine exits early.

In a nutshell, here’s what I’m trying to do (in pseudocode)

// start coroutine "add to scene"

   // start coroutine "fade to black", yield

   // does some simple logic

   // start coroutine "fade in from black", yield

As you can see, I want my “add to scene” Coroutine to be “wrapped” in the complete execution of two other Coroutines, fading a Unity.UI Panel in and out (using Color.Lerp and adjusting the alpha channel of the start color to determine the end color).

The first Coroutine “fade to black” works totally fine; it fades out as expected. Then, the simple logic is performed, as expected.

However, the final Coroutine “fade in from black” seems to yield only one time, even though I am using the same type of yield return statement on it.

What am I doing wrong here?

Here’s some mildly redacted example code:

  private IEnumerator TransitionToScene(string sceneName) {
    Debug.Log("Transitioning. Starting fade out...");

    //Start a fade-out
    yield return StartCoroutine(Fade(FadeStyle.FadeOut));

    // OTHER STUFF IN HERE

    Debug.Log("Loaded! Fading in, now...");

    //Start a fade-in
    yield return StartCoroutine(Fade(FadeStyle.FadeIn));

    Debug.Log("Fade in complete");
  }

Here’s the actual “Fade” Coroutine, also:

  //Create a Coroutine which will fade in or out
  private IEnumerator Fade( FadeStyle style, float fadeTime = .25f ) {

    //Early exit for invalid fade style
    if(style == FadeStyle.Invalid) { Debug.Log("INVALID FADE: Early-exit."); yield break; }

    Debug.Log("Fade coroutine started...");

    //Keep track of our fade progress out here for the coroutine...
    float fadeProgress = 0;

    //Grab the start color
    Color start = fadeImage.color;
    //Create a target color based on the start color...
    Color end = start;
    //If we're fading in, the target alpha is 0. If we're fading out, it's 1.
    end.a = style == FadeStyle.FadeIn ? 0 : 1;

    Debug.Log("Fading from " + start + " to " + end);

    //While we have not faded, yet...
    while(fadeProgress < fadeTime) {

      string inOrOut = style == FadeStyle.FadeIn ? "In" : "Out";
      Debug.Log("Fade " + inOrOut + ": " + fadeProgress  + " / " + fadeTime);

      //Calculate how far along we should be in the color transitions...
      float lerpVal = fadeProgress / fadeTime;
      Color newColor = Color.Lerp(start, end, lerpVal);

      //Apply it
      fadeImage.color = newColor;

      //Increment time
      fadeProgress += Time.deltaTime;

      //Continue next frame
      yield return null;
    }

    Debug.Log("Fade coroutine complete... (" + fadeProgress + " / " + fadeTime + ")");
  }

And, finally, some debug output:

Fade coroutine started...
UnityEngine.Debug:Log(Object)
<Fade>c__Iterator0:MoveNext() (at Assets/SceneManager.cs:75)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
<TransitionToScene>c__Iterator1:MoveNext() (at Assets/SceneManager.cs:160)


Fading from RGBA(0.000, 0.000, 0.000, 0.998) to RGBA(0.000, 0.000, 0.000, 0.000)
UnityEngine.Debug:Log(Object)
<Fade>c__Iterator0:MoveNext() (at Assets/SceneManager.cs:87)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
<TransitionToScene>c__Iterator1:MoveNext() (at Assets/SceneManager.cs:160)


Fade In: 0 / 0.25
UnityEngine.Debug:Log(Object)
<Fade>c__Iterator0:MoveNext() (at Assets/SceneManager.cs:93)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
<TransitionToScene>c__Iterator1:MoveNext() (at Assets/SceneManager.cs:160)


Fade In: 0.001157403 / 0.25
UnityEngine.Debug:Log(Object)
<Fade>c__Iterator0:MoveNext() (at Assets/SceneManager.cs:93)


Fade In: 0.008806617 / 0.25
UnityEngine.Debug:Log(Object)
<Fade>c__Iterator0:MoveNext() (at Assets/SceneManager.cs:93)


Fade coroutine complete... (0.2785531 / 0.25)
UnityEngine.Debug:Log(Object)
<Fade>c__Iterator0:MoveNext() (at Assets/SceneManager.cs:109)

The culprit is how “progress” is measured within the Coroutine itself.

Take this code:

       //Increment time
       fadeProgress += Time.deltaTime;

Because Time.deltaTime can be very large, it is possible for the while-loop to quit execution very early. This can be handled with special cases or you can hack it and use something like yield return new WaitForSeconds();

Another thing I’ve thought about doing (but is not especially safe) is to do:

if(Time.deltaTime < skipThreshold)

around the processing of the Coroutine to avoid incrementing the timer more than we want to.