シングルトンなオブジェクトは便利。だけど初期化のタイミングが難しいという問題がありました。
その時解決策として考えた、自分用メモです。
以下の仕様を満たすことを条件としました
- Awake、Startに依存しない
- 初期化メソッドが必ず定義されてなくてはならない
- 初期化メソッドはAwakeより前に呼び出された場合でも実行される
1.Awake、Startに依存しない
プロパティを使った遅延初期化を行う。
FindObjectOfTypeを使った例が多いですが、ヒエラルキー内全てのオブジェクトを検索する為、規模によって初期化時間に大きく影響する為、
今回はテラシュールブログさんのUnityで少しだけ高速なシングルトン(Singleton)をベースに行います。
2.初期化メソッドが必ず定義されてなくてはならない
シングルトンを作成する基底クラスに、以下の抽象メソッドを追記します。
protected abstract void Init();
これにより、SingletonMonoBehaviourを継承したクラスは、必ず**Init()**を宣言する必要があります。
3. 初期化メソッドはAwakeより前に呼び出された場合でも実行される
基底クラスから初期化のタイミングでInitを呼び出す必要があります。
MonoBehaviourを継承しているならSendMessageが使えます。
しかも、Unity2018から.Net4xが推奨。今まで引数が文字列参照だったのがnameOf演算子で指定ができる!
Awakeより前の呼び出しなら
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後なら
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;
}
実装側は
public class Player : SingletonMonoBehaviour<Player>
{
protected override void Init()
{
Debug.Log("呼び出された時に必ず実行される初期化");
}
}
これでシーン上に配置されてることを前提とした場合、どのタイミングで呼ばれても(Awake実行前に呼ばれても)、
派生クラスで呼ばれてなくてはならい初期化が必ず実行された上で、安全に扱うことができます。
課題としては、その通りこのオブジェクトがシーン上に配置されてることが前提になります。
誤って削除されたとか、テストしようとしたシーンにないとか、そういうときにも対応しなくてはなりません。
まだまだシングルトンは模索中ですが、なにかよさそうな方法とか、こうやってるよ!みたいな意見があったら教えてください!