ゲーム制作をしているときに、音やフェーダーなどのゲームオブジェクトをシーン間で共有するために、シングルトンをよく使います。書き方を覚えれば、とりあえず使えるようになります。
ただ、訳も分からず覚えるより、ちゃんと理解して覚えた方が、覚えやすいですし、忘れにくいです。
それに、わからないままだと何だかモヤっとしませんか?
私はわからないと、わかるまでトコトン調べる質です。「Unityの仕様です」みたいな部分は、そういうもんだという理解はしますが、自分が書くスクリプトぐらい説明できるようになっておきたいと考えるのが、一端のエンジニアと言えるでしょう。
この記事は、Unityのシングルトンのスクリプトについて解説しておきます。私と同じようにモヤっとした人の気分を晴らしたい人は、最後まで読んでください。
Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。
引用元:Wikipedia
【Unity】シングルトンの使い方を徹底解説【ExampleやTemplateにどうぞ】
Unityのシングルトンの使い方とスクリプトの説明をしていきます。その後には、Unityのシングルトンでみんなが気になっていそうなことを、さらっとまとめておきます。
シングルトンを実装したクラス
Unityでシングルトンを実装したクラスのスクリプトは次のようになります。
public class SampleSingleton : MonoBehaviour { public static SampleSingleton instance; private void Awake() { if( instance == null) { instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } public void subroutine() { Debug.Log("サブルーチンコール"); } }
このスクリプトをゲームオブジェクトにアタッチして使います。
シングルトンはプログラム内で、唯一のインスタンスを持つことで、静的変数である「instance」がそのインスタンスを指す変数となります。
通常のシングルトンはinstanceにはnew演算子を使って新たにインスタンスを格納するスクリプトを書きます。このQiitaの記事のような書き方が私の知るシングルトンでした。
そのためAwakeメソッドでinstanceにthisを格納しているスクリプトが、気持ち悪かったんです。なぜなら、どこでこのクラスをインスタンス化しているか不明だったからです。
これを理解するのに役にたったのは、次のページたちです。
参考記事:イベント関数の実行順序
参考記事:MonoBehaviourのコンストラクタ/デストラクタ
この二つのページより分かったのは、MonoBehaviourクラスを継承したクラスのコンストラクタはAwakeメソッドより前に呼ばれることでした。
つまりはAwakeメソッドを呼び出す前には、すでにインスタンス化されているということでした。
これは推測ですが、シーンを開始した後、ゲームオブジェクトをメモリ上に持ち上げるときに、ゲームオブジェクトにアタッチされているスクリプトのクラスをインスタンス化しているんだと思います。
シーン間でゲームオブジェクトを共有できる理由
通常はシーンが変わると、前のシーンのゲームオブジェクトは破棄されますが、DontDestroyOnLoadメソッドでシーンが変わった時に破棄されないゲームオブジェクトとするようにします。
シーンが変わった時には、シングルトンのゲームオブジェクトが再び生成されますが、クラスで一つしか持たないstatic変数にすでにインスタンスが入っているので、切り替えた先のゲームオブジェクトを生成直後に破棄するようにしているんですね。
シングルトンを使うスクリプト
シングルトンのメソッドを使うのは簡単で、クラス名.instance.メソッド名で使用することができます。
public class Test : MonoBehaviour { private void Start() { SampleSingleton.instance.subroutine(); } }
【Unity】シングルトンのあれこれ
備忘録的Tipsとして、Unityのシングルトンのあれこれを残しておこうと思います。きっと何かの参考になるはずです。
シングルトンを継承することはできるのか?【制限付きで可能】
シングルトンを複数用意するとき、同じ記述をしないように、シングルトンの基底クラスを作りたいわけですね。
しかし、シングルトンはクラスで一つのインスタンスですので、基底クラスが変数instanceを持つと、結局そのinstanceには一つのクラスのインスタンスしか格納されません。
そのため、「一つのシーンで一つのシングルトンしか使用しないのであれば、継承したクラスを使うことができる」という、かなり制限付きのシングルトンを作ることはできます。
複数の子クラスのシングルトンを使用すると、最後にAwakeメソッドが実行されたクラスのインスタンスが格納されることになるので、正常に動作しませんよ。
ジェネリックなシングルトンは作れるか?【作っている人いました】
下のGitHubでUnityで使うことを想定したジェネリックなシングルトンですが、状態管理などMonoBehaviourクラスを継承しないタイプのシングルトンです。
参考:【Unity】ジェネリックなシングルトンパターンを利用したスコア管理クラスとその利用方法。
Unityに特化しているという感じではなく、通常のジェネリックなシングルトンという感じです。
ちなみに「ジェネリック」というのは医薬品ではありませんよ。ここのページが分かりやすいですね。
では、MonoBehaviourを継承しつつ、ジェネリックなシングルとんは作れないのか?というと、作れます。というか作っている人いましたね。
下の参考ページにスクリプトが載っています。車輪の再発明をしないためにも、こういうのは有効活用しましょう。
参考:Generic Based Singleton for MonoBehaviours完全版(?)
シングルトンが重い!どうすりゃいいのか?【余計なものを排除しましょう】
まず「シングルトンだから重い」ということはありませんね。しかしゲームの動作が重くて、どうもシングルトンが原因っぽい場合は、余計なものをメモリに上げすぎている可能性があります。
例えば、音を管理するSoundManagerクラスをシングルトンで作っていて、やたらと音をロードさせていたりすると、そりゃ重くなります。
似たような感じで、シーン間でデータを共有できるからと言って、使わないようなデータをシングルトンのインスタンスに持たせていると、プログラムが徐々に重くなっていきます。
シングルトンに限らず、プログラムを軽量に動かすためには、実装のしやすさを多少犠牲にしても、余分なものを排除するようにしましょう。
納得できない人は自力でUnityとシングルトンについて勉強しよう
Unityにおけるシングルトンについてまとめました。私的には、この記事がシングルトンに関する知見の外部記憶装置になりましたが、あなたはどうでしょうか?
もし、納得できねぇなと思ったら、チャンスですね。自分で調べて、理解することで、さらにUnityやデザインパターンについて詳しくなれますよ。
「分からないけど、動けばいいや」ではなく、「わからないところを潰すことで理解を深める」と前向きに学習を進めてくださいね。
最後ですが、Unityのシングルトンについてかなり高度な知識を得ることができるページを紹介しておきます。上級者向けの内容になっていますので、我こそは!という人は読んでみてください。