Guides / Unity Scene Management / Scene Management: Loading and Transitions

Unity Scene Management: Loading and Transitions

What is Scene Management?

Unity's scene management system is the foundation for organizing and transitioning between different parts of your game. It handles loading, unloading, and switching between scenes seamlessly.

Key Components:

  • Scene Loading - Load scenes asynchronously without blocking the game
  • Scene Transitions - Smooth transitions between different game areas
  • Scene Persistence - Keep data and objects between scene changes
  • Additive Loading - Load multiple scenes simultaneously
  • Scene Management API - Programmatic control over scene operations

Why Scene Management Matters

Unity's scene management system offers several advantages:

  • Memory Management - Efficiently manage memory by loading/unloading content
  • Organization - Keep your game organized with logical scene separation
  • Performance - Load only what you need, when you need it
  • User Experience - Create smooth transitions and loading experiences
  • Scalability - Build games of any size with proper scene architecture

Scene Management Overview

Scene Loading Basics

Unity provides several ways to load scenes:

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    public void LoadScene(string sceneName)
    {
        // Load scene synchronously (blocks until complete)
        SceneManager.LoadScene(sceneName);
    }
    
    public void LoadSceneAsync(string sceneName)
    {
        // Load scene asynchronously (non-blocking)
        StartCoroutine(LoadSceneCoroutine(sceneName));
    }
    
    private IEnumerator LoadSceneCoroutine(string sceneName)
    {
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
        
        // Wait until the scene is fully loaded
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
    }
}

Scene Transitions

Create smooth transitions between scenes:

public class SceneTransition : MonoBehaviour
{
    [Header("Transition Settings")]
    public float transitionDuration = 1f;
    public AnimationCurve fadeCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
    
    [Header("UI References")]
    public CanvasGroup fadeCanvas;
    
    public void TransitionToScene(string sceneName)
    {
        StartCoroutine(TransitionCoroutine(sceneName));
    }
    
    private IEnumerator TransitionCoroutine(string sceneName)
    {
        // Fade out
        yield return StartCoroutine(FadeOut());
        
        // Load new scene
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
        asyncLoad.allowSceneActivation = false;
        
        // Wait for scene to load
        while (asyncLoad.progress < 0.9f)
        {
            yield return null;
        }
        
        // Activate scene
        asyncLoad.allowSceneActivation = true;
        
        // Wait for scene to be active
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        // Fade in
        yield return StartCoroutine(FadeIn());
    }
    
    private IEnumerator FadeOut()
    {
        float elapsed = 0f;
        
        while (elapsed < transitionDuration)
        {
            elapsed += Time.deltaTime;
            float alpha = fadeCurve.Evaluate(elapsed / transitionDuration);
            fadeCanvas.alpha = 1f - alpha;
            yield return null;
        }
        
        fadeCanvas.alpha = 1f;
    }
    
    private IEnumerator FadeIn()
    {
        float elapsed = 0f;
        
        while (elapsed < transitionDuration)
        {
            elapsed += Time.deltaTime;
            float alpha = fadeCurve.Evaluate(elapsed / transitionDuration);
            fadeCanvas.alpha = alpha;
            yield return null;
        }
        
        fadeCanvas.alpha = 0f;
    }
}

Scene Persistence

DontDestroyOnLoad

Keep objects between scene changes:

public class PersistentObject : MonoBehaviour
{
    private static PersistentObject instance;
    
