Scene Manager
Async scene loading with loading screen, progress bar, and minimum load time. Clean transitions between levels.
How to Use
Create a persistent 'SceneLoader' object (DontDestroyOnLoad)
Attach SceneLoader and optionally assign a loading screen panel
Call SceneLoader.Instance.LoadScene("LevelName") to load
Hook OnLoadProgress to a UI Slider for a progress bar
Set minimumLoadTime to prevent flash-loading on fast hardware
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
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;
}
}