Resources フォルダー
特殊な最適化

一般的な最適化

パフォーマンスの問題に様々な原因があるように、コードの最適化方法も様々です。通常、 CPU の最適化を行うに当たっては、事前にアプリケーションの綿密なプロファイリングを行うことが強く推奨されます。ただし、どんな場合にも適用できる CPU 最適化もいくつかあります。

プロパティのアドレス指定を ID で行う

Unity は、アニメーター、マテリアル、シェーダーのプロパティーを内部的に表す際、文字列による名は使用しません。速度を高めるために、全てのプロパティー名はプロパティ ID 内でハッシュ化され、プロパティーを示すにはこの ID が使用されます。

したがって、SetGet メソッドをアニメーター、マテリアル、シェーダーなどに使用する場合は、string 型ではなく int 型のメソッドを使用してください。string 型メソッドは単純に、文字列をハッシュにした ID を int 型メソッドに転送します。

文字列のハッシュから作成されたプロパティー ID は、1回の実行中の間において有効です。この使用方法として最も簡単なのは、静的な読み込み専用の int 変数を各プロパティー名について宣言し、その int 変数を文字列の代わりに使用することです。これは、起動時に、追加の初期化コードなしに自動的に初期化されます。

アニメーターのプロパティー名に適した API は Animator.StringToHash、マテリアルとシェーダーのプロパティー名に適した API は Shader.PropertyToID です。

割り当てなしの物理演算 API を使用する

Unity 5.3 以降は、Physics クエリ API の割り当てなしのバージョンが提供されています。例えば RaycastAll コールは RaycastNonAlloc に置き換え、 SphereCastAll コールは SphereCastNonAlloc に置き換えることができます。 2D アプリケーションの場合は、全ての Physics2D クエリー API の割り当てなしのバージョンもあります。

Transform の操作

Transform の位置や角度が変更されると、その Transform と同じゲームオブジェクトおよび全ての子ゲームオブジェクトのコンポーネント全てに、 OnTransformChanged メッセージがディスパッチされます。このため、 1 フレーム内における Transform の位置と角度の変更回数はなるべく少なくするのが賢明です。特定の Transform への適用が必要になる(可能性のある)変更全てをバッチ化して一度に適用することをお勧めします。これにより、その Transform ヒエラルキー全体に伝播されなければならない OnTransformChanged メッセージの数を最小限に抑えられます。これは、アニメーションキャラクターなど、深い(大きな) Transform ヒエラルキーを動かす場合には特に重要です。

ベクター、四元数計算および処理順序

タイトなループ内にあるベクターやクォータニオンの計算に関しては、「整数演算のほうが浮動小数点演算よりも速く、浮動小数点演算のほうがベクターやマトリックス演算やクォータニオン計算よりも速い」ということを覚えておきましょう。

このため、可換演算あるいは結合演算の可能な範囲では常に、 個々の計算処理のコストを最小限に抑えるようにしてください。


Vector3 x;

int a, b;

// 非効率的(ベクターの乗算が 2 回発生する)

Vector3 slow = a * x * b;

// 効率的(整数の乗算が 1 回、 ベクターの乗算が 1 回)

Vector3 fast = a * b * x;

ビルトインの ColorUtility

HTML の文字列方式 (#RRGGBBAA) と Unity ネイティブの ColorColor32 方式との間で色指定の方式の変換を行わなければならないアプリケーションでは、 Unity Community のスクリプトが一般的に使用されています。このスクリプトは速度が遅く、また文字列操作のために相当な量のメモリ割り当てを発生させるものでした。

Unity 5 以降は、この変換を効率的に実行できる ColorUtility API がビルトインで提供されています。可能な限り、こちらの API を使用してください。

Find と FindObjectOfType

基本的には、完成版のコードでは Object.FindObject.FindObjectOfType を一切使用しないことが推奨されます。これらの API を使用すると、 Unity はメモリ内の全てのゲームオブジェクトとコンポーネントの反復処理を行わなければならず、プロジェクトの規模が少し拡大すると効率性が大幅に低下します。

シングルトンオブジェクトのアクセサーの場合はこの限りではありません。グローバル マネージャーオブジェクトは instance プロパティーをアクセス可能にすることがよくあり、シングルトンの既存のインスタンスを検知するためにゲッター関数内に FindObjectOfType コールを持っている場合もよくあります。


class SomeSingleton {

    private SomeSingleton _instance;

    public SomeSingleton Instance {

        get {

            if(_instance == null) { 

                _instance =

                    FindObjectOfType<SomeSingleton>(); 

            }

            if(_instnace == null) { 

                _instance = CreateSomeSingleton();

            }

            return _instance;

        }

    }

}

このパターンは基本的には問題ありませんが、シングルトンのオブジェクトが存在しない場合にアクセサが Scenesで確実に呼び出されるようにするため、入念にコードを調査する必要があります。欠落したシングルトンのインスタンスを getter が自動的に作成しない場合、往々にして、そのシングルトンを探すコードが FindObjectOfType を繰り返し(1 フレームに複数回の場合も多い)呼び出す結果となり、パフォーマンスに余計な負荷が掛かります。

カメラの位置

内部的には、 Unity の Camera.main プロパティーは、 Object.FindObject の特殊なバリアントである Object.FindObjectWithTag を呼び出します。このプロパティーへのアクセスは Object.FindObjectOfType への呼び出しよりも効率的という訳ではありません。どうしてもメインカメラがコードでアドレス指定されなければならない場合は、次の二つのうちのどちらかを行うことが強く推奨されます。

  • Start または OnEnable コールバック内で Camera.main にアクセスし、結果の参照をキャッシュする。

  • アクティブなカメラを提供するか追加できる Camera Manager クラスを作成する。

デバッグコードと [conditional] 属性

UnityEngine.Debug ロギング API は非開発版ビルドが基になったものではなく、呼び出されるとログファイルに書き込みを行います。非開発版ビルドにデバッグ情報を意図的に書き込むことはほとんどありませんので、以下のように、開発版限定のロギングコールをカスタムメソッドでラップすることをお勧めします。


    public static class Logger {

            [Conditional("ENABLE_LOGS")]

            public static void Debug(string logMsg) {

                UnityEngine.Debug.Log(logMsg);

            }

        }

これらのメソッドを [Conditional] 属性で decorate することで、 Conditional 属性の使用する define が、コンパイルされたソース内に decorate されたメソッドが含まれるかどうか決定します。

Conditional 属性に渡された define のどれも定義されていない場合は、 decorate されたメソッド に加えて、 その decorate されたメソッドへの全ての呼び出しがコンパイルされます。この効果は、メソッドとメソッドへの全ての呼び出しが #if … #endif のプリプロセッサ ブロックでラップされた場合と全く同じになります。

Conditional 属性に関する詳細は、 MSDN ウェブサイト msdn.microsoft.com を参照してください。

Resources フォルダー
特殊な最適化