Getting a reference to newly loaded scenes is an issue I came across when doing dungeon generation for MASKED. And I figured I could make a small blog post about it. Sometimes you want to load a bunch of scenes additively and actually access the objects inside of those scenes using a scene reference. When doing a additive load of a scene, you will receive a AsyncOperation.

The AsyncOperation class only helps you keep track of the process of the scene loading and it gives you the ability to not directly activate objects within that scene using the allowSceneActivation property. If you have this disabled, it will never active the game objects in the scene until it is done. If you do this, you have to check if the progress property is 0.9f, you can then toggle allowSceneActivation

Note: be aware to call the method in Start() or to add a delay when you want to set allowSceneActivation to false. This is due to a design choice from Unity Technologies.

In order to actually get the reference to the newly loaded scene you have to keep track of the current loaded scene count. Before calling SceneManager.LoadSceneAsync you store the total loaded scene count by calling SceneManager.sceneCount. And you can then store it in either a dictionary or any other collection type, depending on your approach. You can then use that index after loading is complete to call SceneManager.GetSceneAt(trackedIndex); This will give you the current scene reference.

Scene reference loading MASKED

Some examples

As a example, I’ve created a script that moves the root object of a scene to a different location, upon load. In my case I’ve opted to use the .completed delegate. Once completed, SceneManager.GetSceneAt(operationIndex, position) gets called, ensuring I get the correct scene index. This is just a simple example, I would not recommend using GetRootGameObjects()[0] since it is error prone, but it helps demonstrate how it works.

[SerializeField] private string spawnedSceneName;

    private void Start()
    {
        for (int y = 0; y < 3; y++)
        {
            for (int x = 0; x < 3; x++)
            {
                int operationIndex = SceneManager.sceneCount;
                var operation = SceneManager.LoadSceneAsync(spawnedSceneName, LoadSceneMode.Additive);

                Vector3 targetPosition = new Vector3(x * 20, 0, y * 20);

                operation.completed += (s) => 
                {
                    OnSceneSpawned(SceneManager.GetSceneAt(operationIndex), targetPosition);
                };
            }
        }
    }

    private void OnSceneSpawned(Scene scene, Vector3 targetPosition)
    {
        scene.GetRootGameObjects()[0].gameObject.transform.position = targetPosition;
    }

Here is a way you can approach it when loading one scene additively.

    private IEnumerator Start()
    {
        int index = SceneManager.sceneCount;
        var op = SceneManager.LoadSceneAsync (spawnedSceneName, LoadSceneMode.Additive);

        yield return new WaitUntil(() => op.isDone);

        Scene spawnedScene = SceneManager.GetSceneAt(index);
    }

We can only hope there may be a .Result variable in the AsyncOperation in the future for this method. From my knowledge something like this already exists for the newer Addressables system, which uses asset bundles to load assets, instantiate gameObjects or load scenes.

Extra: tracking progress with multiple scenes

I figured, since we are on the subject of scene loading. I will also explain how you can load multiple scenes and update a slider bar on how far the loading process it. This is one of the benefits of using multiple scenes for dungeon generation. Since there is a async way of instantiating prefabs. (Unless you use the Addressables system)

Below you see a method you can use to load multiple scenes directly. There are several events you can subscribe to. You can use the progress event parameter to get updates regarding to progress. These range from 0f to 1f. There is also a onSceneLoaded event parameter which gets called to notify you when you are able to load the scene reference. you can use activateDirectly to decide if you want objects to load directly after the scene has been loaded. If you set this to false, all scene objects get activated once everything has been loaded.

You can simple add the method below to a scene load script. Make sure to call it using the StartCoroutine() method. At the bottom of this page there is a also a example on how to use this script.

    private IEnumerator LoadMultipleScenes(System.Action<float> progress, System.Action<Scene,
        string> onSceneLoaded, string[] scenesToLoad, bool activateDirectly)
    {
        // Added a silly delay, to prevent this "by design" bug: 
        // https://issuetracker.unity3d.com/issues/loadsceneasync-allowsceneactivation-flag-is-ignored-in-awake

        yield return new WaitForSeconds(1);

        int totalScenes = scenesToLoad.Length;
        int totalScenesLoaded = 0;
        int[] sceneLoadIndexes = new int[totalScenes]; // To keep track of the index. So we can load the scene
        AsyncOperation[] sceneLoadOperations = new AsyncOperation[totalScenes]; // To keep track of progress

        for (int i = 0; i < totalScenes; i++)
        {
            sceneLoadIndexes[i] = SceneManager.sceneCount;
            sceneLoadOperations[i] = SceneManager.LoadSceneAsync(scenesToLoad[i], LoadSceneMode.Additive);
            sceneLoadOperations[i].allowSceneActivation = activateDirectly;

            if (onSceneLoaded != null)
            {
                // Create variables and subscribe to events.
                int sceneLoadIndex = sceneLoadIndexes[i];
                string sceneLoadName = scenesToLoad[i];

                sceneLoadOperations[i].completed += (s) =>
                    {
                        totalScenesLoaded++;
                        onSceneLoaded.Invoke(SceneManager.GetSceneAt(sceneLoadIndex), sceneLoadName);
                    };
            }
        }

        float activeProgress = 0;

        // Remember, when allowSceneActivation is set to false, it will stop at 0.9 percent
        float targetProgress = (activateDirectly) ? 1 * totalScenes : 0.9f * totalScenes;

        WaitForSeconds waitForSeconds = new WaitForSeconds(0.1f);

        while (!Mathf.Approximately(activeProgress, targetProgress))
        {
            // Count all the progress
            activeProgress = 0;

            for (int i = 0; i < totalScenes; i++)
            {
                activeProgress += sceneLoadOperations[i].progress;
            }

            // Send a progress event. Inverse lerp gives the same result as: active / target
            if (progress != null)
            {
                progress.Invoke(Mathf.InverseLerp(0, targetProgress + totalScenes, activeProgress + totalScenesLoaded));
            }

            yield return waitForSeconds; // Wait for 0.1 seconds
        }

        // If we have set the scenes to not activate directly
        if (!activateDirectly)
        {
            activeProgress = targetProgress;

            for (int i = 0; i < totalScenes; i++)
            {
                sceneLoadOperations[i].allowSceneActivation = true;
            }
        }

        while (totalScenesLoaded != totalScenes)
        {
            yield return waitForSeconds; // Wait for 0.1 seconds

            // Send a progress event. Inverse lerp gives the same result as: active / target
            if (progress != null)
            {
                progress.Invoke(Mathf.InverseLerp(0, targetProgress + totalScenes, activeProgress + totalScenesLoaded));
            }
        }
    }

You can utilize the script above like the code you see below

    [SerializeField] private Slider slider;
    [SerializeField] private string[] loadScenes;

    private void Start()
    {
        StartCoroutine(LoadMultipleScenes(OnUpdateProgress, OnSceneLoaded, loadScenes, false));
    }

    private void OnSceneLoaded(Scene scene, string sceneName)
    {
        // Do something with objects in the scene, getting a reference, activating objects... etc
        scene.GetRootGameObjects()[0].gameObject.SetActive(false);
    }

    private void OnUpdateProgress(float progress)
    {
        slider.value = progress;
    }