Unity – コンポーネントのキャッシュのタイミング Part.1

public T GetComponent();

便利だけれど、総当たり処理のため重い。

特に回数アクセスする場合は対象のコンポーネントをキャッシュして
参照するのが良いとされる。

(以下のサンプルスクリプトは全て、新規プロジェクトでHierarchy上で右クリック→UI→Textを選択し生成されたTextにスクリプトをアタッチすることで動作確認ができます。)

まず、良くない例をあげます。

using UnityEngine;
using UnityEngine.UI;

public class Test:MonoBehaviour{
    void Update(){
        Debug.Log($"Position:{GetComponent<RectTransform>().anchoredPosition}");
    }
}

UIのTextにアタッチすると、ログに自分の位置を吐き出し続けます。

これが良くない理由は、public T GetComponent()が重い処理なのに、毎フレーム呼び出されるvoid Update()に記述しているため、大きな負荷となるからです。

ではどのように改善するかですが、例えば以下のようにキャッシュします。

using UnityEngine;
using UnityEngine.UI;

public class Test:MonoBehaviour{
    RectTransform rectTransform;
    void Start(){
        rectTransform = GetComponent<RectTransform>();
    }
    void Update(){
        Debug.Log($"Position:{rectTransform.anchoredPosition}");
    }
}

この例でvoid Update()で参照しているのは、最初のフレームの呼び出し時に実行されるvoid Start()メソッドによってrectTransform変数に代入されたRectTransformです。このように呼び出しが必要な値を代入した変数を持つことをキャッシュと呼ぶそうです。

このキャッシュする方法やタイミングは色々な方法があります。
例えば先ほど提示したvoid Start()やvoid Awake()で変数にキャッシュする方法。

例えばInspector上で変数に代入しキャッシュする方法。

using UnityEngine;
using UnityEngine.UI;

public class Test:MonoBehaviour{
    [SerializeField] RectTransform rectTransform;
    void Reset(){
        rectTransform = GetComponent<RectTransform>();
    }
    void Update(){
        Debug.Log($"Position:{rectTransform.anchoredPosition}");
    }
}

このスクリプトをアタッチ(アタッチ済みの場合はInspector上でスクリプトの名前を右クリック→Reset)すると、UnityのInspectorでRectTransformを取得してくれます。最初のアタッチ時か更新時にUnityのEditor上でvoid Reset()メソッドが呼び出されるためです。
この方法を使う場合はクラス自体に[RequireComponent(System.Type)]属性を付けてnullへの対策を講じたり、あるいはvoid Reset()を消してInspector上で手動で代入しても良いでしょう。

例えばprivateなプロパティにキャッシュをする方法

using UnityEngine;
using UnityEngine.UI;

public class Test:MonoBehaviour{
    RectTransform rectTransform{
        get{
            if(_rectTransform == null){
                _rectTransform = GetComponent<RectTransform>();
            }
            return _rectTransform;
        }
    }
    RectTransform _rectTransform;
    void Update(){
        Debug.Log($"Position:{rectTransform.anchoredPosition}");
    }
}

初めてrectTransformプロパティにアクセスする時に、7行目のif文でnullが返るので8行目で_rectTransformにキャッシュし、今後は取得した_rectTransformを返すようになる。

これは三項演算子かnull合体演算子でもう少し簡潔に書けるが、その内容は次回に。

これらは取得するタイミングや負荷は違うがアクセスの負荷は等しい。
取得するタイミングによる違いは以下の通り

(参考:イベント関数の実行順 – Unity マニュアル

方法 タイミング 負荷考察
void Start() Hierarchy上で有効な時の、最初のフレームのUpdate()の前 最初からHierarchyにオブジェクトの場合、全オブジェクトから最初のフレームに呼び出される。
多くのGetComponentが利用されると最初のフレームは重そう。
void Awake() Start()の前か、
プレハブのインスタンス化直後
ほぼ同上
Inspector上代入 Editorで任意のタイミング 他のオブジェクトとの処理が重なることがないので低負荷。
プロパティ 実行中のアクセス時 最初に必ずアクセスするのであれば最初のフレームは重そうだが、タイミング分散や、アクセスしないことも想定されるのであれば低負荷。

おすすめはInspector上代入かプロパティを使ってのキャッシュです。

次回はnull演算子を使ってみる。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です