Unityチュートリアル:DontDestroyOnLoadでプレイヤーをシーン間に保持する(責務分離対応)

2025年5月26日

目次

はじめに

Unity で複数のシーンをまたいで同じプレイヤーキャラクターを維持したい場合、DontDestroyOnLoad を活用する方法が一般的です。しかし、プレイヤーの生成、移動、削除のタイミングを適切に管理しないと、重複生成や位置のズレなどの問題が発生しがちです。

本チュートリアルでは、次のような構成をもとに、「プレイヤーの永続化」+「出現地点への自動移動」+「UI連携」を実現するシンプルかつ保守性の高い設計パターンを紹介します。

  • プレイヤーを1度だけ生成し、シーンを跨いで維持
  • 各シーンに配置した SpawnPoint へ自動的に移動
  • タイトルに戻った際のプレイヤーの明示的な破棄
  • TextMeshPro を使った現在のシーン名の表示
  • UI ボタンからのシーン遷移処理(イベント登録の方法も含む)

ゲーム制作において「責務分離」や「ライフサイクルの管理」が求められる中、こうした設計は中〜上級の開発者にとっても基本となります。初心者でも理解しやすく、後の拡張にも強い構成となっています。


対象者

  • Unity初心者〜中級者
  • プレイヤーを複数シーンで共通に使いたい方
  • スクリプトの責務分離や保守性にも関心のある方

プロジェクト構成例

Assets/
├── Prefabs/
│   ├── Player.prefab               ← プレイヤーのプレハブ(DontDestroy対象)
│   └── Canvas.prefab               ← 共通UI(ボタンやシーン名表示など)
├── Scenes/
│   ├── TitleScene.unity           ← 初回プレイヤー生成とスタートボタン
│   ├── StageSelectScene.unity     ← ステージ選択画面
│   └── GameScene.unity            ← ゲーム本編(SpawnPointにプレイヤー出現)
├── Scripts/
│   ├── PlayerController.cs         ← プレイヤーの永続化・シングルトン管理
│   ├── PlayerSpawnHandler.cs       ← シーン読み込み後の出現位置制御
│   ├── TitleSceneController.cs     ← スタートボタン処理とプレイヤー生成
│   ├── StageSelectController.cs    ← ステージ選択によるシーン遷移
│   └── SceneNameDisplay.cs         ← UIに現在のシーン名を表示(TextMeshPro対応)

シーン概要

シーン名内容
TitleSceneプレイヤーを初回生成、ステージ選択へ
StageSelectSceneステージ選択画面
GameSceneゲーム本編、出現地点にプレイヤー配置

承知しました。それでは「1. プレイヤーの作成と分離管理」セクションについて、他の項と文体・構成を統一した形に再整理します。


1. プレイヤーの作成と分離管理

1-1. PlayerController.cs(プレイヤーの永続化・シングルトン管理)

このスクリプトは、プレイヤーを1度だけ生成し、シーンをまたいで保持する責任を担います。

DontDestroyOnLoad により、ゲーム中のすべてのシーンで同一のプレイヤーオブジェクトを維持します。

using UnityEngine;

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

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject); // 複製を防ぐ
        }
    }
}

補足

  • Instance は静的プロパティとして1体だけを保証
  • すでに存在している場合は新規生成されたオブジェクトを即座に破棄
  • シーン間でプレイヤーを共有する構成の基本となるクラスです

1-2. PlayerSpawnHandler.cs(シーンロード後の出現位置制御)

このスクリプトは、シーンがロードされたタイミングで、プレイヤーを SpawnPoint の位置に自動で移動させる役割を担います。

出現処理の責任を PlayerController から分離することで、コードの保守性と再利用性を高めます。

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;

public class PlayerSpawnHandler : MonoBehaviour
{
    void OnEnable()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    void OnDisable()
    {
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        StartCoroutine(SetPlayerPositionWithDelay());
    }

    IEnumerator SetPlayerPositionWithDelay()
    {
        yield return null; // オブジェクトがすべて初期化された後に実行

        if (PlayerController.Instance == null) yield break;

        var spawn = GameObject.FindWithTag("SpawnPoint");
        if (spawn != null)
        {
            PlayerController.Instance.transform.position = spawn.transform.position;
        }
        else
        {
            Debug.LogWarning("SpawnPointが見つかりません。タグが正しく設定されているか確認してください。");
        }
    }
}

