【Unity】汎用性のあるシングルトンパターン

2024年4月17日

Unityでシングルトンを使う手順を示します

シングルトンパターンは、特定のクラスのインスタンスが一つしか存在しないことを保証し、グローバルなアクセスポイントを提供します。これにより、リソースの共有やアクセスが一元管理され、メモリ使用の効率化が図られます。設定管理やデータベース接続など、アプリケーション全体で一貫した状態を維持するのに適しています。

シングルトンのクラスが、MonoBehaviourを継承していない(MonoBehaviourクラス)の場合

MonoBehaviourを継承していないクラスをシングルトンとして実装する場合、ゲームオブジェクトにアタッチすることはできません。Projectウィンドウ内に留めておくことになります

以下に、MonoBehaviourを継承していないクラスでのシングルトン実装のサンプルコードを示します。

using UnityEngine;

public class SingletonManager
{
    // MySingletonクラスの唯一のインスタンスを保持する静的フィールドです。
    private static SingletonManager instance;

    // コンストラクタはprivateです。これにより、このクラスの外部からインスタンス化されることは防がれます。
    private SingletonManager()
    {
        // コンストラクタをprivateにして外部からのインスタンス化を防ぎます
    }

    // インスタンスにアクセスするためのプロパティです。インスタンスがまだ作成されていない場合は新たに作成します。
    public static SingletonManager Instance
    {
        get
        {
            // インスタンスがまだ存在しない場合、新しいインスタンスを作成します。
            if (instance == null)
            {
                instance = new SingletonManager();
            }
            // 既存のインスタンスを返します。
            return instance;
        }
    }

    // クラスの具体的な機能を実行するメソッドです。
    public void DoSomething()
    {
        // ログにメッセージを出力
        Debug.Log("SingletonManagerで何かを実行します。");
    }
}

先ほど述べたように、このクラスはMonoBehaviourを継承していないため、ゲームオブジェクトにアタッチすることはできません。代わりに、このクラスの唯一のインスタンスにアクセスするためにInstance プロパティを使用します。

このようにして、MonoBehaviourクラスとしてのシングルトンを実装し、必要なときにアクセスできるようになります。他のクラスからは、MySingleton.Instance を使用してこのシングルトンのインスタンスにアクセスできます。

使い方

前の例で作成したMySingletonクラスを使用して、シングルトンのインスタンスを取得し、そのインスタンスにアクセスする方法を以下に示します。

MySingleton クラスのインスタンスを取得します。

SingletonManager singleton = SingletonManager.Instance;

このコードにより、MySingleton クラスの唯一のインスタンスが取得されます

シングルトンのインスタンスを使用して、メソッドやプロパティにアクセスします

singleton.DoSomething(); // シングルトンのメソッドを呼び出す

これにより、MySingleton クラスの唯一のインスタンスが持つメソッド DoSomething() が呼び出されます

全体コード

using UnityEngine;

public class GameManager : MonoBehaviour
{
    private void Start()
    {
        // SingletonManagerクラスの唯一のインスタンスを取得
        SingletonManager singleton = SingletonManager.Instance;

        // シングルトンのメソッドを呼び出す
        singleton.DoSomething();
    }
}

呼び出しを1行にまとめると

using UnityEngine;

public class GameManager : MonoBehaviour
{
    private void Start()
    {
        // SingletonManagerのインスタンスを取得し、DoSomethingメソッドを実行します。
        SingletonManager.Instance.DoSomething();
    }
}

この呼び出し方で、MonoBehaviourクラスとして実装されたシングルトンを使用できます。このシングルトンパターンを使うことで、アプリケーション内の異なる部分から唯一のインスタンスにアクセスでき、共有のデータや機能を効果的に管理できます

他のゲームオブジェクトにアタッチして試してみましょう

MonoBehaviourを継承している場合(シーン上のゲームオブジェクトにアタッチするスクリプト)

先ほどと違い、シングルトンはMonoBehaviourを継承しているため、ゲームオブジェクトにアタッチすることができます

using UnityEngine;

// MonoBehaviourを継承したSingletonManagerクラスを定義
public class MonoBehaviourSingletonManager : MonoBehaviour
{
    // SingletonManagerのインスタンスを保持する静的フィールド
    private static MonoBehaviourSingletonManager instance;

