Maemaemae

Unity の DontDestroyOnLoad 実践ガイド

Unity でゲーム開発する際、シーン切り替え時にもデータや特定のオブジェクトを維持したい場合があります。BGM の継続再生やプレイヤーデータの保持には DontDestroyOnLoad が必要です。

この記事では、DontDestroyOnLoad の基本的な使い方と一般的な実装パターンを解説します。シーン間でオブジェクトを効率的に永続化する方法を学びましょう。

目次

DontDestroyOnLoad の基本

機能概要

DontDestroyOnLoad は Unity の Object クラスの静的メソッドで、新しいシーンロード時にオブジェクトを保持します。通常、シーン切り替えでは全オブジェクトが破棄されますが、このメソッドを使用したオブジェクトは維持されます。

主な用途

仕組み

DontDestroyOnLoad を適用したオブジェクトは特別な「DontDestroyOnLoad シーン」に移動します。このシーンはエディタで見えず、通常のシーンロードの影響を受けません。

実装方法

基本的な使用法

DontDestroyOnLoad を使用するには、オブジェクトのルートに配置されたスクリプトから呼び出します:

using UnityEngine;

public class PersistentObject : MonoBehaviour
{
    void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}

このスクリプトをゲームオブジェクトにアタッチすると、そのオブジェクトは新しいシーンが読み込まれても破棄されなくなります。

重要な条件と制限

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);
    }
}

ベストプラクティス

1. 明確な責任分担

各永続化オブジェクトは明確な責任を持つべきです:

2. タグとレイヤーの活用

永続化オブジェクトには専用のタグを設定すると管理が容易になります:

// タグを使った永続化オブジェクトの管理
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);
}

3. 初期化シーンの活用

ゲーム起動時に永続化オブジェクトを初期化するための専用シーンを作成するアプローチも効果的です:

  1. Initシーンを作成し、そこにすべての永続化オブジェクトを配置
  2. ゲーム起動時にInitシーンを最初にロード
  3. 初期化完了後、最初のゲームシーンへ遷移

これにより、永続化オブジェクトの初期化が確実に一度だけ行われます。

まとめ

DontDestroyOnLoad は Unity でシーン間のオブジェクト永続化を実現するための強力なツールです。正しく使用すれば、シームレスなゲーム体験を提供し、コードの整理と再利用性を向上させることができます。

この記事で紹介した主なポイント:

これらの知識を活用して、より堅牢でメンテナンス性の高い Unity プロジェクトを開発しましょう。


Unity 2022.3 LTS 以上および C# 9.0 以上を基準としています。

UnityC#ゲーム開発シーン管理オブジェクト永続化