4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Unity】呼び出された時に絶対に初期化が行われるシングルトンなオブジェクト

Last updated at Posted at 2019-02-10

シングルトンなオブジェクトは便利。だけど初期化のタイミングが難しいという問題がありました。
その時解決策として考えた、自分用メモです。

以下の仕様を満たすことを条件としました

  1. Awake、Startに依存しない
  2. 初期化メソッドが必ず定義されてなくてはならない
  3. 初期化メソッドはAwakeより前に呼び出された場合でも実行される

1.Awake、Startに依存しない

プロパティを使った遅延初期化を行う。
FindObjectOfTypeを使った例が多いですが、ヒエラルキー内全てのオブジェクトを検索する為、規模によって初期化時間に大きく影響する為、
今回はテラシュールブログさんのUnityで少しだけ高速なシングルトン(Singleton)をベースに行います。

2.初期化メソッドが必ず定義されてなくてはならない

シングルトンを作成する基底クラスに、以下の抽象メソッドを追記します。

SingletonMonoBehaviour.cs
protected abstract void Init();

これにより、SingletonMonoBehaviourを継承したクラスは、必ず**Init()**を宣言する必要があります。

3. 初期化メソッドはAwakeより前に呼び出された場合でも実行される

基底クラスから初期化のタイミングでInitを呼び出す必要があります。
MonoBehaviourを継承しているならSendMessageが使えます。
しかも、Unity2018から.Net4xが推奨。今まで引数が文字列参照だったのがnameOf演算子で指定ができる!

Awakeより前の呼び出しなら

SingletonMonoBehaviour.cs
    protected static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                Type type = typeof(T);

                foreach (var tag in findTags)
                {
                    GameObject[] objs = GameObject.FindGameObjectsWithTag(tag);

                    for (int j = 0; j < objs.Length; j++)
                    {
                        instance = (T)objs[j].GetComponent(type);
                        if (instance != null)
                        {
                            instance.SendMessage(nameof(Init));
                            return instance;
                        }
                    }
                }

                Debug.LogWarning(string.Format("{0} is not found", type.Name));
            }

            return instance;
        }
    }

Awake後なら

SingletonMonoBehaviour.cs
    virtual protected void Awake()
    {
        CheckInstance();
    }

    protected bool CheckInstance()
    {
        if (instance == null)
        {
            instance = (T)this;
            instance.SendMessage(nameof(Init));
            return true;
        }
        else if (Instance == this)
        {
            return true;
        }

        Destroy(this);
        return false;
    }

実装側は

Player.cs
public class Player : SingletonMonoBehaviour<Player>
{
    protected override void Init()
    {
        Debug.Log("呼び出された時に必ず実行される初期化");
    }
}

これでシーン上に配置されてることを前提とした場合、どのタイミングで呼ばれても(Awake実行前に呼ばれても)、
派生クラスで呼ばれてなくてはならい初期化が必ず実行された上で、安全に扱うことができます。
課題としては、その通りこのオブジェクトがシーン上に配置されてることが前提になります。
誤って削除されたとか、テストしようとしたシーンにないとか、そういうときにも対応しなくてはなりません。
まだまだシングルトンは模索中ですが、なにかよさそうな方法とか、こうやってるよ!みたいな意見があったら教えてください!

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?