    // ゲームオブジェクトが起動時に呼ばれるメソッド
    private void Awake()
    {
        // インスタンスが未設定の場合の処理
        if (instance == null)
        {
            // このクラスのインスタンスを設定
            instance = this;
            // シーンが切り替わってもオブジェクトが破棄されないように設定
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // すでにインスタンスが存在する場合、このオブジェクトを破棄
            Destroy(gameObject);
        }
    }

    // インスタンスにアクセスするためのプロパティ
    public static MonoBehaviourSingletonManager Instance
    {
        // インスタンスを返すゲッター
        get { return instance; }
    }

    // 何か処理を行うメソッド
    public void DoSomething()
    {
        // ログにメッセージを出力
        Debug.Log("MonoBehaviourSingletonManagerで何かを実行します。");
    }
}

DontDestroyOnLoad(gameObject); は、シーンの変更があったときに特定のゲームオブジェクトが破棄されないように設定するために使います。通常、Unityでは新しいシーンに移行すると、前のシーンに存在していたゲームオブジェクトは全て破棄されます。しかし、DontDestroyOnLoad メソッドを使用することで、ゲームオブジェクトを新しいシーンへと「持ち越す」ことができます。

ここでの gameObject は、このメソッドが呼ばれたスクリプトがアタッチされているゲームオブジェクトを指します。このメソッドを呼び出すことで、そのゲームオブジェクトはシーンが変わっても破棄されずに残り続けます。これは、ゲーム全体を通じて存在し続けるべき機能やデータを持つオブジェクト、例えば音楽プレイヤーやゲームマネージャーなどによく使用されます。

コード中の Destroy(gameObject); が必要な理由は、シングルトンパターンの基本原則に関連しています。シングルトンパターンでは、クラスのインスタンスが1つだけ存在することを保証する必要があります。Awake() メソッド内でこの制御を行います。

  1. インスタンスの重複を防ぐ: Awake() メソッドが呼ばれたとき、すでに instance 変数にインスタンスが設定されているかどうかをチェックします。もし instancenull でなければ、それはシーン内に別のインスタンスが既に存在することを意味します。
  2. シーン間での持続性: 最初に作成されたインスタンスは DontDestroyOnLoad() を使用してシーンが変わっても破棄されないように設定されます。これにより、そのインスタンスがアプリケーション全体で持続します。
  3. 不要なインスタンスの破棄: 既に instance が設定されている場合(つまり、シングルトンとして機能しているインスタンスが既に存在する場合)、新しく作成されたインスタンスは不要です。これが Destroy(gameObject); が必要な理由です。この呼び出しにより、シーンに余分なインスタンスが残らず、リソースの無駄遣いを防ぐことができます。

このように、Destroy(gameObject); はシングルトンパターンがそのルールを正しく守るために重要な役割を果たします。余分なインスタンスが生成された場合にこれを適切に処理し、システム全体の整合性を保ちます。

設計上、シーンにこのスクリプトが1つだけアタッチされていれば、本来不要ともいえます

呼び出し方は、MonoBehaviourを継承していない時と同じになります

MonoBehaviourSingletonManager.Instance.DoSomething();

シングルトンマネージャを使って、ゲーム内のさまざまな場所から共有のデータや機能にアクセスできます

参考)シーン構成

ゲームオブジェクトとコンポーネント

実行中

ヒエラルキーウィンドウに特徴があります
現在のシーンとは別にDontDestroyOnLoadシーンが作成されているのがわかります
そして、そのシーンには、MonoBehaviourSingletonManagerスクリプトがアタッチされているのがわかります
これにより、現在のシーンが入れ替わってもこれが残ることがわかります

これを利用して、シーンが切り替わってもBGMが途切れず流すことができたりします

コード

using UnityEngine;

public class GameManager : MonoBehaviour
{
    // Startメソッドは、このオブジェクトがシーンにロードされたときに一度だけ呼び出されます。
    void Start()
    {
        // MonoBehaviourSingletonManagerのインスタンスを取得し、DoSomethingメソッドを実行します。
        MonoBehaviourSingletonManager.Instance.DoSomething();

        // SingletonManagerのインスタンスを取得し、DoSomethingメソッドを実行します。
        SingletonManager.Instance.DoSomething();
    }
}
using UnityEngine;

// MonoBehaviourを継承したSingletonManagerクラスを定義
public class MonoBehaviourSingletonManager : MonoBehaviour
{
    // SingletonManagerのインスタンスを保持する静的フィールド
    private static MonoBehaviourSingletonManager instance;

