そもそもシングルトンとは?
Wikiにはこう書かれています。
Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。
しかし,これだけでは過去の私はいまいちイメージが掴めませんでした。
なので私が学習を進めるうえで思いついたよさげな例えで説明します。
要らない人は次の項まで飛んでください。
・ ・ ・ ・ ・ ・
皆さんはDontDestroyOnLoadという単語を聞いたことがありますでしょうか?
UnityにはScene切り替え時にstaticではない値を破棄するという仕様があるのですが,
このDontDestroyOnLoadという機能を使うとScene切り替えが行われても値が破棄されず,
スクリプトをアタッチしたゲームオブジェクトも残り続けます。
ではここで,以下の2つのシーンがあると想定しましょう。
タイトルシーン
ゲームシーン
そしてこの2つのシーンでは,シーン切り替えが行われたとしても音楽を途切れず再生し続けたいです。
ここで使うのが値が破棄されなくなるDontDestroyOnLoadです。
しかしこれも,そのまま使うのでは欠点が……
シーンを切り替えるとゲームオブジェクトは新しく生成されますが,
DontDestroyOnLoadがついたゲームオブジェクトは削除されなくなります。
つまり,シーンを切り替える度にDontDestroyOnLoadがついたゲームオブジェクトが増え続けてしまうんです。
それが繰り返されると下記のような地獄が形成されます。
パッと見でダメなので,これを解決したいです。
Q. ではどうするか
同一のゲームオブジェクトが複数存在した場合,新しく生成されたものは削除して常にゲームオブジェクトは1つにしよう!って感じです。
そしてこの「同一のゲームオブジェクトは常に1つだけにする」という考え方が,Wiki様にも書かれているシングルトンの考え方になります。
では実際に使ってみます。
シングルトンを使う
下記が私が作成したシングルトンのコードになります。
シングルトンの書き方は人によって十人十色なので,興味がある人は他の方が作成したものについても調べてみてください。
C#
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;
}
}
void Awake()
{
// 既に同一名のクラスが存在していた場合
if (I != this)
{
// ゲームオブジェクトごと削除
Destroy(gameObject);
return;
}
if (DontDestroy)
{
DontDestroyOnLoad(gameObject);
}
// 継承先でAwakeが使えなくなるので代用
Init();
}
/// <summary>
/// 派生先用のAwake
/// </summary>
protected virtual void Init() { }
}
①クラスを作成する
今回はゲーム全体を管理するクラスとしてGameManagerというクラスを作成します。
C#
public class GameManager : Singleton<GameManager>
{
// シーンを引き継いで値を保持する
protected override bool DontDestroy => true;
// 保持したい値,または,複数のクラスから参照したい値
public int num = 999;
}
継承元のSingletonクラスはジェネリックを使用しているので,
複数のクラスにも簡単にシングルトンの処理を施すことができます。
②別クラスから参照する
適当に値を参照したいクラスを作成します。
C#
public class Player : Monobehaviour
{
private int hoge;
void Start()
{
hoge = GameManager.I.num;
}
}
値はプロパティを通して取得ができます。
値を参照したいクラス.I.変数名という簡単な記述ですね。
注意する点
シングルトンは大変便利な存在なのですが,クラス間の結合が強くなるという恐ろしい点があります。
なので,以下の要素に注意するとよいでしょう。
少数のクラスからしか参照されないものにはシングルトンは使わない
参照されるものの数が少ない場合,シングルトンではなくstaticを使用する
工夫した点
ジェネリックを使用したことで継承で簡単に使えるようになった。
シーンを引き継いでゲームオブジェクトを残すか選択できるようにした。
シーンにゲームオブジェクトがないにも関わらず値が参照された場合,エラーを吐くようにした。
シングルトンが参照されたタイミングで値を設定することで,処理順によるバグを防いだ。
参考にしたサイト
◆ 天神いな / アマガミナゲームス
第1回 シングルトンとサービスロケータ【UnityC# で学ぶデザインパターン】
この人の動画マジで超分かり易くておすすめです。もっと有名になって欲しい……
◆ 白黒_unity
【Unity】ゲームオブジェクトの検索&シングルトン
処理順によるバグを防ぐのはこの人の動画を参考にしました。
↧