【Unity】知らなきゃ損! 「SingletonMonoBehaviour」の解説と使い方

どうも、個人ゲーム開発者のゆみねこです。

Unityで特定のGameObjectにアクセスする時、どうやってますか?

  • FindやFindWithTagを使う
  • Inspectorからアタッチする

おおむね、この2つだと思います。
が、場合によっては面倒なこともありますよね。

今回は、この問題を一発で解決する方法を紹介します。
その名も「SingletonMonoBehaviour」です。

SingletonMonoBehaviourとは

シングルトンパターンのMonoBehaviour

(Script初心者にはつらい説明なので、分からないなら次の項へ飛ばしていいです)

シングルトンパターンをMonoBehaviourで実装したものです。
MonoBehaviourを継承したクラスはstaticにできないので、そこらへんをいくつか工夫して実装したものになります。

インスタンスが1つだけ

SingletonMonoBehaviourを使うと、そのSceneに1つしか存在できないGameObjectを作成できます。

例えばEnemyクラスを作ったとして、Prefab化して大量に生成する使い方が考えられますよね。
でも、SingletonMonoBehaviourの場合はできません。

一見するとデメリットですが、これにより、Scene中に1つしか存在してはいけないGameObjectが管理しやすくなります。

  • 進行を管理するゲームマネージャ
  • スコアを保存しておくためのGameObject
  • 効果音を再生するサウンドマネージャ

など、主にマネージャー系の作成で重宝します。

アクセスが簡単になる

どちらかと言うと、これが本命。
SingletonMonoBehaviourで作成されたGameObjectは、FindやGetComponentなしにアクセスできます。

例えば、GameManagerクラスのGameStartというメソッドを呼び出したい時は

GameManager.Instance.GameStart();

これだけでOKです。すごくないですか?

使い方

メリットが分かったところで、さっそく使ってみましょう。

導入

上記ページよりcsファイルをダウンロードし、UnityのAssetフォルダ以下に突っ込めばOKです。

GameObjectを作る

SingletonMonoBehaviourを継承して、新しいGameObjectを作ります。

public class Parameter : SingletonMonoBehaviour<Parameter> {
    public int score;
}

1行目に注目。
MonoBehaviourではなく、SingletonMonoBehaviourを継承しています。

Scriptを適当なGameObjectにアタッチすれば準備完了です。

アクセスの仕方

クラス名.Instance でOKです。
今回の場合なら、

// スコアを100点にする
Parameter.Instance.score = 100;

でOK。同一Scene内なら、どのGameObjectからでもアクセスできます。

DontDestroyOnLoad

DontDestoryOnLoadにチェックを入れた場合、Scene間をまたいで存在するようになります。

これにより、Sceneに関係なくあらゆるGameObjectから一発でアクセスできるGameObjectを作成できます。

スポンサードサーチ

注意すべきこと

なにかと便利なSingletonMonoBehaviourですが、デメリットもあります。

メモリを消費する

SingletonMonoBehaviourはstaticで実体を保持します。
staticとは読んで時のごとく静的な変数で、ゲーム開始時から終了時まで、ずっとデータがメモリに残り続けます。

そこまでシビアに考える必要はありませんが、あまりにも使いすぎるとメモリを無駄に消費するということは頭に留めておきましょう。

バグが起きやすくなる

先ほどの例のような使い方だと、実質グローバル変数を定義しているのと変わりません。

どこからでもアクセスできるということは、バグが起きた時に原因の特定が困難になることを意味します。

複数の実体は持てない

Scene内に1つしか存在できないため、いくつも生成することが想定される敵や弾などには使えません。

マネージャー系など、2つ以上存在すると不都合が出るものに使うのが好ましいです。

なにをやっているのか?

先に断っておくと、初心者には厳しい内容です。
が、「仕組みまで知っておきたい!」という方のために、なるべく分かりやすく解説しておきます。

インスタンス(実体)

まず大前提として、クラスや構造体にはインスタンス(実体)という概念があります。実体とは読んで時のごとくなので説明が難しいですが……。

例えば、Enemyクラスを作ったとして、

Enemy slime = new Enemy();
Enemy dragon = new Enemy();

とかやりますよね。
この、slimeやdragonがインスタンスです。

クラスは変数やメソッドをまとめたものですが、それらを実体として生成したものがインスタンスです。

構造体でも同じことが言えて、

transform.position = new Vector3(0,0,0);

これは、Vector3型の(0,0,0)という新しいインスタンスを作成し、transform.positionに代入しているわけです。

いいですか?
これが今後の説明の全ての基本になるので、無理矢理にでも理解して下さい。

static

C#にはstaticという修飾子があります。

先ほども簡単に説明しましたが、staticをつけると、インスタンスの生成が必要なくなります。
ゲームの開始時に自動でインスタンスを作成するからです。

public static class Enemy(){
    void Attack(){
        Debug.Log("攻撃");
    }
}

void Start(){
    Enemy.Attack();
}

こんな感じで、newをしなくてもそのままAttackを呼び出せます。
ちなみに、newをつけるとコンパイルエラーが発生します。

じゃあなんでSingletonMonoBehaviourが必要なの?

「え? じゃあSingletonMonoBehaviourを継承せずとも、クラスの最初にstaticつけりゃええやん?」

と思ったあなた。残念ですができません。試しにやってみてください。

GameObjectにアタッチするScriptは、必ずMonoBehaviourクラスを継承しなければならないですよね?

これは「MonoBehaviourクラスを引き継いで新しいクラスを作るよ」という記述なわけですが、なんとびっくり、staticクラスは継承ができません。

そんなわけで、上手い具合に工夫してやる必要があるわけです。

インスタンスを内部で生成して隠す

では、どう工夫するのか?
クラスの実体を、staticで生成しちゃえばいいのです。

Tがなんなのかまで説明すると日が暮れるので省略しますが、赤枠部分で自分自身のインスタンスを作成しています。

すぐ下が、生成したインスタンスを読み取り専用で渡すためのプロパティです。

更に、その下でGameObjectの重複を調べています。
このif文は「このScript(this)が、さっき生成したインスタンスと違うかどうか」を判定しています。

2つ以上のGameObjectにScriptをアタッチしちゃった場合や、Instantiateで追加生成した場合、このif文に引っかかます。
Destroyで、生まれた直後に殺される運命となるわけですね。アーメン。

正直、使い始めの段階でここまで理解する必要はありません。
気になる方は「シングルトン」で検索すれば、より詳しく解説した良記事がたくさんヒットするはずです。

まとめ

以上、ざっくりとしたまとめでした。

先述した注意点を踏まえると、ゲームの進行やパラメータを管理するマネージャに限定して使うのがベターです。

間違っても、簡単にアクセスできるメリットだけに誘惑されて、手当たり次第にSingletonMonoBehaviourを使わないでくださいね(まあ、実際に使ってみて痛い目を見るほうが早いですが)。

用法用量を守ればとても便利なので、使い所を見つけたら活用していきましょう!

ちなみにぼくは、Unityに限らず、個人で活動するゲーム開発者向けの情報を、Twitterで日々発信しています(質問にも答えています)。

記事の更新を見逃したくないという人は、ぜひぼくのTwitterをフォローしてもらえると嬉しいです!

記事をシェアする