【具体例付き】UnityでのSingleton(シングルトン)利用方法【初級者】

Singleton とは何か?

Wiki にはこう書かれています。

Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。ロケールやルック・アンド・フィールなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される。

https://ja.wikipedia.org/wiki/Singleton_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

ロケールやルック・アンド・フィールが何かは知りませんが、Unity で Singleton デザインパターンを利用するシナリオとしては、特に Scene を跨いで状態を管理する場合でしょう。

ゲーム全体を管理する GameManager や BGM 等を管理する AudioManager 等のインスタンスを作成しているなら、それが該当するかと思います。

活用用途を詳細に知りたい方は、以下の記事が参考になると思います。

ソフトライム

シングルトンは、特定のオブジェクトがアプリケーション内で一意であることを確保し、コードの整理や効率的なリソース管理に役立…

Singleton 活用の具体的なイメージ

Wiki には「そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターン」とか書いてありましたけど、これだけでは過去の私はイメージが掴めませんでした。

ですので、Singleton 活用の具体的なイメージを見ていきましょう。

皆さんは DontDestroyOnLoad という単語を聞いたことがありますでしょうか?

Unity には Scene 切り替え時に static ではない値を破棄するという仕様があるのですが、この機能を使用すると Scene 切り替えが行われても値が破棄されず、スクリプトをアタッチしたゲームオブジェクトも残り続けます。

ではここで、以下の2つの Scene があると仮定します。

・Title Scene
・Game Play Scene

上記 2 つでは、Scene の切り替えが発生した場合でも BGM を途切れず再生し続けたいです。

ここで使用するのが、値が破棄されなくなる DontDestroyOnLoad となります。
この機能、大変便利ではありますが、そのまま使用するのでは次のような欠点が……

シーンを切り替えるとゲームオブジェクトは新しく生成されますが、DontDestroyOnLoad がついたゲームオブジェクトは削除されなくなります。

つまり、シーンを切り替える度に DontDestroyOnLoad がついたゲームオブジェクトが増え続けてしまうため、下記のような地獄が形成されます。

Q. ではどうするべきか
同一のゲームオブジェクトが複数存在した場合、新しく生成されたものは削除して常にゲームオブジェクトは 1 つにしよう!って感じです。

そしてこの「同一のゲームオブジェクトは常に 1 つだけにする」という考え方が、Wiki にも書かれている Singleton の考え方になります。

Singleton を使用する

以下のものが Singleton コードになります。

using UnityEngine;

public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    // シーンを跨いで値を保持するか
    protected abstract bool DontDestroy { get; }

    private static T instance;
    public  static T I
    {
        get
        {
            // 値が参照されたタイミングで判定
            if (instance == null)
            {
                // nullだった場合は全オブジェクトを探索
                // 名前が一致するクラスがあった場合は取得する
                instance = FindObjectOfType<T>();

                // 名前が一致するものがなかった場合
                if (instance == null)
                {
                    // コンソールウィンドウにエラーを出力
                    Debug.LogError($"{typeof(T)}のインスタンスが存在しません。");
                }
            }
            return instance;
        }
    }

    // 継承先でもAwakeを呼び出したい場合は,overrideする
    protected virtual void Awake()
    {
        // 既に同一名のクラスが存在していた場合
        if (I != this)
        {
            // ゲームオブジェクトごと削除
            Destroy(gameObject);
            return;
        }

        if (DontDestroy)
        {
            DontDestroyOnLoad(gameObject);
        }
    }
}

Singleton 活用例

① クラスを作成する

今回はゲーム全体を管理するクラスとして GameManager というクラスを作成します。

public class GameManager : Singleton<GameManager>
{
    // シーンを引き継いで値を保持する
    protected override bool DontDestroy => true;

    // 保持したい値,または,複数のクラスから参照したい値
    public int num = 999;
}

② 別クラスから参照する

適当に値を参照したいクラスを作成します。

public class Player : Monobehaviour
{
    private int hoge;

    void Start()
    {
        hoge = GameManager.I.num;
    }
}

値はプロパティを通して取得ができます。

Singleton 活用の注意点

Singleton は大変便利ですが、クラス間の結合が強くなるというデメリットもあります。
ですので、以下の要素に注意してください。

・少数のクラスからしか参照されないものには Singleton は使わない。
・参照されるものの数が少ない場合、Singleton ではなく static を使用する。

SNS もやってるよ!