in C#, Tutorial, Unity

Implementing a Loading Bar in Unity

This tutorial will explain how to create a loading bar in Unity. This is the second part of a longer series on scene management.

Introduction

Despite Unity’s best effort, implementing a loading bar is all but easy. The reason behind this is that the set of APIs provided heavily relies on assumptions that, at best, are a consequence of poor design choices.

Before digging into them, we need to understand what loading a level actually means for Unity. If you are used to SceneManager.LoadScene (or the now deprecated Application.LoadLevel) you might be unfamiliar with the sequence of operations that Unity performs:

  • Loading. Like the name suggests, in this phase Unity loads from the memory the assets that make the new scene. If your level is rather big or uses large assets, this is the phase that will take most of the time. Unity relies on a background process to load assets minimising the performance drop on the scene that is currently being played.
  • Activation. Once all that’s needed for a scene is loaded, Unity has to activate it. This process deletes all the assets that are currently in the game, and replaces them with the ones that have been previously loaded. This operation will always introduce a little bit of a lag, and so far there is very little you can do to prevent that.

The idea behind this tutorial is to delegate the loading of the new scene to Unity’s dedicated background process. We can now periodically query the state of the loading and display it on the screen. When all the assets are loaded, we can finally trigger the activation of the scene.

The API

The function provided by Unity to load a scene in the background is SceneManager.LoadSceneAsync, which returns an instance of AsyncOperation. Asynchronous operations (documentation) are objects that can be used to query the current state of a background process. It provides three essential components:

  • allowSceneActivation: when set to false, the scene is not activated when it is fully loaded, giving more control over the entire process;
  • isDone: a boolean value that becomes true when the process is completed;
  • progress: a number from 0 to 1 that indicates the current state of the process.

Except that …is not that simple! Both isDone and progress suffer from heavy assumptions. Firstly, isDone becomes true when the scene is both both loaded and activated. Secondly, the range from 0 to 0.9 of progress is used for the loading part only, while 1 is reserved for the completion of the activation progress. Values from 0.9 to 1 (extremes excluded) are never used.

The Code

With all the knowledge gathered so far, we can now write a proper asynchronous function to monitor the loading of a scene. Since we are required to update the loading bar in parallel with the loading process, we need to wrap out code into a coroutine. This is simply a function that returns IEnumerator, and that signals when it can be stopped and resumed using the keyword yield. Coroutines will be explored in a further tutorial.

After invoking SceneManager.LoadSceneAsync, we force allowSceneActivation to false for a better control over the activation process. We then loop over isDone, starting the activation process when the loading is completed (progress == 0.9f):

IEnumerator AsynchronousLoad (string scene)
{
	yield return null;

	AsyncOperation ao = SceneManager.LoadSceneAsync(scene);
	ao.allowSceneActivation = false;

	while (! ao.isDone)
	{
		// [0, 0.9] > [0, 1]
		float progress = Mathf.Clamp01(ao.progress / 0.9f);
		Debug.log("Loading progress: " + (progress * 100) + "%");

		// Loading completed
		if (ao.progress == 0.9f)
		{
			Debug.Log("Press a key to start");
			if (Input.AnyKey())
				ao.allowSceneActivation = true;
		}

		yield return null;
	}
}

This code prints the current progress on the console, but it can be easily changed to update any UI you have designed.

Note: If you are to compare two floating values, it’s better to rely on Mathf.Approximately (documentation), rather than ==. This is because floating point arithmetic is prone to approximation errors. In this very specific case, however, progress is set to exactly 0.9f when the scene is loaded.

The issues

The technique explained in this tutorial works very well if you have to load a single scene at a time. There are other hidden assumptions in the Unity APIs which can cause unwanted behaviours.

  • Single loading. You can only load a single scene at a time.
  • Activation in order. Scenes must be activated in the same order they have been loaded.
  • Dangling scenes. If you lose your reference to AsyncOperation before activating a scene, the entire loading queue will be blocked until you restart Unity.

Conclusion

Become a Patron!

This post explains how to create a loading bar with Unity.

The next part of this tutorial will explain how to load scene incrementally. Such a technique is essential if your world is very large and you want to create a seamless experience, free of loading bars.

Other resources

💖 Support this blog

This website exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.

Patreon Patreon_button
Twitter_logo

YouTube_logo
📧 Stay updated

You will be notified when a new tutorial is released!

📝 Licensing

You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.

You are not allowed to redistribute the content of this tutorial on other platforms, especially the parts that are only available on Patreon.

If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻

Write a Comment

Comment