使用手順(Unityエディタ)

  • 各ゲーム用シーンに 空の GameObject(例:PlayerManager や SceneInitializer) を作成し、PlayerSpawnHandler スクリプトをアタッチします
  • シーン内に、Tag を SpawnPoint に設定した Empty GameObject(例:SpawnPoint) をプレイヤー出現位置として配置します

補足

  • yield return null により、シーン内オブジェクトの初期化完了後に移動処理を行う
  • プレイヤーに直接アタッチしても動作しますが、責務の分離の観点から別 GameObject に配置するのが望ましいです

2. プレイヤープレハブの作成

  1. Hierarchy に Player を作成(3Dなら Capsule、2Dなら Sprite)
  2. Tag を “Player" に設定
  3. Rigidbody, Collider などを必要に応じて追加
  4. PlayerController.cs をアタッチ
  5. Prefab化して Assets/Prefabs/Player.prefab に保存

3. 出現地点(SpawnPoint)の配置

  • 各ゲーム用シーン(例:GameScene)に Empty オブジェクトを配置
  • 名前:任意(例:SpawnPoint)
  • Tag:SpawnPoint

4. TitleSceneの設定

UI構成(スタートボタンの配置)

  1. Canvas 上に Button を作成します
  2. Text を「Show Stage Select」など、スタートを連想させる文言に変更します
  3. Button の OnClick() イベントに以下の関数を設定します:
TitleSceneController.OnStartGame()

スクリプト:TitleSceneController.cs(プレイヤー生成とシーン遷移)

このスクリプトは、ゲーム開始ボタンを押したときにプレイヤーを生成し、次のシーン(ステージ選択)へ遷移する役割を担います。

using UnityEngine;
using UnityEngine.SceneManagement;

public class TitleSceneController : MonoBehaviour
{
    public GameObject playerPrefab;

    public void OnStartGame()
    {
        if (PlayerController.Instance == null)
        {
            Instantiate(playerPrefab);
        }

        SceneManager.LoadScene("StageSelectScene");
    }
}

使用手順(Unityエディタ)

  • TitleSceneController を空の GameObject にアタッチします
  • playerPrefab に、事前に作成したプレイヤーのプレハブをドラッグ&ドロップで割り当てます
  • ボタンの OnClick() に OnStartGame() を設定します

補足

  • プレイヤーは PlayerController により DontDestroyOnLoad でシーンをまたいで維持されます
  • すでに存在する場合は生成されず、1体だけが維持される構成です
  • 複数シーンで同じプレイヤーを共有したいゲーム構成に適しています

Unityのコード上でButtonのOnClickイベントを設定することも可能です。TitleSceneController で、Start()や Awake() のタイミングで Button コンポーネントを取得し、onClick にリスナーとしてメソッドを追加する形になります。

以下にその実装例を示します。


✅ TitleSceneController.cs(イベント登録をコードで設定)

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class TitleSceneController : MonoBehaviour
{
    public GameObject playerPrefab;
    public Button showStageSelectButton; // インスペクターでアサインする

    void Awake()
    {
        if (showStageSelectButton != null)
        {
            showStageSelectButton.onClick.AddListener(OnStartGame);
        }
        else
        {
            Debug.LogWarning("showStageSelectButton がアサインされていません。");
        }
    }

    public void OnStartGame()
    {
        if (PlayerController.Instance == null)
        {
            Instantiate(playerPrefab);
        }

        SceneManager.LoadScene("StageSelectScene");
    }
}

補足設定(Unityエディタ上)

  1. Canvas > Button を作成
  2. Textを “Show Stage Select” に変更
  3. 作成したボタンを TitleSceneController の showStageSelectButton にドラッグしてアサイン
  4. playerPrefab も忘れずにアサイン

備考

  • これにより、OnClick登録が完全にコードで行われるので、シーン内のUI設定ミスを防ぎやすくなります。
  • 複数のボタンを同様に管理する場合は、Dictionary<string, Button> などを使って柔軟な処理も可能です。

