Unity Scene Management – Finalizing Scene Loading/Unloading Process

March 19, 2019

Advancing Tower Defense Tutorial

Altering Scene Format

Myriad Games Studio – HOW TO USE THE UNITY SCENEMANAGER

Now we are going to look at fixing our scene loading so that occur with better timing and in the right order. The fading animation timing also needs fixed to work with this new scene loading setup.

We want to look at ordering these operations as: complete animation, complete loading next scene(s), unload current scene. Because we are looking to do several methods in order and only want to proceed when the previous is finished, my first thought was to make them all coroutines and have an overall IEnumerator run them with yield return. As far as I’ve seen, yield return can be used to ensure a coroutine finishing processing before starting the next action, as scene in the SceneLoaderAsync script I am following from Myriad Games in a previous blog post.

I started by removing any scene loading from the FadeOut coroutine so it was strictly dealing with fading out the screen. I then typed out the general scheme I was looking to follow with the timing of my coroutines:

  • IEnumerator Fade Out
  • IEnumerator Load Scene
  • IEnumerator Unload Scene
  • IEnumerator Fade In

This was the general order of actions I was intending to create. The screen should fade out, the next scene(s) should load in, the current scene(s) should unload, and then the screen should fade back in.

I started with a modified version of the SceneLoaderAsync script that I just called SceneLoader. I changed the input parameters of everything from string to int to work with build indices, and I modified LoadScenesInOrder by removing the part on using a loading screen for now as well as adding a line at the end that sets the loaded scene as active. I also added a coroutine that was UnloadSceneRoutine that was just a very minimalistic script to use UnloadSceneAsync method:

private IEnumerator UnloadSceneRoutine(int sceneBuildIndex)
{
SceneManager.UnloadSceneAsync(sceneBuildIndex);

yield return null;
}

Finally I put all of the SceneLoader script into my SceneManagerLite script for now since this is basically what it should be in charge of, and referncing coroutines from another script was giving me more issues than refencing methods, so this was easier for now. This gave me the 4 general coroutines I was looking to set up now.

I then went to test this with my Play method (which determines what scenes should be loaded/unloaded when the player presses Play from the main menu). The SceneLoaderAsync script just put their main coroutine into a method that was solely responsible for starting the coroutine, so I attempted to emulate that concept with my first attempt just to see if it would work. I had the LoadScenesInOrder and UnloadSceneRoutine just in their own methods and had them in the list of 4 actions I wanted to process, so it looked like:

IEnumerator PlayRoutine()
{
yield return StartCoroutine(FadeOut());

LoadScene(levelSelectorBuildIndex);

UnloadScene(mainMenuBuildIndex);

yield return StartCoroutine(FadeIn());
}

This was then just started in the method called Play(), so it would stay correctly tied to the button if was already attached to. I also wasn’t sure if this would be necessary to attach it to the button in the first place as well.

This setup almost worked, but the timing was still a bit weird. Just as the Fade In would happen, you would catch a glimpse of the scene to be unloaded before the actual scene being loaded in would appear. So I then just tried using all coroutines with yield return to see if that helped with the timing:

IEnumerator PlayRoutine()
{
yield return StartCoroutine(FadeOut());

yield return StartCoroutine(LoadScenesInOrder(levelSelectorSceneBuildIndex));

yield return StartCoroutine(UnloadSceneRoutine(mainMenuBuildIndex));

yield return StartCoroutine(FadeIn());

}

And this worked perfectly! Everything looked nice and orderly for my very low load time scene transition. From this, I could see larger scenes might present an issue since the screen would most likely just be black for a while, but for now this was a decent solution. Now I would also need to update my other scene transition methods but thought they would be pretty repetitive so I wanted to look into creating a single scene transition method to make working with these easier.

If I was going to be loading and unloading a single scene every time, this would be pretty straight forward to setup, but there are times where I need to load and unload more scenes than that. The method, called SceneTransition, would simply run the coroutine, SceneTransitionRoutine, which had a template similar to the one seen above in PlayRoutine, except that I added a foreach loop for the loading/unloading sections to go through an array to deal with the variable amount of scenes to deal with. This ended up coming out as this:

private IEnumerator SceneTransitionRoutine (int[] buildIndicesToBeLoaded, int[] buildIndicesToBeUnloaded)
{
yield return StartCoroutine(FadeOut());

foreach (int buildIndex in buildIndicesToBeLoaded)
{
yield return StartCoroutine(LoadScenesInOrder(buildIndex));
}

foreach (int buildIndex in buildIndicesToBeUnloaded)
{
yield return StartCoroutine(UnloadSceneRoutine(buildIndex));
}

yield return StartCoroutine(FadeIn());
}

This obviously would then have the SceneTransition method require int arrays as parameters as well. This solution is not amazing, but it ended up working for me for now. Now the methods connected to the buttons need to have their own int array variables that will just add the scenes to be loaded into one array, and scenes to be unloaded into a separate array. This feels a bit weird since this small project generally only deals with one scene at a time, sometimes with two, but I wanted to try and set something up that could handle more cases in the future. The Play() method was now:

public void Play()
{
int[] scenesToBeLoaded = { levelSelectorSceneBuildIndex };
int[] scenesToBeUnloaded = { mainMenuBuildIndex };

SceneTransition(scenesToBeLoaded, scenesToBeUnloaded);
}

And this also worked! I think this solution will be what I use going forward for the scene management for updating this tower defense tutorial. It’s not perfect, but it gets the job done and allows me to use a multiscene setup.

Last update, so this did give me a bit of trouble working with multiple scenes when determining the active scene. It wasn’t enough to just have the scene I wanted to be active just be the last value in the array, so I added a more controlled way to determine the active scenes. I moved the SetActive method from the loading method, to the overall SceneTransitionRoutine. This would be processed after the scenes were all loaded and unloaded. It would also take an input that was an additional parameter added to the SceneTransitionRoutine parameter list that was a single int that determined what build index would become the active scene. This resolved that issue.