simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:シーンに一つだけしかアタッチできないSingletonMonoBehaviour+実装例

グローバルスコープに唯一のインスタンスを生成して、プロジェクトのどこからでも容易にアクセスできる仕組みがシングルトンの良いところです。
絶対に一つしかインスタンスを生成していないことをコードから保証できる点で、優れたデザインパターンだと思います。

さて、Unity の MonoBehaviour というゲームオブジェクトにアタッチして使うクラスは、そんなC#のシングルトン書式が通用しません。
なので、次の記事で示されるような仕組みを用意する必要があります。

qiita.com

具体的には以下のコードです。

SingletonMonoBehaviour.cs

using System;
using UnityEngine;

namespace SimpleHexWorld
{
    public abstract class SingletonMonoBehaviour<T> : SimpleMonoBehaviour where T : SimpleMonoBehaviour
    {
        private static T _instance = null;
        public static T Instance
        {
            get
            {
                if (null == _instance)
                {
                    Type t = typeof(T);

                    _instance = (T)FindObjectOfType(t);
                    if (_instance == null)
                    {
                        Debug.LogError(t + " is not attached in this scene");
                    }
                }

                return _instance;
            }
        }

        virtual protected void Awake()
        {
            if (this != Instance)
            {
                Destroy(this);
                Debug.LogError(
                    typeof(T) +
                    " is already attached, so this script component was removed." +
                    " Object.name = " + Instance.gameObject.name + " causes this trouble.");
                return;
            }

            //DontDestroyOnLoad(this.gameObject);
        }
    }
}

簡単に説明すると、ゲーム開始後にどこかで Instance にアクセスすると、シーン内を探索した後、シーン内で唯一のスクリプトコンポーネントを返します。
もしシーン内に存在しない場合はエラーログが流れて null が返ります。
またシーン内の複数のオブジェクトに誤ってアタッチしてしまった場合、エラーログが Awake のタイミングで流れ、2つめ以降のスクリプトコンポーネントはゲームオブジェクトから削除されます。

以下、使い方についての実装メモです。

クラスは継承を使って、Awake を上書きしつつ、しっかり継承元の Awake を呼ぶようにします。

namespace SimpleHexWorld
{
    public class UnityWorld : SingletonMonoBehaviour<UnityWorld>, IViewWorld
    {
        protected override void Awake()
        {
            base.Awake();

使いどころでは Instance の null チェックを忘れずに!(?を一つ付けると null だったら null を返し、null じゃなければアクセスして値を返します。うれしい省略書式ですね!)

_meshData[layerIndex]._materials[subMeshIndex2] = UnityWorld.Instance?.BlockMaterials[keyValue.Key];