DontDestroyOnLoadとは?

DontDestroyOnLoadとは、シーンを切り替えても指定したGameObjectを破棄せずに残し続けるためのUnityのメソッドです。

通常、SceneManager.LoadSceneなどで別のシーンを読み込むと、それまでのシーンに存在していたGameObjectはすべて破棄されます。

しかしゲームを作っていると、シーンが変わってもずっと存在し続けてほしいオブジェクトが出てきます。

そういったオブジェクトをDontDestroyOnLoadに登録しておくと、シーン遷移をまたいでも破棄されず、状態を保持したまま生き続けます。

// このGameObjectをシーン遷移後も破棄しないようにする
DontDestroyOnLoad(gameObject);

どんな時に使う?

シーンを跨いで保持したいデータや機能がある場合に役立ちます。

具体的には、以下のようなオブジェクトでよく使われます。

  • ゲーム全体を管理するGameManager
  • シーンが変わってもBGMを途切れさせたくないAudioSource
  • スコアやプレイヤーの設定など、シーンを越えて保持したいデータ

例えばBGMを鳴らすAudioSourceを持ったGameObjectDontDestroyOnLoadに登録しておけば、

タイトルからゲーム本編へシーンを切り替えても、音楽が途切れずに鳴り続けます。

このように「シーンが変わっても破棄されたくないもの」を残すのが、DontDestroyOnLoadの役割です。

基本的な使い方

使い方はとてもシンプルで、残したいGameObjectを引数に渡してDontDestroyOnLoadを呼ぶだけです。

呼び出すタイミングは、生成直後に一度だけ実行されるAwakeメソッドが定番です。

using UnityEngine;

public class BgmPlayer : MonoBehaviour
{
    private void Awake()
    {
        // このGameObjectをシーン遷移後も破棄しないようにする
        DontDestroyOnLoad(gameObject);
    }
}

これだけで、このBgmPlayerがアタッチされたGameObjectはシーンを切り替えても破棄されなくなります。

注意したいのは、引数に渡すのは「残したいオブジェクト自身」だという点です。

DontDestroyOnLoad(this)のようにコンポーネントを渡すこともできますが、実際に保持されるのはそのコンポーネントが属するGameObjectです。

意図しないオブジェクトを残してしまわないよう、基本的にはgameObjectを渡すと覚えておくと分かりやすいです。

Singletonパターンとの組み合わせ

DontDestroyOnLoadは、シングルトンパターンと組み合わせて使われることが非常に多いです。

GameManagerSoundManagerのように「シーンをまたいで常に一つだけ存在し、どこからでもアクセスしたい」クラスとの相性が良いためです。

シングルトンのInstanceAwakeで確定させ、同時にDontDestroyOnLoadを呼んでおくことで、シーン遷移後も同じインスタンスを参照し続けられます。

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            // シーンを跨いでも破棄されないようにする
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // 後から生成された重複インスタンスは破棄する
            Destroy(gameObject);
        }
    }

    public void StartGame()
    {
        Debug.Log("Game Started");
    }
}

シングルトンパターン自体の詳しい解説は、別の記事にまとめています。

あわせて読むと、なぜこの実装になるのかが理解しやすくなります。

重複生成を防ぐ

DontDestroyOnLoadを使うときに必ず気をつけたいのが、オブジェクトの重複です。

というのも、シーンを再ロードしたり、最初のシーンに戻ったりすると、そのシーンに最初から配置されているGameObjectが改めて生成されてしまうからです。

例えばGameManagerを最初のシーンに配置しておくと、そのシーンに戻ってくるたびに新しいGameManagerが生成され、DontDestroyOnLoadで残っていた古いものと合わせて2個、3個と増えていきます。

これを防ぐのが、先ほどのコードにあったInstanceのチェックです。

private void Awake()
{
    if (Instance == null)
    {
        // まだInstanceが存在しないので、自分を登録する
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        // すでにInstanceが存在するなら、自分は不要なので破棄する
        Destroy(gameObject);
    }
}

Instanceがすでに存在している=過去のシーンから生き残ったオブジェクトがいる、ということです。

その場合は新しく生成された自分自身をDestroyすることで、常に一つだけが残るように保てます。

DontDestroyOnLoadとシングルトンがセットで語られるのは、この重複対策が欠かせないためです。

注意点

便利なDontDestroyOnLoadですが、挙動にいくつかクセがあるので押さえておきましょう。

専用のシーンに移動する

DontDestroyOnLoadを呼ばれたGameObjectは、元のシーンからDontDestroyOnLoadという名前の特殊なシーンへ移動します。

