Mathf.Clamp01 CoRoutine

I’m trying to fade individual pieces of the GUI in C#. Just simple phrases that pop up on the screen and then fade out over time, like small tutorials to my games controls. For this, I took code from a ScreenFade script. My (shortened) code is as follows:

	void Awake () {
		StartCoroutine(TutorialAddPhrase(3));
		c = Color.white;
		cDelta = Color.white;
		alpha = 1.0f; 
		fadeSpeed = 1.0f;
		fadeDir = -1;
	}

    void OnGUI () {

            print (cDelta.a);
    		GUI.color = cDelta;
    		cDelta.a = alpha;
    		GUI.Label (new Rect (200, 200, 200, 100), "TESTING");
    }

    void TutorialAddPhrase (int wait) {
    		
    		yield return new WaitForSeconds(wait);
    		alpha += fadeDir * fadeSpeed * Time.deltaTime; 
    		alpha = Mathf.Clamp01(alpha);    		
    }

This renders the following error:
Assets/Scripts/GUIscript.cs(197,14): error CS1624: The body of GUIscript.TutorialAddPhrase(int)' cannot be an iterator block because void’ is not an iterator interface type

If I change that last function from “void” to “IEnumerator”, it doesn’t give an error, but it doesn’t really properly fade out the alpha. The alpha stops at 0.9863821 (for instance, it differs).

What am I doing wrong here? I hope you can point me in the right direction. I’ve only just begun working in C#, so it confuses me at times.

1 Answer

1

The problem you are having here, is that StartCoroutine only executes once! That means, that after the WaitForSeconds happens, the bits afterwards don’t repeat. Things using Time.deltaTime need to go into the Update or FixedUpdate loops, otherwise they aren’t much use to you.

What you probably intended to do, was wait 3 seconds, and then fade the alpha. In order to do that, you need to set up a boolean

bool fading = false;

then in your coroutine, set it to true.

IEnumerator TutorialAddPhrase (int wait) {

       yield return new WaitForSeconds(wait);
       fading = true;
}

then to do the actual fading, have something in Update:

void Update()
{
    if(fading)
    {
        alpha = Mathf.Clamp01(alpha + (fadeDir * fadeSpeed * Time.deltaTime));
    }
}

Another problem is that in your GUI function, you are treating GUI.Color as a reference type, when it is in fact a value type. This means you can’t assign GUI.color to some color, and then expect it to change if you change the original- it only assigns what that color is at the exact moment you use it.

Instead, do this-

Color tempColor = new Color(cDelta.r, cDelta.g, cDelta.b, alpha);
GUI.color = tempColor;

It works, thank you very much! And I think I understand what you're saying about the reference/value type, as well. Makes sense. One more thing you might want to adjust to your code: the if statement just says "(fading)", instead of "(fading == true)". Thanks again! :D

yeah, if(fading) is EXACTLY THE SAME as if(fading == true) just so you know. If statements always use bool values, and anything else you put in there has to be able to convert into a boolean before the if can handle them- however, actual booleans can just be passed straight in!

I would strongly recommend not using Update, and instead just make the fading happen in the coroutine. That avoids all those awkward "if (fading)" constructions.

@ Syclamoth: I stand corrected. Did not know that! @ Eric5h5: Well, that's what I was trying to do in the first place, but I didn't have much luck. Why would you recommend not using Update? Too heavy on performance?

@Veliremus: As I said, it's awkward to use Update for things like this, and it will get very messy when code gets more complex. Update is really only for things that happen every single frame indefinitely; instead of making conditionals to check for things that happen sometimes but not others, it's simpler and generally better for performance to make a coroutine that just runs when it needs to and stops when it's done.