A common feature of games is that the screen briefly fades to black during scene changes. This is done to ‘soften’ the transition as a sudden switch from one scene to another can be a bit jarring if it’s visible when it happens.
So I’ve found it handy to have a screen fader that I can easily drop in to a new project any time I want to have fade to black (or some other colour) scene transitions and I decided that it would be a nice subject for a mini tutorial. It will also serve to demonstrate a couple of seemingly often overlooked but very useful features, i.e. callbacks and CanvasGroups.
The aim of this tutorial will be to provide a screen fader that is fairly simple to implement and is also flexible and easy to use.
The way this screen fader will work is by overlaying a UI image on the screen and changing it’s alpha value to fade it in and out.
To make it as easy as possible to add to a project, we’ll have the fader create its own UI elements in code, which means all we have to do is drop the screen fader script onto an empty game objet to make it available for us to use. To make this work we’ll need the screen fader to create its own Canvas, CanvasGroup component and an Image.
Step 1)
This is the code to create the Canvas at runtime:-
What this does is create a new GameObject with a Canvas component attached. It then parents it to the GameObject that script is on, then it sets the RenderMode to ScreenSpace overlay and sets the sort order to the value specified in the inspector (default 99). I have made the sort order easy to change so that you can make sure the fader image is always on top of everything else. However depending on what you wanted you could also use the sort order to have it appear behind some elements.)
Step 2)
This is the code to create the CanvasGroup at runtime:-
We use AddComponent() to add the CanvasGroup component to the canvas we just created, and then store, in a member variable, the reference to the component that it returns for later use. Then we set interactable and blocksraycasts to false, which means that any child objects of the canvas group won’t block mouse clicks. This isn’t strictly necessary for our purposes here, and indeed in some instances you may want your screen fader to block input, in which case you would set interactable and blocksraycasts to true.
Lastly we disable the GameObject as we don’t need to have it enabled until we are ready to use it.
Step 3)
This is the code to create the Image at runtime:-
Firstly we create a new GameObject with an Image Component attached, then we set it as a child of the canvas we created in step 1. Next we set its colour to the value specified in the inspector (default black), and lastly scale it up a bit, this last bit is done to prevent a slight border appearing around the edges in some screen resolutions.
Step 4)
Set the image position and size and make it scale with the screen size:-
The easiest way to change position and size of ScreenSpace UI elements is to use the RectTransform component, so firstly we get a reference to the Image RectTransform using GetComponent().
Next we set the anchors to the bottom left and top right to make the image stretch with the screen size. The anchors are Vector2s with values between 0,0 and 1,1, where 0,0 is the bottom left of the screen and 1,1 is the top right of the screen.
Next we centre the image by setting its anchoredPosition to 0,0;
Lastly we set the width and height to match the current screen size (due to the scaling we did in step 3, it will be slightly larger than the screen).
Now we need to put all of that above into the Awake function of the screen saver. This is the complete Awake() function.
The only part of the Awake() function I haven’t discussed yet is the very first block of code. Basically what this does is make the screen fader not get destroyed during scene changes, and also ensures that the only instance of it that can exist is the first one that is created. It also grabs a static reference to the first instance so that it is possible to access non static methods and properties.
That’s the basic setup finished, so next we’ll define a couple of static methods that will enable us to perform fade outs and fade ins. The methods will invoke callbacks when finished so that we can delay execution of other code whilst the fader is in operation. We will also be able to specify a delay before and/or after the screen fade.
This is the public static method to do a fade out:-
As you can see it is quite simple, all it does is check to see if the callback argument is null, and if so it points it to the default empty callback function and then it sets the canvas active.
Next it stops any current fade in/out operations (I’ll discuss that function further on) and runs the fade out coroutine, which is where most of the work is done.
It uses the variant of StartCoroutine that returns a CoRoutine object, which we store for use by the StopActiveFades() function.
This is the code for the fade out coroutine:-
There again, this is quite straightforward, first of all we yield wait for the delayBefore period.
Then we loop while the group.alpha property is less then 1. The alpha property controls the transparency of all children of the group (in this instance our image we created in step 3), a value of zero means fully transparent and a value of one means fully opaque.
In the while loop we use MathF.MoveTowards to linearly fade from transparent to opaque, we can use the speed property in the inspector to control how quickly this happens.
After the while loop exits we yield wait for the delayAfter period if any, and then we invoke the callback to signal that the fade is done.
The fade in functions are almost identical, except in the while loop we change the group.alpha from one to zero and when it’s finished it disables the canvas.
Now let’s take a look at the StopActiveFades() function:-
This function just ensures that there can’t be two fade operations running at the same time, as that could result in unwanted behaviour.
What it does is check the fadeInCR and fadeOutCR variables and if they aren’t null it stops the coroutine they point to and sets the variable to null.
Last of all is the default empty callback function, which doesn’t need any explanation.
In the next part of this tutorial I will show how we can use the screen fader in our applications, but in the meantime here is the full screen fader script:-
As always any and all comments are welcome, see you next time.