「GameManager」技術資料 - シングルトンによるグローバルなゲーム管理

以下は、GameManagerクラスに関する技術資料です。本資料では、Unity初心者にも理解しやすいよう、シングルトンパターンを活用したクラス設計や、コード例、ログ出力によるデバッグ手法、シーンを跨いだ状態管理のポイントなどを詳細に解説します。


シングルトンの基本

概要

GameManagerクラスは、Unityプロジェクトにおいて「グローバルなゲーム状態管理」を行うためのクラスであり、シングルトンパターンを採用することで、常に同一のインスタンスへアクセスできる仕組みを提供します。これにより、ゲーム中どのシーンからでも共通的なゲームロジックや状態に簡単にアクセスすることが可能となります。


主な特徴と実装ポイント

  1. シングルトンパターンの実装
    GameManagerはシングルトンパターンにより、グローバルアクセス可能な唯一無二のインスタンスを提供します。
    • static変数 _instance にインスタンスを保持
    • Instanceプロパティを介して、どのスクリプトからでも同じGameManagerにアクセス可能
    • Awake()メソッドで重複インスタンスを検出し、余分なインスタンスを破棄することで、複数存在することを防止
  2. DontDestroyOnLoadによるシーンまたぎの保持
    DontDestroyOnLoad(gameObject)を呼ぶことで、GameManagerはシーン切り替え時にも破棄されず、ゲーム全体を通じて存在し続けます。これにより、シーン間で共通状態を一貫して管理できます。
  3. インスタンスID(GetInstanceID)によるデバッグ支援
    GetInstanceID()によってインスタンスごとに異なるIDを取得でき、ログに出力することで、複数のGameManagerが生成・破棄される状況下でも、どのオブジェクトがいつ操作されたか明確に把握できます。これは、初学者が「なぜ2つ目のGameManagerが生まれたのか?」といったトラブルを理解・解消するのに役立ちます。
  4. nameofキーワードによる保守性向上
    nameof(GameManager)と記述することで、クラス名を直接文字列で書かずに済みます。クラス名変更時にもログ出力などが自動的に対応可能となり、保守性が向上します。
  5. エラーログでの明確なフィードバック
    Instanceアクセス時にインスタンスが存在しない場合はDebug.LogErrorで明確なエラーメッセージを表示します。これによって、なぜGameManagerが参照できないかを即座に把握でき、デバッグが容易になります。

シーン例

1つ目のGameManager

2つ目のGameManager

Player

コード例

以下は、上記のポイントを踏まえたGameManagerクラスのサンプルコードです。

using UnityEngine;

/// <summary>
/// ゲーム全体を通じてシーンを跨いで保持される、シングルトンによるゲーム管理クラス。
/// - シングルトンパターンにより、常に同一のインスタンスにアクセス可能
/// - DontDestroyOnLoadでシーン遷移時にも破棄されず、ゲーム全体を通して利用可能
/// </summary>
public class GameManager : MonoBehaviour
{
    // シングルトンインスタンスを保持するための静的フィールド
    private static GameManager _instance;

    /// <summary>
    /// シングルトンインスタンスへのグローバルアクセス用プロパティ。
    /// インスタンスが存在しない場合はエラーログを出力します。
    /// </summary>
    public static GameManager Instance
    {
        get
        {
            if (_instance == null)
            {
                Debug.LogError($"[{nameof(GameManager)}] インスタンスが存在しません。シーン内に{nameof(GameManager)}を持つオブジェクトを配置してください。");
                return null;
            }
            return _instance;
        }
    }

    /// <summary>
    /// AwakeはGameObject生成時に呼ばれ、シングルトンのインスタンス確立と重複排除を行います。
    /// </summary>
    private void Awake()
    {
        Debug.Log($"[{nameof(GameManager)}] {gameObject.name} (ID: {GetInstanceID()}) のAwakeが呼ばれました");

        // すでにインスタンスが存在して、かつそれが自分自身でない場合は重複として破棄
        if (_instance != null && _instance != this)
        {
            Debug.Log($"[{nameof(GameManager)}] 他のインスタンスが存在するため、このインスタンス(ID: {GetInstanceID()})は破棄されます: {gameObject.name}");
            Destroy(gameObject);
            return;
        }

        // このインスタンスをシングルトンとして登録
        _instance = this;
        Debug.Log($"[{nameof(GameManager)}] {gameObject.name} (ID: {GetInstanceID()}) をシングルトンインスタンスとして登録しました");

        // シーンを跨いでこのオブジェクトを保持
        DontDestroyOnLoad(gameObject);
        Debug.Log($"[{nameof(GameManager)}] {gameObject.name} (ID: {GetInstanceID()}) はDontDestroyOnLoadで保持されます");
    }

