Unity でゲーム開発する際、シーン切り替え時にもデータや特定のオブジェクトを維持したい場合があります。BGM の継続再生やプレイヤーデータの保持には DontDestroyOnLoad が必要です。
この記事では、DontDestroyOnLoad の基本的な使い方と一般的な実装パターンを解説します。シーン間でオブジェクトを効率的に永続化する方法を学びましょう。
DontDestroyOnLoad は Unity の Object クラスの静的メソッドで、新しいシーンロード時にオブジェクトを保持します。通常、シーン切り替えでは全オブジェクトが破棄されますが、このメソッドを使用したオブジェクトは維持されます。
DontDestroyOnLoad を適用したオブジェクトは特別な「DontDestroyOnLoad シーン」に移動します。このシーンはエディタで見えず、通常のシーンロードの影響を受けません。
DontDestroyOnLoad を使用するには、オブジェクトのルートに配置されたスクリプトから呼び出します:
using UnityEngine;
public class PersistentObject : MonoBehaviour
{
    void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}
このスクリプトをゲームオブジェクトにアタッチすると、そのオブジェクトは新しいシーンが読み込まれても破棄されなくなります。
DontDestroyOnLoad を使用する際の重要な注意点は:
DontDestroyOnLoad はルートオブジェクト(階層の最上位)にのみ適用できます。子オブジェクトに直接適用することはできません。DontDestroyOnLoad を適用すると、すべての子オブジェクトも自動的に保持されます。最も一般的な実装方法です:
using UnityEngine;
public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }
    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}
重複インスタンスが作成された場合は自動的に破棄され、唯一のインスタンスが保持されます。
複数のサービスを管理する拡張性の高い方法:
using System.Collections.Generic;
using UnityEngine;
public class ServiceLocator : MonoBehaviour
{
    public static ServiceLocator Instance { get; private set; }
    private Dictionary<System.Type, object> _services = new Dictionary<System.Type, object>();
    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    public void RegisterService<T>(T service) => _services[typeof(T)] = service;
    public T GetService<T>() => _services.TryGetValue(typeof(T), out object service)
        ? (T)service : default;
}
このパターンでオーディオ、セーブデータ、入力管理などを一元管理できます。
Unity の SceneManager との組み合わせ例:
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelManager : MonoBehaviour
{
    public static LevelManager Instance { get; private set; }
    [SerializeField] private GameObject _loadingScreen;
    private bool _isLoading = false;
    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            _loadingScreen.SetActive(false);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    public void LoadScene(string sceneName)
    {
        if (!_isLoading)
            StartCoroutine(LoadSceneAsync(sceneName));
    }
    private System.Collections.IEnumerator LoadSceneAsync(string sceneName)
    {
        _isLoading = true;
        _loadingScreen.SetActive(true);
        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);
        while (!operation.isDone)
            yield return null;
        _loadingScreen.SetActive(false);
        _isLoading = false;
    }
}
この例では、シーン切り替え時にローディング画面を表示し、スムーズな遷移を実現しています。
問題:
// 悪い例
void Awake()
{
    DontDestroyOnLoad(gameObject);  // 各シーンで複製が発生
}
解決策:
// 良い例
void Awake()
{
    if (FindObjectsOfType<GameManager>().Length > 1)
    {
        Destroy(gameObject);
        return;
    }
    DontDestroyOnLoad(gameObject);
}
シーン切替時にシーン固有オブジェクトへの参照が失われる問題。
解決策:
void OnEnable()
{
    SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDisable()
{
    SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    // 参照を再取得
    _player = GameObject.FindWithTag("Player");
}
永続オブジェクトが蓄積してメモリを圧迫する問題。
解決策:
public void CleanupPersistentObjects()
{
    // 不要なオブジェクトを削除
    GameObject[] tempEffects = GameObject.FindGameObjectsWithTag("PersistentTemp");
    foreach (var obj in tempEffects)
    {
        Destroy(obj);
    }
}
各永続化オブジェクトは明確な責任を持つべきです:
永続化オブジェクトには専用のタグを設定すると管理が容易になります:
// タグを使った永続化オブジェクトの管理
void Awake()
{
    GameObject[] persistentObjects = GameObject.FindGameObjectsWithTag("Persistent");
    if (persistentObjects.Length > 1)
    {
        foreach (var obj in persistentObjects)
        {
            if (obj != gameObject)
            {
                Destroy(gameObject);
                return;
            }
        }
    }
    gameObject.tag = "Persistent";
    DontDestroyOnLoad(gameObject);
}
ゲーム起動時に永続化オブジェクトを初期化するための専用シーンを作成するアプローチも効果的です:
これにより、永続化オブジェクトの初期化が確実に一度だけ行われます。
DontDestroyOnLoad は Unity でシーン間のオブジェクト永続化を実現するための強力なツールです。正しく使用すれば、シームレスなゲーム体験を提供し、コードの整理と再利用性を向上させることができます。
この記事で紹介した主なポイント:
これらの知識を活用して、より堅牢でメンテナンス性の高い Unity プロジェクトを開発しましょう。
Unity 2022.3 LTS 以上および C# 9.0 以上を基準としています。