5. ステージ選択画面の設定

スクリプト:StageSelectController.cs

このスクリプトは、ボタンの OnClick() イベントからシーン名を受け取り、指定されたシーンへ遷移する役割を担います。とてもシンプルで拡張性の高い構成です。

using UnityEngine;
using UnityEngine.SceneManagement;

public class StageSelectController : MonoBehaviour
{
    // 任意のシーン名を受け取ってロード
    public void OnStageSelected(string sceneName)
    {
        SceneManager.LoadScene(sceneName);
    }
}

使用手順(Unityエディタ)

  1. ステージ選択用の UI ボタンを作成
  2. 各ボタンの OnClick() イベントに StageSelectController.OnStageSelected(string) を登録
  3. 引数として、遷移させたいシーン名を入力(例:GameScene, Stage1 など)

注意点

  • 入力するシーン名は、必ず Build Settings に登録されている名前と完全一致させてください
  • シーンの追加忘れや綴りミスがあると、SceneManager.LoadScene によるロード時にエラーになります

 メリット

  • 異なるステージに対応するシーンを、コードの変更なしでボタン側から自由に指定可能
  • シーン構成が増えてもスクリプトを修正せずに対応でき、拡張性・保守性に優れています

6. シーン名表示(TextMeshPro対応)

スクリプト:SceneNameDisplay.cs

このスクリプトは、現在アクティブなシーンの名前を UI 上に自動表示する機能を提供します。TextMeshProUGUI コンポーネントが付いた Text オブジェクトにアタッチするだけで動作します。

using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;

public class SceneNameDisplay : MonoBehaviour
{
    void Start()
    {
        if (TryGetComponent<TextMeshProUGUI>(out var text))
        {
            text.text = SceneManager.GetActiveScene().name;
        }
    }
}

 使用手順

  1. Canvas 内に Text (TextMeshPro) を作成します
  2. 上記スクリプト SceneNameDisplay.cs を Text オブジェクトにアタッチします
  3. ゲームを実行すると、そのシーンの名前(例:GameScene や TitleScene)が自動で表示されます

補足

  • 表示される名前は Build Settings に登録されたシーンの .unity ファイル名です
  • 開発中のデバッグ用途や画面識別、UIの仮表示などに便利です

7. タイトルに戻る(オプション)

スクリプト例:プレイヤーを破棄してタイトルに戻る

この関数は、ゲーム中に「タイトルに戻る」操作を行う際、既存のプレイヤーオブジェクトを明示的に削除して初期状態に戻す役割を果たします。

PlayerController が DontDestroyOnLoad によってシーンを跨いで維持されているため、戻る前に破棄処理が必要です。

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameSceneController : MonoBehaviour
{
    public void OnReturnToTitle()
    {
        if (PlayerController.Instance != null)
        {
            Destroy(PlayerController.Instance.gameObject);
        }

        SceneManager.LoadScene("TitleScene");
    }
}

使用手順(Unityエディタ)

  • タイトルに戻るボタンを用意し、OnClick() に GameSceneController.OnReturnToTitle() を設定します
  • このスクリプトを、ゲーム中の適当な空の GameObject にアタッチしておきます

補足

  • DontDestroyOnLoad によって維持されたプレイヤーが新しく再生成されるためには、このように手動で削除する処理が必要になります
  • この構成により、ゲームの最初からやり直したいときの状態リセットが実現できます
  • この処理は、ゲーム内に「タイトルへ戻る」や「再起動」のような選択肢を設けたい場合に有効で、明示的なライフサイクル管理ができることから中〜上級設計にも適しています
  • 必要に応じて「BGMの停止」や「UIリセット」などの処理も併用可能です

8. Build Settingsの確認

File > Build Settings にて次のシーンを登録:

  • Scenes/TitleScene
  • Scenes/StageSelectScene
  • Scenes/GameScene

順番は任意ですが、必ず登録が必要です。


チェックリスト

  • プレイヤーは TitleScene で1度だけ生成される
  • シーン間でプレイヤーが維持される(DontDestroyOnLoad)
  • 各シーンで SpawnPoint に移動できる
  • シーン名が UI に表示される
  • タイトルに戻ると再生成される