39 Comments

  1. Thanks for another great tutorial. Just a comment, I don’t think it’s a good idea to trust in that “ao.progress == 0.9f”, as float comparisons are not reliable. Maybe you could replace it with a more robust check like Mathf.Approximately(ao.progress, 0.9f)?

    • Hey! Usually yes, since floating point arithmetic is the root of all evil. :p
      If I’m not wrong, however, Unity should return EXACTLY 0.9f when the loading phase is completed. So in this case it should work fine. I think the issue here is that if it’s 0.8999999999999f, it will be assumed to be 0.9f, but in actuality the scene is not loaded yet.

        • Hey!

          I am indeed! I had so many requests about this. The problem is… the last part is quite beefy, and I want to make sure is good quality. Making long series takes a lot of time… sorry for the wait! 😀

          • No worries at all – appreciate the blogs! Quality over substance all the time!

          • Nice to hear, that’s also something I am looking for!

            Had already implemented async loading with this API in the past, but found that commiting the load still made the game freeze for some time… which is not exactly seamless. (Maybe the scene size was too large? dunno..)

            Hoping that you might have covered this and maybe other things I haven’t encountered.

          • I’m also waiting for the last part! Thanks for the tutorials 😀

  2. Hello! First of all thank you, this worked very well. I have a question,why there are two “yeld return null” one in the first line and the other in the while. Sorry about my english…
    Thanks!

  3. This is one of the clearest tutorials on this confusing topic that I’ve found so far. This website looks fantastic and I’m looking forward to exploring. But seriously, did it not occur to you that it might be a good idea to explain, for the sake of less experienced Unity developers, how the code listed above fits in to the project? Does it need to be invoked by ‘StartCoroutine’?

  4. It seems Unity 5.3 still does not support load multiple scene async at he same time. You have to activate the first scene before second scene start async loading.
    Any ideal how to do it for this situation? @Alan Zucconi

  5. Great tutorial except for the reason of Unity is not working. After that, returns a ‘scene (not loaded)’ but no errors.
    I am using the Unity 5.3.4f.

    Do you know something about this issue?

    Best regards!

    • Hey! Unity has updated and changed how it handles levels repeatedly through its past versions. This tutorial was designed before 5.5. I might have to go back and see if Unity broke it. :p

  6. This is one of the best tutorials I’ve ever read in my life. Thank you for explaining it so clearly and giving extremely useful snippets!

    One additional thought regarding the “Activation” process’ unavoidable lag, a little misdirection goes a long way. It’s a great time to throw up some real short info (about 1 second to read) and the lag becomes almost invisible.

    Thanks again for the great info!

  7. What happened to part 3?! Parts 1 and 2 covered what to do and how we need workarounds, but we’re left hanging. Obviously I’m looking at this issue myself and am struggling to find how to asynchronously load additive content. Much disappointment that this great tute isn’t concluded. Whatever work you’ve already got, please post!

  8. Another plea-bump for part 3 🙂

    Also glad I came across your site in my search. Seems to be some great resources here and you really present info in a clear detailed manner. Thank you for choosing to share your wealth of knowledge.

  9. First of all excellent tutorial! Thanks! Do you think you could make a tutorial of how to use the loading bar for an AssetBundle scene hosted on a server?

  10. Hey Alan,
    Great article, easy to read and very helpful. Everything works like a charm but I am having a small problem. Since it looks like you know a “thing or two” about it, after hours of fruitless researching I thought I will contact you. Basically, I am using a UI button and ‘allowSceneActivation’ to stop the new level from loading before the player is ready. Everything is working fine but sometimes button seems to not respond to the click. What happens is every now and then when I press the button it will go darker as they do, but no function assigned to the button will be called. Sometimes I need to press it 2 to 5 times. The button is exactly the same as another 20 buttons in the game that always respond 100% of a time. I have played with EventSystem script and the ‘Drag Threshold’ but that is not changing anything either. It seems to happen even more often when I minimize the app and bring it to focus again. It is Android build. If you have seen something like this before any info would be appreciated as this is driving me crazy. I thought it might be a coroutine or aSyncLoad itself that is messing with the input. I don’t know, I am rather new to the Unity. Any help will be greatly appreciated. Thanks for your time !

  11. Are you still doing tutorials? I like the way you explain Unity and C#.
    There are no really good tutorials that skim the basics of the important things to learn in Unity when creating a game. For instance, loading asset bundles (how to arrange assets for the build, what to package for publishing online, etc), setting up GooglePlay services, game-framework examples, etc. There’s lots of good tutorials, but you have to get bits and pieces from each, they are either needlessly over-complicated or way to basic and/or inefficient.

  12. Hi Alan Zucconi,
    Thank you for your tutorial on scene loading with a progress bar.
    It works just fine.
    I have a problem and I hope that someone can help me.
    I started working, in Unity, with Asset Bundles, and I need a progress bar, to see that the asset is being downloaded (i.e.):

    IEnumerator InitializeLevelAsync (string levelName, bool isAdditive)

    {
    // Load level from assetBundle.
    AssetBundleLoadOperation request = AssetBundleManager.LoadLevelAsync(sceneAssetBundle, levelName, isAdditive);
    if (request == null)
    yield break;
    yield return StartCoroutine(request);
    }

    Can you help?
    Thanks

Webmentions

  • Scene Management in Unity 5 - Alan Zucconi March 25, 2019

    […] Part 2. Implementing a Loading Bar in Unity 5 […]