Creating a smooth user experience for Unity scene changes
In this article, we’re going to talk about a common feature in video games, and how we can implement this feature in our own projects. What I am referring to is the loading screen.
You might be wondering if such a thing is really necessary. Technically it isn’t, but it is very nice feature to have for the users.
Switching to a new scene in Unity requires everything in the scene to be loaded, which can take some time depending on how much content the scene has. This can be disconcerting for the users to have to sit and wait for loading to finish without any feedback.
In this article, we’re going to go through the whole process of transitioning between scenes by way of an intermediary loading screen, which will show us our progress to the next scene. Let’s get started.
The first thing we’ll need is two existing scenes to transition between. It would be helpful if one of these scenes had a lot of content to load, so that we can really see our progress bar at work. I will be using assets from The Great Fleece package, which is available at no cost from the Unity Asset Store.
Once those are in place, we’ll then want to create a new scene for the loading screen itself. Create the new scene and switch to it in the Unity Editor.
Creating our background
Once we have our scene created, we’ll next want to give our players something to look at. In the scene, add a new UI Image object.
We’ll next want to modify this image in a few ways. For starters, we’ll want it to stretch to cover the whole screen. To do this, we need to adjust the values of the Anchors fields to be zeros in the Min fields and ones in the Max fields.
Alternatively, we can click the square in the upper left corner of the Transform section to get the Anchor Presets menu. In that menu, select the stretch in all directions option. This accomplishes the same thing as changing the Anchors manually.
Once the anchors are configured, we then need to change the transform values of Left, Top, Right, and Bottom all to zero. This means that our image should start at pixel zero on all sides, or in other words it should stretch to the edges of the screen.
At this point, we could add a sprite into the Source Image field to use that as the background image. If we don’t have an image, we could just set the color to whatever we want.
Making the progress bar
Next we’ll create the progress bar. The idea here is to have a graphic on screen that will fill up over time to represent how much of the next scene has been loaded.
To do this, first add another UI Image to the scene. We can shape this image however we wish. We could even stretch it to the edges of the screen like the background image if we wanted to. One thing we do require, however, is to actually have a Source Image for this image. This allows us to unlock all of the image filling features that will allow us to have the “progress” effect.
We get access to the filling features by changing the Image Type field to “Filled”. We then can see several fields all having to do with how the image fills. Play around with those fields for a little bit to see everything that is possible and find what works best for your project. It’s quite a powerful feature.
Loading the loading screen scene
With the background and progress bar set, we’re now ready to do the scripting to switch us between scenes. Actually, make that almost ready.
One thing we must do in order to switch between scenes is to go into the Build Settings window and add all relevant scenes to the build. So that would be the scene we’re coming from, the scene we’re going to, and the loading screen scene itself.
With that taken care of, let’s quickly talk about the most basic method of switching scenes. I’m guessing since you’re reading this article on making a loading screen, you might already know how to do this, but I don’t want to make any assumptions.
The most basic way to switch scenes is to use the static LoadScene method of the SceneManager class in the Unity.SceneManagement library. That method requires either the name or index number of the scene to load as a parameter.
So, to load the LoadScreen scene, we need to have in our scripts either:
Because, as we can see in the Build Settings window image above, the index of the LoadScreen scene in my example is 2.
Now, where do we put this line of code? That’s up to you to determine. We may wish to load a new scene in any number of contexts, from clicking a button in a menu to performing an action in-game.
Wait, why are we learning this?
We’re building a load screen to get away from this direct method, aren’t we? Yes, that’s true. However, we can’t have a loading screen to another loading screen (well, I guess we could, but that would be ridiculous).
We still need this direct method to get us to our loading screen scene. This shouldn’t be an issue, because our loading screen should be an asset-light scene that loads fairly quickly.
Loading the next scene
Ok, so now we have our loading screen, and we can get to our loading screen from some other scene. Now it’s time to put all the pieces together and load in that asset-heavy scene that takes some real time to load.
We’ll need a new script to handle loading the next scene and updating the progress bar. I have named my example script LoadLevel. We need this script attached to something in the scene, so go ahead and attach it to the UI Canvas object.
In order to update the progress bar, our new script will need access to it, so create a serialized variable in the script of type UnityEngine.UI.Image. In the Unity Editor, drag the progress bar Image object to that field in the script.
In the script, we need to be periodically evaluating and updating our progress bar. This would seem to call for a coroutine method, so let’s go ahead and make one. If you aren’t familiar, a Unity coroutine is a method of type IEnumerator.
Within our new coroutine, we need to do two main things. First, we need to be loading our next scene. Second, we need to be continually evaluating the progress of that first task, and updating our progress bar to match it.
The first goal can be accomplished by returning to the SceneManager class we used earlier, only this time we’ll be using the asynchronous LoadSceneAsync method.
What is asynchronous? To put it in very simple terms, using an asynchronous method means the overall program does not wait until the method has completed to move on. The code continues to run in the background until it has completed.
So we will use the LoadSceneAsync method to load the next scene in the background while we continue to run other code in this scene. LoadSceneAsync returns a value of AsyncOperation type. We’ll want to capture that in a variable for later use.
That covers loading the next scene, so now we want to focus on updating our progress bar. We want to continually be updating it, so after we kick off the asynchronous loading of the next scene, we want to create a loop that checks if that operation has finished. If it has not, then we change our progress bar’s fill amount to match its level of progress.
To accomplish this, we will use two properties from the AsyncOperation variable we created. The first is isDone, which is a boolean value that tells us whether the operation has completed or not. The second is progress, which is a float value between zero and one which tells us how complete the operation is.
The progress bar’s fillAmount property is also a float between zero and one, so it matches perfectly with the progress property.
So that we aren’t constantly sending requests for updates, we can finish our coroutine with a line of code that will give our coroutine some rest periods. To do that, we will use the method WaitForEndOfFrame to tell the coroutine to not run again until the next frame.
With our coroutine written, we need a line of code telling Unity to start the coroutine. We can go ahead and put that in the Start method.
All completed, our script should look something like this:
That’s all there is to it. I suppose a loading screen doesn’t seem like it should be that difficult to implement, so it’s good that it is actually not difficult. Nevertheless, the value of that feedback to the player can not be overstated.
Next, we will return to the topic of Cinemachine in Unity, and over the next several articles we will be getting much more in-depth than we ever have before. Until then, thanks for reading.