ただの適当な開発記

会社勤めしつつUnityでアプリ作ってる人の雑記

<Unity5GameOptimization>第2章:スクリプト最適化

親記事
cocokyoro.hateblo.jp

第2章 スクリプトの最適化(Scripting Strategy)についてまとめていきます。
第1章はUnityのProfilerについての説明だったので個別にはまとめてないです。(あとからまとめるかも
訳について、いやそれは違うでしょっていうのがあればご指摘お願いしますm(__)m

風呂上がりに1日1節ずつ追加してく感じでいきます。


コンポーネントの参照をキャッシュする

(CacheComponentReferences)

Unity上でスクリプトを書く際によくやりがちなミスは、GetComponent()メソッドを使いすぎることだ。

void Damage(){
     if(getComponent<Player>().hp < 0)
     {
          GetComponent<RigidBody>().enabled = false;
          GetComponent<Collider>().enabled = false;
          GetComponent<Animator>().SetTrigger(“death”);
     }
}

このようなメソッドは、呼ばれるたびに4つの異なるコンポーネントを取得していることになる。
ヒープメモリ的には問題ないかもしれないが、CPU使用の観点から見るとあまりよろしくない。
特にこのメソッドがUpdateの中で呼ばれている際にはパフォーマンスに大きな影響を与える。

参照を保持しておくことはごく僅かなメモリ領域しか消費しない。(Unityのバージョンやプラットフォームや断片化の種類にもよるが、32bitか64bitのメモリ領域を指す)

先ほどのコードをキャッシュ化させるとこうなる。

private Player _player;
private RigidBody _rigidBody;
private Collider _collider;
private animator _animator;

void Damage(){

     void Awake()
     {
          _player = getComponent<Player>();
          _rigidBody = GetComponent<RigidBody>();
          _collider = GetComponent<Collider>();
          _animator = GetComponent<Animator>();
     }
     if(_player.hp < 0)
     {
          _rigidBody.enabled = false;
          _collider.enabled = false;
          _animator.SetTrigger(“death”);
     }
}

こうすれば処理を呼び出すごとに取得しなくても済むし、CPUのオーバーヘッドも削減できる。

■最速のメソッドでコンポーネントを取得する

(原題:Obtaining Components using the fastest method)

GetComponentには
GetComponent(string)
GetComponent()
GetComponent(typeof(T))
の3種類があるが、これらのうちどれが早い処理かはUnityのバージョンによって異なる。
各メソッドを100万回実行した際の処理時間について比較してみる。
Unity4では
GetComponent(string) : 841ms
GetComponent() :169ms
GetComponent(typeof(T)) : 122ms
GetComponent(typeof(T))が最速
そしてGetComponent(string)は遅すぎるので使わないほうが良い
(検証コードは省きます)

Unity5では
GetComponent(string) : 2961ms
GetComponent() :113ms
GetComponent(typeof(T)) : 114ms
とtypeof(T)の結果がほぼ一緒になった。
Unity5にするにあたってUnityTechnologiyes側がSystem.Tyeの扱いを改善したらしい。
GetComponent(string)が逆に遅くなっていることにも注目したい。
Unity5では型指定でGetCompnentするのがよい。

ちなみにUnity4にはgameObjectにいくつかのアクセサが存在した。
collider,rigidbody,cameraなどがそうだ。
実はこちらのほうが取得速度は速かったりする。
しかしこのアクセサはunity5で廃止された。
残ってるのはgameObject.transformのアクセサのみである。
Unity4ユーザーがUnity5へアップデートする場合、廃止されたアクセサを使っている箇所は自動でGetComponent()に置き換わる。

ここで注意したいのは、頻繁に呼ばれる処理のなかで該当するアクセサを使っていた場合である。
自動で置き換えた際に前節の■CacheComponentReferencesの例にあるような、パフォーマンス上よろしくないコードに置き換わってしまうことがある。

■空のコールバックを削除する

(原題:Removing empty callback declarations)

UnityでC#スクリプトを新規作成すると2つのメソッドがデフォルトで宣言されている。

        // Use this for initialization
	void Start () {
	}

	// Update is called once per frame
	void Update () {
	}

Unityのエンジンは初期化の段階でこれらのメソッドをコールバックのリストに格納して、それぞれ決まったタイミング*1で呼び出す。

Startはシーンがロードされたタイミングか、Prefabがinstantiateされたタイミングにしか呼ばれないため、そこまで大きなオーバーヘッドはならないかもしれない。
しかし、オブジェクトをInstantiateするタイミングは大抵がゲーム内で重要な実行タイミングであることが多く、同時に他の処理が走る可能性がある以上不要なStartは消しておくべきである。
Updateは毎フレーム呼ばれるので、当然消しておいたほうがいい。

検証のためProfilerでCPU使用率を確認したところ、
・何もアクティブじゃない状態
・MonoBehaviourのコールバックが宣言されていないコンポーネントをアクティブにした状態
・空のMonoBehaviourコールバックが宣言されたコンポーネントをアクティブにした状態
の3つのパターンでは最後のパターンだけ大きなCPU使用率になった。

この問題の解決法はシンプルで、空の宣言を消せばよい。
しかし、すでにいくつものスクリプトを作成しているプロジェクトでは、該当する箇所を探すのがとても面倒である。
そんなときはエディタの正規表現での検索機能を使おう。

Updateメソッドを検出する正規表現はこちら

void\s*Update\s*?\(\s*?\)\s*?\n*?\{\n*?\s*/|}

正規表現を知っている人なら分かると思うが、これはvoid Update{}の各単語と記号の間に空白と改行文字が含まれているパターンも検出してくれる。
StartやUpdate以外にもMonoBehaviourに紐付いたコールバックはたくさんある*2ので、一度把握しておいた方がいい。

■Vector3.Distance()よりもVector3.sqrMagnitudeを使う

cocokyoro.hateblo.jp