- Home /
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?
I'm intrigued by your need to stop and then start the same coroutine based on a value. Can you give a little bit more detail on what you're trying to accomplish and why you need to stop the coroutine just to start it again? I have a solution for your issue but it may not be suitable, depending on what you're trying to do.
As far as I know, if you perform StopCoroutine inside your coroutine itsself, the original coroutine will keep running untill its next yield statement.
This means you should be able to do this without any problems. As long as there's not a yield between the following 2 lines.
StopCoroutine("coroutine");
StartCoroutine("coroutine");
perhaps you're overthinking the problem.
why not just assign a lower relative volume to the music ?
the player can always turn it up...
Answer by Key_Less · Jun 06, 2014 at 04:55 PM
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.
Hi, $$anonymous$$ey_Less! Thank you for your extremely thorough answer. This is exactly the approach I was taking when I wrote my sound player -- it does ducking just like this. But the problem is not necessarily that I have trouble ducking sounds -- it works fine. $$anonymous$$y question is about coroutines, and about stopping and starting them elegantly, or simply. It just seems odd that a person should need an extra coroutine wrapper -- the way your PlayAudioClip and WaitForAudioToFinish functions are seperate. These two lines solve the problem in your code:
StopAllCoroutines();
StartCoroutine(WaitForAudioToFinish(audio.clip.length));
And I use the same ones. But I feel like there must be a better way to do it. Don't you think there should? Or am I acting all crazytown again?
Cheers,
Simon
Answer by aero80 · Jul 03, 2014 at 07:01 AM
You should check out
http://www.youtube.com/watch?v=VnbfEyL85kE Part 1 http://www.youtube.com/watch?v=Ex2td1En94Y 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.
Answer by Andres-Fernandez · Jul 03, 2014 at 07:02 AM
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));
}
Your answer
Follow this Question
Related Questions
OnCollisionEnter as IEnumerator problem (WaitForSecond) 2 Answers
Coroutine in C# and Question 2 Answers
Powerup issue - Speed 1 Answer
Coroutine error with result 1 Answer
Destroy Without A Collision Or Trigger 2 Answers