Hierarchyウィンドウでも、実行中は通常のシーンとは別にDontDestroyOnLoadシーンが表示され、その下に対象のオブジェクトが並びます。

そのため、SceneManager.GetActiveSceneで取得したシーンのオブジェクトを走査するような処理では、残したオブジェクトが含まれない点に注意が必要です。

ルートのオブジェクトにのみ有効

DontDestroyOnLoadは、ルート(最上位)のGameObjectに対して効果を発揮します。

もし対象が他のGameObjectの子になっている場合、子だけを渡しても保持されません。

コンソールに「DontDestroyOnLoad only works for root GameObjects or components on root GameObjects.」という警告が表示され、その呼び出しは無視されます。

残したいオブジェクトは、あらかじめルートに配置しておくのが基本です。なお親(ルート)をDontDestroyOnLoadに登録すれば、その子もまとめて残ります。

意図せず余計なオブジェクトまで残してしまわないよう、残したいオブジェクトは親子関係を整理しておくのがおすすめです。

どうしても子オブジェクトだけを残したい場合は、transform.SetParent(null)で親から切り離してからDontDestroyOnLoadを呼ぶ方法があります。

破棄したい時は明示的にDestroyする

DontDestroyOnLoadに登録したオブジェクトは、シーンを切り替えても自動では消えません。

つまり、不要になったタイミングで自分からDestroyしてあげる必要があります。

// 不要になったDontDestroyOnLoadのオブジェクトを破棄する
Destroy(GameManager.Instance.gameObject);

「タイトルに戻ったらBGMを止めて破棄したい」といったケースでは、明示的にDestroyを呼ぶことを忘れないようにしましょう。

残し続けるつもりがないオブジェクトをそのままにしておくと、メモリ上に居座り続けてしまいます。

DontDestroyOnLoadを解除するには

先ほどのDestroyはオブジェクトそのものを消す操作でしたが、「消さずに通常のシーンへ戻したい」という場面もあります。

ただ、DontDestroyOnLoadには対応する「解除メソッド」が用意されていません。

一度登録したオブジェクトを通常のシーンに戻したい場合は、SceneManager.MoveGameObjectToSceneで別のシーンへ移動させます。

using UnityEngine;
using UnityEngine.SceneManagement;

public class LoaderObject : MonoBehaviour
{
    public void MoveToCurrentScene()
    {
        // 現在のアクティブシーンに移動させる
        Scene active = SceneManager.GetActiveScene();
        SceneManager.MoveGameObjectToScene(gameObject, active);
    }
}

これを使えば、「ロード中だけ常駐させたいが、ロード後はそのシーンと一緒に破棄したい」といった制御もできます。

シーン側のオブジェクト参照に注意

DontDestroyOnLoadしたマネージャーが、シーン側のGameObjectInspectorで参照していると、シーン切り替え時に参照がMissingになってしまいます。

残ったマネージャーだけが生き延び、元のシーンにあった参照先は破棄されてしまうためです。

シーン側のオブジェクトを扱いたい場合は、SceneManager.sceneLoadedイベントを購読して、読み込まれた新しいシーンの中から対象を探す方法が定番です。

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        // 先ほどのSingletonパターンに、sceneLoadedの購読を足した形
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);

            // シーンが読み込まれた時のコールバックを登録
            SceneManager.sceneLoaded += OnSceneLoaded;
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void OnDestroy()
    {
        // 登録の解除を忘れずに
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // 新しいシーンに合わせた初期化処理
        Debug.Log($"Loaded: {scene.name}");
    }
}

sceneLoadedを使えば、シーン遷移のたびに必要な初期化を走らせられます。

なお、DontDestroyOnLoadしたオブジェクトはアプリ終了まで残るため、イベントの購読も残り続けます。

OnDestroyでの-=による解除を忘れないようにしましょう。

まとめ

今回は、シーン遷移をまたいでオブジェクトを残すDontDestroyOnLoadについて解説しました。

GameManagerやBGM用のAudioSourceなど、シーンが変わっても存在し続けてほしいオブジェクトに使うと非常に便利な機能です。

一方で、シーンの再ロードによる重複生成には注意が必要で、シングルトンのInstanceチェックと組み合わせて対策するのが定番のパターンになります。

また、解除にはSceneManager.MoveGameObjectToSceneを使うことや、シーン側のオブジェクトへの参照が切れやすい点など、いくつかのクセも押さえておきましょう。

使いどころを見極めて、シーンを越えて状態を保持したい場面でぜひ活用してみてください。