Unityチュートリアル:DontDestroyOnLoadでプレイヤーをシーン間に保持する(責務分離対応)
対象者
- Unity初心者〜中級者
- プレイヤーを複数シーンで共通に使いたい方
- スクリプトの責務分離や保守性にも関心のある方
プロジェクト構成例
Assets/
├── Prefabs/
│ ├── Player.prefab
│ └── Canvas.prefab(共通UI)
├── Scenes/
│ ├── TitleScene.unity
│ ├── StageSelectScene.unity
│ └── GameScene.unity
├── Scripts/
│ ├── PlayerController.cs ← プレイヤーの永続化のみ
│ ├── PlayerSpawnHandler.cs ← 出現位置移動の処理
│ ├── TitleSceneController.cs
│ ├── StageSelectController.cs
│ └── SceneNameDisplay.cs
シーン概要
シーン名 | 内容 |
---|---|
TitleScene | プレイヤーを初回生成、ステージ選択へ |
StageSelectScene | ステージ選択画面 |
GameScene | ゲーム本編、出現地点にプレイヤー配置 |
1. プレイヤーの作成と分離管理
1-1. PlayerController.cs(永続化・Singleton)
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public static PlayerController Instance;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
このスクリプトは 「プレイヤーを一度だけ生成し、以降のシーン間で維持」 する責任のみを持ちます。
1-2. PlayerSpawnHandler.cs(出現位置制御)
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerSpawnHandler : MonoBehaviour
{
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (PlayerController.Instance == null) return;
var spawn = GameObject.FindWithTag("SpawnPoint");
if (spawn != null)
{
PlayerController.Instance.transform.position = spawn.transform.position;
}
}
}
GameManager や空オブジェクトにアタッチするだけで機能します。
Player に直接アタッチしても問題ありません(初学者向けにはこの方が扱いやすい)。
2. プレイヤープレハブの作成
- Hierarchy に Player を作成(3Dなら Capsule、2Dなら Sprite)
- Tag を “Player" に設定
- Rigidbody, Collider などを必要に応じて追加
- PlayerController.cs をアタッチ
- Prefab化して Assets/Prefabs/Player.prefab に保存
3. 出現地点(SpawnPoint)の配置
- 各ゲーム用シーン(例:GameScene)に Empty オブジェクトを配置
- 名前:任意(例:SpawnPoint)
- Tag:SpawnPoint
4. TitleSceneの設定
ボタンのUI設定(例:Show Stage Select)
- Canvas上に Button を配置し、Textは Show Stage Select
- 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");
}
}
インスペクタから playerPrefab にプレイヤーのプレハブを割り当てます。
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エディタ上)
- Canvas > Button を作成
- Textを “Show Stage Select” に変更
- 作成したボタンを TitleSceneController の showStageSelectButton にドラッグしてアサイン
- playerPrefab も忘れずにアサイン
備考
- これにより、OnClick登録が完全にコードで行われるので、シーン内のUI設定ミスを防ぎやすくなります。
- 複数のボタンを同様に管理する場合は、Dictionary<string, Button> などを使って柔軟な処理も可能です。
5. ステージ選択画面の設定
スクリプト:StageSelectController.cs
using UnityEngine;
using UnityEngine.SceneManagement;
public class StageSelectController : MonoBehaviour
{
public void OnStageSelected(int stageNumber)
{
SceneManager.LoadScene("GameScene");
}
}
ステージごとに異なるシーンをロードする構成への拡張も可能です。
6. シーン名表示(TextMeshPro対応)
スクリプト:SceneNameDisplay.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
public class SceneNameDisplay : MonoBehaviour
{
void Start()
{
if (TryGetComponent<TextMeshProUGUI>(out var text))
{
text.text = SceneManager.GetActiveScene().name;
}
}
}
- Canvas に Text(TMP) を配置してアタッチするだけで、各シーンの名前が自動表示されます。
7. タイトルに戻る(オプション)
public void OnReturnToTitle()
{
if (PlayerController.Instance != null)
{
Destroy(PlayerController.Instance.gameObject);
}
SceneManager.LoadScene("TitleScene");
}
プレイヤーを明示的に破棄してから再起動する設計です。
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フレーム待機がより堅牢になります。
ディスカッション
コメント一覧
まだ、コメントがありません