クラゲ日記

UnityのTipsなど技術関連がメイン。ゆるゆる更新します。

【Unity】SerializeFieldなキャッシュ変数をビルド前にキャッシュする

こんにちは。けっとです。

パネルの中に複数のボタンが置かれるUIなど特にそうですが、Sceneに配置する個々のオブジェクトが階層化されていくことがあります。

そしてそういう場合、UIパネル内のボタン文字列をメニュー選択項目に合わせて変更する場合など階層下の子に何度もアクセスする場合が多くなります。

これを transform.Find(); や GetComponent<>(); で逐一取得するのは無駄ということで、変数をキャッシュするモチベーションが生まれるわけです。

例えば、"Button1", "Button2"という2つのボタンをもつUIパネルオブジェクトを制御するスクリプト(HogePanel)を考えると、こんな感じにAwake();でキャッシュを初期化するのが一般的かと思います。

using UnityEngine;
using System.Collections;

public class HogePanel: MonoBehaviour{
    private Transform mButton1;
    private Transform mButton2;

    void Awake(){
        mButton1 = transform.Find("Button1");
        mButton2 = transform.Find("Button2");
    }
...
}

これはこれで悪くないのですが、ビルド前から分かっている参照対象ならビルド前に初期化出来るはずです。 ということで次のような方法を試してみました。特徴としては、

  • キャッシュ初期化はビルド前(実行時に負荷がない)
  • コードから初期化するので、キャッシュ先を柔軟に変えられ、ドラッグ&ドロップミスがない
  • コードから初期化するので、同じ構造を持つオブジェクトが複数あってもOK(これはPrefabを使っても同じですが)
  • SerializeFieldなprivate変数 なのでInspectorを汚さない
  • キャッシュ変数はTransform, GameObject, Component などSerializeFieldに出来るものならなんでもOK

といったところです。まず1つ目。継承されるオレオレMonoBehaviourクラス(以前の記事のTransCacheBehaviourの改良です)です。

using UnityEngine;
using System.Collections;

public abstract class CacheBehaviour : MonoBehaviour {
    public virtual bool InitCacheField()
    {
        return false;
    }
}

次に2つ目。Scene中のキャッシュ変数を全て初期化しなおすためのEditor拡張クラスです。 ショートカットキーを作ってキャッシュをしなおすようにしておきます。

using UnityEngine;
using UnityEditor;
...

public class MyCommonEditorWindow : EditorWindow {
...
    [MenuItem ("My/Serialize Cache Reflesh &c")]
    private static void RefleshCache()
    {
        int n = 0;
        var tcbs = GameObject.FindObjectsOfType<CacheBehaviour>();
        foreach (var tcb in tcbs)
        {
            if( tcb.InitCacheField())
            {
                ++n;
            }
        }
        Debug.Log(n.ToString() + " Components' cache field were refleshed.");
    }
...
}

かなりシンプルですが、準備のクラスは以上2つです(MyCommonEditorWindowはAssets/Editor以下に入れないとダメかも)。 使う側のクラスは、上と同じHogePanelの場合

using UnityEngine;
using System.Collections;

public class HogePanel: CacheBehaviour{
    [SerializeField]
    private Transform mButton1;
    [SerializeField]
    private Transform mButton2;

    public override bool InitCacheField()
    {
        mButton1 = transform.Find("Button1");
        mButton2 = transform.Find("Button2");
        return true;
    }
...
}

こうなります。これでEditor中に"Alt+C"を押すと、 Consoleに "xxx Components' cache field were refleshed."と表示されシーン中のオブジェクトのキャッシュが初期化されます。 Awake();でのキャッシュ初期化も、正直大したコストではないとは思いますが、数が増えればバカには出来ませんし、出来るんだからビルド前にやっておこうか、という感じでした。

…注意点としては、うっかり[SerializeField]を付け忘れるとログも正しく表示されるのに実行時はnullになっていることです。一度慣れればミスしても症状からすぐ想像が付くようになるのですが……。