Coroutine design problem?

So, let’s say I want to start a coroutine that tweens a value from 0 to 1.

I start my coroutine. It is instance A.
When the coroutine is half finished, and the value is tweened to .5, I want to start the coroutine again by calling it, and thus create instance B of he coroutine. BUT I don’t want them to run at the same time – I only want B to run now. I want to start afresh with B, and for A to have been stopped by StopCoroutine.

How do you do this elegantly, without calling a wrapper function that is void? I can’t call StopCoroutine for a function WITHIN that function, and then move forward and run the new instance, correct?

EDIT: As a practical example, let’s assume I have a class that manages the volume of an Audio Source – the background music for a game. When the player gets a coin, I want to tween the volume of the music from .8 to .3, and then back up again, so the coin sound gets emphasis. The whole process of lowering and raising the music’s volume takes, oh, say, 3 seconds. But let’s say that the user gets two coins in 1.5 seconds. How does the music-dampening coroutine work that handles that? Currently, my approach is to have two functions, one of which is a public void function that always stops the private ienumerator function that does the tweening, and starts it again.

How do you handle this problem?

What you’re trying to achieve is sound ducking. I would recommend creating a script that holds a list of audio source references that need to “duck” when the audio source is playing.

So for example, you could attach the script to the game object that contains the coin audio source, and then add the audio source that contains the background music as a reference. This way, any time you play the coin sound clip, you could lower the volume for any audio clip references that need to duck their volume.

I’ll try to demonstrate what I mean with a little bit of code:

public List<AudioSource> SoundsToDuck;

// Custom Play function
public void PlayAudioClip()
{
   DuckAudioClips(true);
   audio.Play();
   StopAllCoroutines();
   StartCoroutine(WaitForAudioToFinish(audio.clip.length));
}

/// <summary>
/// This will loop through any audio source reference attached in the inspector
/// and set their volumes depending on whether they are being ducked or unducked.
/// </summary>
private void DuckAudioClips(bool duck)
{
   foreach (var clip in SoundsToDuck)
   {
      // If we want to duck the sound
      if(duck)
      {
         clip.volume = 0.3f;
      }
      else
      {
         // Unduck the sound. 
         clip.volume = 0.8f;
      }
   }
}

/// <summary>
/// Use this to unduck the audio references once the audio 
/// source this script is attached to has finished playing.
/// </summary>
private IEnumerator WaitForAudioToFinish(float delay)
{
   // Yield for the amount of time it takes this audio clip to finish.
   yield return new WaitForSeconds(delay);

   // Unduck the sound clips.
   DuckAudioClips(false);
}

I warn you, this is all untested. This approach could handle your issue with collecting several coins quickly, since the reference sounds will remain ducked as long as the coin audio clip is playing. However, this only sets the volume of the background music and does not fade it as you are intending to do, but hopefully this can still help and give you some more ideas.

You should check out

Unity Coding Tips: Coroutine Coordinator Part 1 - YouTube Part 1
Unity Coding Tips: Coroutine Coordinator Part 2 - YouTube Part 2

from Prime31. Basically he is writing a wrapper around coroutines which then you can pause, cancel…etc coroutines anytime you want. I think that will solve your problem.

Haven’t tried, but I believe you can wait for another coroutine (from Unity Gems and from unity patterns). Call the same routine with one parameter to not call itself again if it’s already nested. No wrapper needed.

IEnumerator sameCoroutine(bool callSelf) {
   if (callself)
   yield return StartCoroutine(sameCoroutine(false));
}