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 を使用する。