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

Comments

40 responses to “Implementing a Loading Bar in Unity”

  1. Thank you very much ! Awesome post ! <3

    1. You’re welcome!

  2. carlosfarinhas avatar
    carlosfarinhas

    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

  3. 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.

    1. Hi!
      Yes, I am indeed still writing tutorials!
      Haven’t had the chance to post much this month, but I am still working on new content! 🙂

  4. 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 !

    1. Hey! This seems such a weird behaviour!
      I’ve never experienced something like that before, so is very hard to know what’s wrong without seeing the entire project! 🙁

  5. Joao Silva avatar
    Joao Silva

    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?

    1. Thank you!
      At the moment I have no planned tutorial on the topic. But if other devs are interested, that might change! 🙂

  6. Thanks. I will be waiting the article of your experience with seamless world loading.

  7. Scrimshaw avatar
    Scrimshaw

    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.

  8. 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!

  9. Hay Alan. Thank you for this awesome quality tutorial series. When are you posting “Seamless Scene Loading in Unity” tutorial? Really waiting on that one. 🙂

    1. Hey!
      Unfortunately I don’t have a plan for that yet. It will be few months though! 🙁

  10. Hey Alan,
    Great Tutorials. Really helped me out. Are you still planning to do the final seamless tutorial? Thanks.

  11. Lorenzo Campanile avatar
    Lorenzo Campanile

    Thanks to this I’ve created my loading screen, thank you! I published my game on the play store (https://play.google.com/store/apps/details?id=com.NeoSanto.Blaster)

  12. 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!

    1. Ohhh thank you so much! <3

  13. Sorry to bring you this, but on version 5.5, this script caused an infinite loop on Unity. The “progress” is stuck on 0.9 and “isDone” is never true.

    1. 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

  14. 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!

    1. Hey! Did you try using the latest version of Unity?
      It seems there are few differences in the way they load scenes.

  15. 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

  16. Seriously great tutorial 🙂

  17. thanks, it works for me

  18. 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’?

    1. Hey! Thank you very much.
      Yeah, it is a coroutine and needs to be invoked with StartCoroutine(AsynchronousLoad(“scene”))! 😀

  19. Thank you, we are all waiting for seamless loading 🙂

  20. Virginia avatar
    Virginia

    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!

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

  22. 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)?

    1. 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.

      1. Kieron avatar

        Hi!

        Are you planning on the final part?

        Thanks!

        1. 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! 😀

          1. Kieron avatar

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

          2. 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.

          3. Soojung Ha avatar
            Soojung Ha

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

          4. it’s been over a year, Alan, where’s the second part?

Leave a Reply

Your email address will not be published. Required fields are marked *