    private void Awake()
    {
        // Singleton pattern
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

Scene Data Persistence

Save and load data between scenes:

public class SceneDataManager : MonoBehaviour
{
    [System.Serializable]
    public class SceneData
    {
        public string playerName;
        public int playerLevel;
        public Vector3 playerPosition;
        public List completedQuests;
    }
    
    private SceneData currentData;
    
    private void Start()
    {
        // Load data when scene starts
        LoadSceneData();
    }
    
    public void SaveSceneData()
    {
        // Save current scene data
        currentData = new SceneData
        {
            playerName = PlayerPrefs.GetString("PlayerName"),
            playerLevel = PlayerPrefs.GetInt("PlayerLevel"),
            playerPosition = transform.position,
            completedQuests = GetCompletedQuests()
        };
        
        // Save to persistent storage
        string json = JsonUtility.ToJson(currentData);
        PlayerPrefs.SetString("SceneData", json);
        PlayerPrefs.Save();
    }
    
    public void LoadSceneData()
    {
        if (PlayerPrefs.HasKey("SceneData"))
        {
            string json = PlayerPrefs.GetString("SceneData");
            currentData = JsonUtility.FromJson(json);
            
            // Apply loaded data
            ApplySceneData(currentData);
        }
    }
    
    private void ApplySceneData(SceneData data)
    {
        // Apply loaded data to current scene
        transform.position = data.playerPosition;
        // Apply other data as needed
    }
    
    private List GetCompletedQuests()
    {
        // Get completed quests from quest system
        return new List();
    }
}

Additive Scene Loading

Loading Multiple Scenes

Load additional scenes without unloading the current one:

public class AdditiveSceneLoader : MonoBehaviour
{
    [Header("Scene Settings")]
    public string[] scenesToLoad;
    public bool loadOnStart = true;
    
    private List loadedScenes = new List();
    
    private void Start()
    {
        if (loadOnStart)
        {
            LoadAllScenes();
        }
    }
    
    public void LoadAllScenes()
    {
        foreach (string sceneName in scenesToLoad)
        {
            LoadSceneAdditive(sceneName);
        }
    }
    
    public void LoadSceneAdditive(string sceneName)
    {
        if (!loadedScenes.Contains(sceneName))
        {
            StartCoroutine(LoadSceneAdditiveCoroutine(sceneName));
        }
    }
    
    private IEnumerator LoadSceneAdditiveCoroutine(string sceneName)
    {
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
        
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        loadedScenes.Add(sceneName);
        Debug.Log($"Loaded scene: {sceneName}");
    }
    
    public void UnloadScene(string sceneName)
    {
        if (loadedScenes.Contains(sceneName))
        {
            StartCoroutine(UnloadSceneCoroutine(sceneName));
        }
    }
    
    private IEnumerator UnloadSceneCoroutine(string sceneName)
    {
        AsyncOperation asyncUnload = SceneManager.UnloadSceneAsync(sceneName);
        
        while (!asyncUnload.isDone)
        {
            yield return null;
        }
        
        loadedScenes.Remove(sceneName);
        Debug.Log($"Unloaded scene: {sceneName}");
    }
}

Advanced Scene Management

Scene Manager with Events

Create a comprehensive scene management system:

public class AdvancedSceneManager : MonoBehaviour
{
    [Header("Scene Settings")]
    public string[] sceneNames;
    public float loadingProgress;
    
    [Header("Events")]
    public UnityEvent OnSceneLoadStart;
    public UnityEvent OnSceneLoadComplete;
    public UnityEvent OnSceneUnloadStart;
    public UnityEvent OnSceneUnloadComplete;
    
    private Dictionary loadedScenes = new Dictionary();
    private string currentActiveScene;
    
    public void LoadScene(string sceneName, LoadSceneMode mode = LoadSceneMode.Single)
    {
        StartCoroutine(LoadSceneCoroutine(sceneName, mode));
    }
    
    private IEnumerator LoadSceneCoroutine(string sceneName, LoadSceneMode mode)
    {
        OnSceneLoadStart?.Invoke();
        
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName, mode);
        asyncLoad.allowSceneActivation = false;
        
        // Update loading progress
        while (asyncLoad.progress < 0.9f)
        {
            loadingProgress = asyncLoad.progress;
            yield return null;
        }
        
        // Activate scene
        asyncLoad.allowSceneActivation = true;
        
        // Wait for completion
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        // Update scene tracking
        Scene loadedScene = SceneManager.GetSceneByName(sceneName);
        loadedScenes[sceneName] = loadedScene;
        currentActiveScene = sceneName;
        
        OnSceneLoadComplete?.Invoke();
    }
    
    public void UnloadScene(string sceneName)
    {
        if (loadedScenes.ContainsKey(sceneName))
        {
            StartCoroutine(UnloadSceneCoroutine(sceneName));
        }
    }
    
    private IEnumerator UnloadSceneCoroutine(string sceneName)
    {
        OnSceneUnloadStart?.Invoke();
        
        AsyncOperation asyncUnload = SceneManager.UnloadSceneAsync(sceneName);
        
        while (!asyncUnload.isDone)
        {
            yield return null;
        }
        
        loadedScenes.Remove(sceneName);
        OnSceneUnloadComplete?.Invoke();
    }
    
    public void SetActiveScene(string sceneName)
    {
        if (loadedScenes.ContainsKey(sceneName))
        {
            SceneManager.SetActiveScene(loadedScenes[sceneName]);
            currentActiveScene = sceneName;
        }
    }
    
    public bool IsSceneLoaded(string sceneName)
    {
        return loadedScenes.ContainsKey(sceneName);
    }
    
    public string GetCurrentActiveScene()
    {
        return currentActiveScene;
    }
}

Scene Loading UI

Loading Screen Implementation

Create a professional loading screen:

public class LoadingScreen : MonoBehaviour
{
    [Header("UI References")]
    public Slider progressBar;
    public TextMeshProUGUI loadingText;
    public TextMeshProUGUI progressText;
    public Image loadingIcon;
    
