Topic 7 of 15 9 min intermediate

Scenes & Loading

Additive scenes and scene flow

A scene is a container for GameObjects. Most games use several: a menu, a level, maybe a persistent scene that survives everything else. Loading them well is the difference between a smooth transition and a three-second freeze.

Scenes must be in the build settings

A scene that is not listed in File → Build Settings cannot be loaded at runtime. It works in the editor while it is open, then fails in a build.

"It works in the editor but the build shows a black screen" is almost always a scene missing from the build list, or one listed in the wrong order — index 0 is what launches first.

Loading, the wrong way and the right way

SceneManager.LoadScene blocks. Everything stops until the new scene is ready: no animation, no music, no input. For anything larger than a tiny scene, load asynchronously.

C#
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;

public class SceneLoader : MonoBehaviour
{
    public IEnumerator LoadLevel(string sceneName)
    {
        AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);

        while (!op.isDone)
        {
            // 0 to 0.9 is loading; it clamps there until activation
            float progress = Mathf.Clamp01(op.progress / 0.9f);
            progressBar.value = progress;
            yield return null;
        }
    }
}
op.progress stops at 0.9, not 1. The last tenth is activation, which only happens once you allow it. Divide by 0.9 to get a sane 0–1 bar.

Holding a scene until you are ready

Set allowSceneActivation = false to load everything but wait — useful for a "Press any key to continue" screen or to guarantee a minimum loading time so the bar does not flash past.

C#
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
op.allowSceneActivation = false;

// Fully loaded, just not swapped in yet
while (op.progress < 0.9f)
    yield return null;

yield return WaitForPlayerInput();

op.allowSceneActivation = true;   // now it switches

Additive loading

By default a new scene replaces the current one. LoadSceneMode.Additive adds it alongside instead, which is how you build streaming worlds and persistent managers.

C#
// Add a scene without unloading what is already there
SceneManager.LoadScene("Forest", LoadSceneMode.Additive);

// Remove it again
SceneManager.UnloadSceneAsync("Forest");

// New objects spawn into the active scene
SceneManager.SetActiveScene(SceneManager.GetSceneByName("Forest"));
The persistent-scene pattern

A common structure is a small "Bootstrap" scene loaded first, containing managers, audio, and UI that must never be destroyed. Everything else loads additively on top and unloads freely.

This avoids DontDestroyOnLoad entirely, which sidesteps the duplicate-manager problem below and makes it obvious where global objects live — they are simply in the scene that is always loaded.

DontDestroyOnLoad and its trap

This marks an object to survive scene loads. It is useful for music players and managers, and it has one failure mode that catches everybody.

C#
public class AudioManager : MonoBehaviour
{
    public static AudioManager Instance { get; private set; }

    private void Awake()
    {
        // Load the first scene again and a SECOND manager appears,
        // because the surviving one is already there.
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
}
Without that guard, returning to the menu scene duplicates every persistent object. Two audio managers means every sound plays twice, slightly out of phase — a distinctive symptom worth recognising.
DontDestroyOnLoad only works on root objects. Calling it on a child silently does nothing.

Knowing when a scene is ready

C#
private void OnEnable()
{
    SceneManager.sceneLoaded += OnSceneLoaded;
}

private void OnDisable()
{
    SceneManager.sceneLoaded -= OnSceneLoaded;
}

private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    if (scene.name == "Level2")
        SpawnPlayerAtCheckpoint();
}

What to take away

  • Every loadable scene must be listed in Build Settings.
  • Prefer LoadSceneAsync; LoadScene freezes the game.
  • progress caps at 0.9 until you allow activation.
  • Additive loading keeps managers alive without DontDestroyOnLoad.
  • Guard persistent singletons against duplicating themselves.