発展アイデア

内容概要
ステータス管理HPやアイテムなどをPlayerControllerに追加し、維持する
カメラ制御Cinemachine導入で追従・境界制御など
ステージ番号の受け渡しScriptableObjectや静的変数を使用
UIManagerの導入UI制御を一元管理して再利用性アップ

まとめ

このチュートリアルでは:

  • プレイヤーを DontDestroyOnLoad で維持
  • 出現地点をシーンごとに配置し、自動で移動
  • シーン名を自動表示
  • 責務分離で保守しやすいコード構造を実現

初心者でも取り組みやすく、今後のプロジェクト設計にも応用できる構成です。

通常この構成で問題ありません。SceneManager.sceneLoaded イベントは、シーンのロード完了後(≒すべてのGameObjectが生成された後)に呼ばれるため、GameObject.FindWithTag(“SpawnPoint") や PlayerController.Instance が有効になるタイミングとしては適切です。


安全な理由(仕組み)

  • SceneManager.sceneLoaded は 非同期ロードが完了した後に発火されるイベントです。
  • この時点で、HierarchyにあるGameObjectはすべて生成済みなので、
    • GameObject.FindWithTag(“SpawnPoint") は正しく取得可能
    • PlayerController.Instance が DontDestroyOnLoad オブジェクトなら正しく参照可能

注意点

ただし、以下のようなケースでは注意が必要です:

1. SpawnPointの生成が遅延している

  • スクリプトの Start() や Awake() 内で tag を設定している場合、FindWithTag に間に合わない可能性があります。
    • → 対処策:SpawnPoint オブジェクトは必ずシーン内でプレハブとして配置・タグ付けしておく

2. PlayerController.Instance の生成が遅い

  • TitleScene → StageSelectScene → GameScene と複数のシーンを挟んでいる場合、PlayerController がロード前に破棄されていると null になります。
    • → DontDestroyOnLoad とシングルトンのチェックが正しく機能しているか確認

改善例(遅延対策)

PlayerController.Instance が存在するまで少し待つ、という保険をかける場合は以下のようなCoroutineを使う方法もあります:

void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    StartCoroutine(WaitAndWarp());
}

IEnumerator WaitAndWarp()
{
    yield return null; // 1フレーム待つ(すべての Start() 後)

    if (PlayerController.Instance == null) yield break;

    var spawn = GameObject.FindWithTag("SpawnPoint");
    if (spawn != null)
    {
        PlayerController.Instance.transform.position = spawn.transform.position;
    }
}

結論

  • 今のコードは 通常のUnityのシーン読み込みフローでは安全です。
  • ただし、生成タイミングのズレや複雑なシーン構成があるなら、保険として Coroutine を使った1フレーム待機がより堅牢になります。

発展アイデア:GameSceneデバッグ時のプレイヤー自動生成

スクリプト:PlayerBootstrapper.cs

このスクリプトは、GameScene から単体で再生したときに、プレイヤーが存在しない場合に自動生成する補助機能です。デバッグ時の利便性を高める目的で使用します。

using UnityEngine;

public class PlayerBootstrapper : MonoBehaviour
{
    public GameObject playerPrefab;

    void Awake()
    {
        if (PlayerController.Instance == null)
        {
            Instantiate(playerPrefab);
        }
    }
}

使用手順(Unityエディタ)

  1. GameScene に空の GameObject(例:PlayerBootstrapper)を作成
  2. 上記スクリプトをアタッチ
  3. playerPrefab に、事前に作成した Player プレハブを割り当て

補足

  • 通常のゲーム起動(TitleScene → GameScene)ではプレイヤーがすでに存在しているため、このスクリプトは何もしません
  • GameScene を単体でテスト再生したときだけ playerPrefab を自動生成します
  • DontDestroyOnLoad との併用により、複製も防げる設計です

おすすめの配置

このスクリプトは、Scene専用・シンプルな責務を持つ補助スクリプトなので、「Scripts/Utility/」などのフォルダに置くとプロジェクト構成が整理されます。

訪問数 17 回, 今日の訪問数 1回

Unity

Posted by hidepon