    /// <summary>
    /// プレイヤーが走る動きなど、ゲーム内処理の一例メソッド。
    /// </summary>
    public void Run()
    {
        Debug.Log($"[{nameof(GameManager)}] プレイヤーが走ります (呼び出し元: {gameObject.name}, ID: {GetInstanceID()})");
    }
}

テスト実行の様子

エディター実行後、スペースキーを押下してRunメソッドが実行されることを確認します


想定ユースケースと運用上の注意点

  • 最初のシーンでの配置
    ゲーム開始時のシーンにGameManagerを配置することで、直後からGameManager.Instanceへアクセス可能となり、状態管理を一元化できます。
  • 複数配置時の対処
    仮に誤って複数のGameManagerを設置しても、余分なインスタンスは自動的に破棄されます。ログ出力には破棄されたインスタンスのIDが表示されるため、原因究明が容易です。
  • Prefab化と再利用
    GameManagerをPrefabとして用意し、最初のシーンに一度だけ配置します。それ以降はシーン遷移時に新たなGameManagerを配置する必要はなく、同一インスタンスを再利用できます。

まとめ

本資料では、GameManagerクラスの技術的背景や設計方針、具体的な実装例を通じて、シングルトンパターンを用いたグローバルなゲーム管理手法を解説しました。これを理解することで、シーン間をまたぐ状態管理、共通ロジックへの簡易アクセス、ログ出力を用いたトラブルシューティングなど、Unityにおける開発の基礎を着実に身につけることができます。

クラス図 (Class Diagram)

GameManagerクラス内でのシングルトン実装構造を示すためのクラス図です。

説明:

  • - static GameManager _instance: プライベートな静的フィールド。
  • + static GameManager Instance { get }: パブリックな静的プロパティ。_instanceを返し、未初期化時はエラーをログに出します。
  • + void Awake(): Unityのライフサイクルメソッド。インスタンスが未登録であれば_instanceを設定し、他インスタンスがある場合は破棄します。
  • + void Run(): ゲーム内動作の例メソッド。

シーケンス図 (Sequence Diagram)

シーンロード時にAwake()が呼ばれ、GameManagerのシングルトンが確立される過程、そしてRun()メソッドが呼ばれるまでの流れを示します。

説明:

  • 最初のGameManager(A)がシーンに読み込まれるとAwake()が呼ばれ、_instanceがAに設定されます。
  • 2つ目のGameManager(B)が存在すると、そのAwake()は既に_instanceがAで埋まっていることを検知し、Bは自動的に破棄されます。
  • Unity側からGameManager.Instanceを参照すると、常にAが返ります。
  • GameManager.Run()を呼び出すと、Aインスタンスから実行され、ログが出力されます。

コンポーネント図 (Component Diagram) ※任意

シーン内に存在するGameManagerコンポーネントが、どのようにUnityエンジンと関わるかをシンプルに示すコンポーネント図の例です。

説明:

  • Unityエンジンはシーン内に配置されたGameManagerゲームオブジェクト上のGameManagerスクリプト(コンポーネント)を実行します。
  • GameManagerMonoBehaviourを継承しており、Unityのライフサイクル関数(AwakeStartUpdate等)をフックして動作します。
  • このコンポーネントを通じて、UnityがGameManagerにアクセスし、シングルトンロジックが機能します。

これらの図を通して、初学者は以下を理解しやすくなります。

  • クラス図:GameManagerがどのようなフィールドやプロパティを持ち、それがシングルトンパターンを実装するためのキーとなっているか。
  • シーケンス図:シーン読み込み時のAwake()呼び出し、重複インスタンスの破棄、Instanceプロパティ経由でのアクセス、Run()実行までの流れが時系列で可視化される。
  • コンポーネント図:UnityというフレームワークがGameManagerコンポーネントを介してシステムに組み込み、シングルトンオブジェクトを管理している構造を俯瞰できる。