    // ゲームオブジェクトが起動時に呼ばれるメソッド
    private void Awake()
    {
        // インスタンスが未設定の場合の処理
        if (instance == null)
        {
            // このクラスのインスタンスを設定
            instance = this;
            // シーンが切り替わってもオブジェクトが破棄されないように設定
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // すでにインスタンスが存在する場合、このオブジェクトを破棄
            Destroy(gameObject);
        }
    }

    // インスタンスにアクセスするためのプロパティ
    public static MonoBehaviourSingletonManager Instance
    {
        // インスタンスを返すゲッター
        get { return instance; }
    }

    // 何か処理を行うメソッド
    public void DoSomething()
    {
        // ログにメッセージを出力
        Debug.Log("MonoBehaviourSingletonManagerで何かを実行します。");
    }
}
using UnityEngine;

// MonoBehaviourを継承しないSingletonManagerクラスを定義
public class SingletonManager
{
    // MySingletonクラスの唯一のインスタンスを保持する静的フィールドです。
    private static SingletonManager instance;

    // コンストラクタはprivateです。これにより、このクラスの外部からインスタンス化されることは防がれます。
    private SingletonManager()
    {
        // コンストラクタをprivateにして外部からのインスタンス化を防ぎます
    }

    // インスタンスにアクセスするためのプロパティです。インスタンスがまだ作成されていない場合は新たに作成します。
    public static SingletonManager Instance
    {
        get
        {
            // インスタンスがまだ存在しない場合、新しいインスタンスを作成します。
            if (instance == null)
            {
                instance = new SingletonManager();
            }
            // 既存のインスタンスを返します。
            return instance;
        }
    }

    // クラスの具体的な機能を実行するメソッドです。
    public void DoSomething()
    {
        // ログにメッセージを出力
        Debug.Log("SingletonManagerで何かを実行します。");
    }
}

参考

使い分け

MonoBehaviour を継承しているシングルトンと継承していないシングルトンの使い分けは、Unity ゲーム開発においてとても重要です。それぞれの用途と特性を理解することで、効率的で効果的なゲーム設計が可能になります。

MonoBehaviour を継承するシングルトン

MonoBehaviour を継承するシングルトンは、Unity のシーン内で活動するゲームオブジェクトにアタッチする形で使用します。このタイプのシングルトンは、以下の特徴と利点があります:

  • シーンの要素として機能: ゲームオブジェクトにアタッチされているため、Unity エディタのインスペクターを通じてパラメータを設定したり、シーン内の他のオブジェクトとの相互作用が容易です。
  • Unity のライフサイクルイベントを利用: Update(), Start(), Awake() などのライフサイクルメソッドを直接使用できるため、フレームごとの更新処理や初期化処理がシンプルに実装できます。
  • シーン間でのデータ持続: DontDestroyOnLoad() メソッドを使用して、シーン遷移時にも破棄されないように設定することができます。

用途の例: ゲーム全体で一つだけ存在するマネージャー(例:ゲームマネージャー、サウンドマネージャー、UIマネージャー)。

MonoBehaviour を継承しないシングルトン

MonoBehaviour を継承しないシングルトンは、主に非UI関連の背後で動作するサービスや、データ管理用のクラスに使用されます。このタイプのシングルトンは以下の特徴を持ちます:

  • シーンに依存しない: シーンやゲームオブジェクトに依存せず、純粋にコード上で存在します。Unity エディタのインスペクターで設定することはできません。
  • メモリとリソースの効率的な管理: シーンのロードやアンロードの影響を受けず、アプリケーションのライフサイクルに沿って存在するため、メモリやリソースの管理が容易です。
  • 非UI関連のデータやロジックの管理に適している: ネットワーク処理、データベース管理、設定やプリファレンスの保存など、UIと直接関連しない機能を担います。

用途の例: 設定ファイルの読み込み、データベースアクセス、ネットワーク通信。

使い分けのポイント

  • シーンやゲームオブジェクトとの相互作用が必要な場合や、Unity のライフサイクルに基づく処理が多い場合は MonoBehaviour を継承するシングルトンを使用します。
  • バックグラウンドでのデータ処理や、UIとは無関係のサービスを提供する場合は MonoBehaviour を継承しないシングルトンを選択します。

これらの選択を適切に行うことで、ゲームのアーキテクチャがよりクリアになり、メンテナンスと拡張が容易になります。