beginner Utilities

Scene Manager

Async scene loading with loading screen, progress bar, and minimum load time. Clean transitions between levels.

Unity 2022.3+ · 2.5 KB · SceneLoader.cs

How to Use

1

Create a persistent 'SceneLoader' object (DontDestroyOnLoad)

2

Attach SceneLoader and optionally assign a loading screen panel

3

Call SceneLoader.Instance.LoadScene("LevelName") to load

4

Hook OnLoadProgress to a UI Slider for a progress bar

5

Set minimumLoadTime to prevent flash-loading on fast hardware

6

Pair with ScreenFader for polished scene transitions

Features

  • Async scene loading with progress callback for loading bars
  • Minimum load time to prevent jarring flash-loads on fast hardware
  • Loading screen GameObject toggled automatically during transitions
  • Load by scene name or build index with overloaded methods
  • ReloadCurrentScene convenience method for quick restarts
  • UnityEvents for load started, progress updates, and load complete

When to Use This

Use in any multi-scene game — platformers with levels, RPGs with overworlds and dungeons, horror games with chapters, or any game with a main menu. Essential when you want a loading screen with a progress bar between scenes. Especially valuable for mobile games where loading times vary significantly across devices.

Common Mistakes

All scenes must be added to the Build Settings (File > Build Settings > Scenes In Build) or LoadSceneAsync will fail silently. The loading screen GameObject must be a child of the SceneLoader's DontDestroyOnLoad object, otherwise it gets destroyed during the scene transition it's supposed to cover. Setting minimumLoadTime to 0 on fast hardware causes the loading screen to flash for a single frame.

Source Code

SceneLoader.cs
C#
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Events;
using System.Collections;

/// <summary>
/// Async scene loader with loading screen support.
/// Provides progress callbacks for loading UI.
/// </summary>
public class SceneLoader : MonoBehaviour
{
    public static SceneLoader Instance { get; private set; }

    [Header("Loading Settings")]
    [SerializeField] private float minimumLoadTime = 1f;
    [SerializeField] private GameObject loadingScreen;

    [Header("Events")]
    public UnityEvent<float> OnLoadProgress;
    public UnityEvent OnLoadStarted;
    public UnityEvent OnLoadComplete;

    private bool isLoading;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    /// <summary>
    /// Load a scene by name with loading screen.
    /// </summary>
    public void LoadScene(string sceneName)
    {
        if (isLoading) return;
        StartCoroutine(LoadSceneAsync(sceneName));
    }

    /// <summary>
    /// Load a scene by build index with loading screen.
    /// </summary>
    public void LoadScene(int buildIndex)
    {
        if (isLoading) return;
        string scenePath = UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(buildIndex);
        string sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePath);
        StartCoroutine(LoadSceneAsync(sceneName));
    }

    /// <summary>
    /// Reload the current scene.
    /// </summary>
    public void ReloadCurrentScene()
    {
        LoadScene(SceneManager.GetActiveScene().name);
    }

    private IEnumerator LoadSceneAsync(string sceneName)
    {
        isLoading = true;

        OnLoadStarted?.Invoke();

        if (loadingScreen != null)
            loadingScreen.SetActive(true);

        OnLoadProgress?.Invoke(0f);

        float startTime = Time.unscaledTime;

        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);
        operation.allowSceneActivation = false;

        while (!operation.isDone)
        {
            // Unity loads to 0.9, then waits for activation
            float progress = Mathf.Clamp01(operation.progress / 0.9f);
            OnLoadProgress?.Invoke(progress);

            if (operation.progress >= 0.9f)
            {
                // Enforce minimum load time
                float elapsed = Time.unscaledTime - startTime;
                if (elapsed < minimumLoadTime)
                {
                    yield return new WaitForSecondsRealtime(minimumLoadTime - elapsed);
                }

                OnLoadProgress?.Invoke(1f);
                yield return new WaitForSecondsRealtime(0.1f);

                operation.allowSceneActivation = true;
            }

            yield return null;
        }

        if (loadingScreen != null)
            loadingScreen.SetActive(false);

        OnLoadComplete?.Invoke();
        isLoading = false;
    }
}