    [Header("Loading Settings")]
    public float rotationSpeed = 90f;
    public string[] loadingMessages = {
        "Loading...",
        "Preparing game world...",
        "Loading assets...",
        "Almost ready..."
    };
    
    private Coroutine loadingCoroutine;
    
    public void ShowLoadingScreen(string sceneName)
    {
        gameObject.SetActive(true);
        loadingCoroutine = StartCoroutine(LoadingCoroutine(sceneName));
    }
    
    private IEnumerator LoadingCoroutine(string sceneName)
    {
        // Start loading
        AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
        asyncLoad.allowSceneActivation = false;
        
        float progress = 0f;
        int messageIndex = 0;
        
        while (progress < 1f)
        {
            // Update progress
            progress = Mathf.Clamp01(asyncLoad.progress / 0.9f);
            progressBar.value = progress;
            progressText.text = $"{Mathf.RoundToInt(progress * 100)}%";
            
            // Update loading message
            if (progress > (messageIndex + 1) / (float)loadingMessages.Length)
            {
                loadingText.text = loadingMessages[messageIndex];
                messageIndex = Mathf.Min(messageIndex + 1, loadingMessages.Length - 1);
            }
            
            // Rotate loading icon
            loadingIcon.transform.Rotate(0, 0, rotationSpeed * Time.deltaTime);
            
            yield return null;
        }
        
        // Activate scene
        asyncLoad.allowSceneActivation = true;
        
        // Wait for scene to be active
        while (!asyncLoad.isDone)
        {
            yield return null;
        }
        
        // Hide loading screen
        gameObject.SetActive(false);
    }
}

Scene Management Best Practices

Performance Optimization

  • Use Async Loading - Never block the main thread
  • Preload Scenes - Load scenes in the background
  • Unload Unused Scenes - Free up memory when possible
  • Use Object Pooling - Reuse objects between scenes

Memory Management

  • Monitor Memory Usage - Use Unity Profiler to track memory
  • Unload Unused Assets - Call Resources.UnloadUnusedAssets()
  • Use Addressables - For large projects with many assets
  • Optimize Scene Size - Keep scenes focused and manageable

User Experience

  • Loading Screens - Provide visual feedback during loading
  • Progress Indicators - Show loading progress to users
  • Smooth Transitions - Use fade effects and animations
  • Error Handling - Handle loading failures gracefully

Common Scene Management Issues

Scene Not Loading

Problem: Scene fails to load or takes too long

Solutions:

  • Check scene name spelling
  • Verify scene is in Build Settings
  • Use async loading for large scenes
  • Check for memory issues

Data Loss Between Scenes

Problem: Player data is lost when changing scenes

Solutions:

  • Use DontDestroyOnLoad for persistent objects
  • Save data before scene changes
  • Use static variables for temporary data
  • Implement proper data persistence

Performance Issues

Problem: Scene loading causes frame drops

Solutions:

  • Use async loading
  • Preload scenes in the background
  • Optimize scene content
  • Use additive loading for large worlds

Resources and Next Steps

Unity Documentation

Learning Resources

Ready to create seamless game experiences? Start with simple scene loading and gradually work your way up to complex scene management systems!