Unity標準のUIといえば俗にいう「uGUI」です。
これらuGUIコンポーネントは通常のTransform
ではなくて、RectTransform
を使う必要があります。
ところで。
みなさん、RectTransform
をスクリプトで取得する時にはGetComponent<RectTransform>()
ってやってるんじゃないかと 思います。
でも知ってました?RectTransform
ってTransform
のサブクラスなんですよ。
なので、
var rectTransform1 = GetComponent<RectTransform>();
var rectTransform2 = transform as RectTransform;
実はこの2つとも正しくRectTransform
が取得できます。
すると気になるのが速度です。 『遅い遅い、何やってんだ、しっかりしろ、どうなってるんだ。』 と評判のGetComponent
が出てくるからには速度が気になります。
というわけで調べてみました。
GetComponent vs Cast vs Cache
using UnityEngine;
using UnityEngine.Profiling;
public class SpeedCheck : MonoBehaviour
{
private readonly int max = 10000;
private CustomSampler samplerMemberCache;
private CustomSampler samplerGetComponent;
private CustomSampler samplerAsCast;
private RectTransform cachedRectTransform;
void Start()
{
//サンプラー用意
samplerGetComponent = CustomSampler.Create("GetComponent");
samplerAsCast = CustomSampler.Create("AsCast");
samplerMemberCache = CustomSampler.Create("MemberCache");
//cache
cachedRectTransform = GetComponent<RectTransform>();
}
void Update()
{
//GetComponent
samplerGetComponent.Begin();
for(var i = 0;i < max;++i)GetComponent<RectTransform>().Rotate(0,0,Time.deltaTime);
samplerGetComponent.End();
//as演算子
samplerAsCast.Begin();
for (var i = 0; i < max; ++i) (transform as RectTransform).Rotate(0, 0, Time.deltaTime);
samplerAsCast.End();
//メンバ変数キャッシュ
samplerMemberCache.Begin();
for (var i = 0; i < max; ++i) cachedRectTransform.Rotate(0, 0, Time.deltaTime);
samplerMemberCache.End();
}
}
GetComponent:2.73ms
AsCast:2.53ms
MemberCache:2.28ms
うーん。 予想を裏切らない、
「GetComponet使うなら、asでキャストしたほうが良い。 でもやっぱりキャッシュが一番速いよね」
という思った通りの結果となりました。
+ ExtendMethodAsCast vs PropertyCache
(まぁ、メンバ変数へのキャッシュが最速なのはさておき) As演算子でキャストするのは良いんですが、そのまま使おうとすると括弧を一つ挟まなくてはいけないので若干面倒です。
なので、こういう拡張メソッドを用意したらどうでしょう
public static class RectTransformExtension
{
public static RectTransform ToRectTransform(this Transform t)
{
return t as RectTransform;
}
}
それと、メンバ変数へのキャッシュはうっかりStart(Awake)でキャッシュを入れ忘れたらまずいですよね。
なので、こういうことをよくやります。
private RectTransform _cachedRectTransform;
public RectTransform RectTransform =>
_cachedRectTransform != null ? _cachedRectTransform : (_cachedRectTransform = transform as RectTransform);
さてこれらは速いんでしょうか。(「プロパティキャッシュ」と本当に呼ぶかどうかは知りません。勝手につけました)
検証コードに以下を追加して、再検証してみます。
//as演算子 拡張メソッド版
samplerMethodExtendAsCast.Begin();
for (var i = 0; i < max; ++i) transform.ToRectTransform().Rotate(0, 0, Time.deltaTime);
samplerMethodExtendAsCast.End();
//プロパティキャッシュ
samplerPropertyCache.Begin();
for (var i = 0; i < max; ++i) RectTransform.Rotate(0, 0, Time.deltaTime);
samplerPropertyCache.End();
GetComponent:2.95ms
AsCast:2.61ms
MemberCache:2.29ms
拡張メソッドAsCast:2.66ms
プロパティキャッシュ:2.68ms
拡張メソッドの方は、メソッド呼び出しで若干オーバーヘッドが出るので遅くなることは想定してましたが、微々たるものですね。悪くないです。
ところが、予想に反してプロパティキャッシュがやたら遅いです。Castに負けるってありえなくないですか?なんででしょう?????
##(UnityEngine.Object)のnullチェック! またお前か!
普段からunityをお使いの紳士・淑女はご存知の方も多いと思います。 UnityEngine.Objectを継承しているクラスにおいて、nullチェックはnullチェックではなく、nullチェック+生存確認です。
なので、
public RectTransform RectTransform =>
_cachedRectTransform != null ? _cachedRectTransform : (_cachedRectTransform = transform as
の
_cachedRectTransform != null
は
_cachedRectTransform.IsAlive() //こんなメソッドはありません。あくまでも概念です
をやっているようなもので、遅い原因はコイツです。
ここでは、別に生存を取りたいわけではなくて、キャッシュしてあるかどうかを判断するためのnullチェックなので、いってしまえばbool的な使い方です。
試しにちょっとソースコードが太りますが、プロパティキャッシュを以下のように変更してみます。
private RectTransform _cachedRectTransform;
// public RectTransform RectTransform => _cachedRectTransform != null ? _cachedRectTransform : (_cachedRectTransform = transform as RectTransform); //これは辞めて
private bool isCached;
public RectTransform RectTransform
{
get
{
if (!isCached)
{
_cachedRectTransform = transform as RectTransform;
isCached = true;
}
return _cachedRectTransform;
}
}
GetComponent:3.16ms
AsCast:2.53ms
MemberCache:2.24ms
拡張メソッドAsCast:2.73ms
プロパティキャッシュ:2.37ms
GetComponent > 拡張メソッドAsCast > AsCast > プロパティキャッシュ > MemberCache
概ね想像通りの結果となりました。
なお。今回はわかりやすさ優先でわざわざboolの変数(isCached)を用意しましたが、
??
(null合体演算子) を使って
private RectTransform _cachedRectTransform;
public RectTransform RectTransform => _cachedRectTransform ?? (_cachedRectTransform = transform as RectTransform);
こう書いた方が効率が良いです。(タイピング量的にも、処理的にも)
##まとめ
まぁ、実の所GetComponent
もそんな禿げ上がるほど遅くはないので、よほどピーキーなチューニングが必要な環境でもない限り使ったっていいんじゃないの?という気もします。(マサカリ案件)
次点でプロパティ使ったキャッシュかなーと、というのもただでさえ汚いStartやAwakeをあまり汚したくないのと、プロパティキャッシュ方式ならスニペットとかで簡単にコード挿入できるかなーとか。(あくまでも個人的な意見です。 ここら辺のキャッシュを加味した MonoBehaviour継承のクラスを使うのはまぁ好き好きで)(マサカリ案件その2)
何にせよ、Unityには素敵なProfilerが最初から付いてるので、(心と時間に)余裕があれば計測しましょう。
知らないで使うのと、知って使うのとでは色々と見える世界が変わってくると思います。
それでは。