- Home /
IEnumerator hell.
I really don´t know how to solve this.
The problem: I have a "Fader" class. This class handles the screen fading out. In 2-3 or more different cases i need to wait for the fader class to finish up before i can do the next stuff.
All the fader class methods are IEnumerators.
So from my main class i can do this:
private IEnumerator FadeOut()
{
faderCanvas.gameObject.SetActive(true);
Fader fader = faderCanvas.GetComponent<Fader>();
yield return fader.StartCoroutine(fader.Fade(1, 0));
}
But in order to use that, the method calling this has to an IEnumerator:
public IEnumerator EndDay()
{
yield return StartCoroutine(FadeOut());
//DoNextStuff
}
Because the "DoNextStuff should not run until the screen is completely black. So i am ending up in every method that needs this Fader class to work to finish must be an IEnumerator.
Isn´t there a better way to solve this?
For your screen to fade in and out, the best solution is to animate a black image that covers your entire screen. You just need two points in each animation clip, one for fully alpha and one for no alpha (making the screen look like it fades in and out). Coroutines are really not the best way to do this. Brackeys has some good tutorials on fade-outs and screen transitions. I suggest you check it out! @jfoc2002
The fader class solves my needs. The "Animation" way is not the way for me to go on this. Also this doesn´t answer my question.
Answer by FlaSh-G · Aug 12, 2020 at 09:51 AM
A coroutine (the IEnumerator method, when used in a StartCoroutine context) allows you to wait for things. A regular method does not allow you to do that, it will be executed in one go and then it's done, not giving Unity the time to redraw the game one or more times. If you need a method to be able to wait until a coroutine is finished, it also needs to be a coroutine.
However, you have two alternatives to coroutines:
async/await - You can do some research on that in the Unity context. I'm personally not too fond of this, because it seems to require a big fat extra framework to work properly, as the default stuff is not documented and thus might have a chance to work 90% of the time and then suddenly stop working - that's due to the "hidden/undefined behavior" nature of async/await.
Callbacks. You can make your coroutine accept a delegate (like
System.Action
) that the method invokes after finishing the animation. Would look like this:
.
private IEnumerator DoSomethingAfterSeconds(float seconds, Action onFinish)
{
yield return new WaitForSeconds(seconds);
onFinish();
}
You can then call it like, for example, this:
DoSomethingAfterSeconds(5, () => Debug.Log("I waited for 5 seconds!");
To be honest, I don't see the problem with making methods who wait for one another coroutines. And maybe, it's possible to revisit your idea and maybe, you'll notice that you can implement your system(s) without methods waiting for methods waiting for methods to finish.
Thank you so much! I didn´t know about the amazing callbacks :-) Works like a charm. I don´t even have to use an IEnumerator to call Fader class!
And with the callback it gives my Fader class some really good flexibillity! That was i was after. Thank you!
Answer by Bunny83 · Aug 12, 2020 at 10:01 AM
Yes, there are workarounds. For example I created the CoroutineHelpers a long time ago. It combines several utilities. First of all it provides a singleton where all coroutines are running on so you don't have any issues when switching between scenes and you can start a coroutine from everywhere. Next it provides a wrapper class (Run class) that wraps around the actual coroutine. This allows you to wait for the same coroutine in different places. To do that the Run class provides a boolean indicating that the coroutine has finished. It also provides the "WaitFor" property to create a new coroutine which can be used inside another coroutine to yield on. Back in the day we did not have the CustomYieldInstruction and this was the only way around this. Maybe I'll update the utility one day -.-
Anyways, in order to have the other class wait for your fader, you would need to give that class / coroutine access to the Run instance you created for the fader.
Depending on your requirements you can also register a delegate that should run when a coroutine has finished by simply using "ExecuteWhenDone" on the Run instance. To start a coroutine with this utility you can simply do
Run runInstance = Run.Coroutine(YourCoroutine());
This returns a Run instance which you can pass around to other scripts where they can use
yield return runInstance.WaitFor;
or what would be better now
yield return new new WaitUntil(()=>runInstance.isDone);
Note that Unity's StartCoroutine method returns a Coroutine instance which generally can be used in another coroutine to wait for this coroutine. However, at least back in the day, it was not possible to have two or more coroutines waiting for the same coroutine instance. That was one of the main reasons I created those utilities. Apart from that it also provides several helpers to run simple delegates. This is useful when you just want to run some code after a certain time or doing something like your fader (with Run.Lerp).
So it's a bit out-of-date but does still work.
Woaw...really nice. But this is waaaay over my knowledge. But i think this will come handy in the future. Thank you for you answer. I descided to go with callbacks. Works really good for my current needs.