パフォーマンスの問題に様々な原因があるように、コードの最適化方法も様々です。通常、 CPU の最適化を行うに当たっては、事前にアプリケーションの綿密なプロファイリングを行うことが強く推奨されます。ただし、どんな場合にも適用できる CPU 最適化手法もいくつかあります。
Unity は、アニメーターやマテリアルやシェーダーのプロパティのアドレスを内部的に指定する際、文字列名は使用しません。速度を高めるために、全てのプロパティ名はプロパティ ID 内にハッシュ化され、プロパティのアドレス指定にはこの ID が使用されます。
したがって、Set や Get メソッドをアニメーターやマテリアルやシェーダーに使用している場合は、文字列型のメソッドではなく整数型のメソッドを使用してください。文字列型メソッドは単純に、文字列のハッシュ化を実行してから、ハッシュ化された ID をi整数型メソッドに転送します。
文字列のハッシュから作成されたプロパティ ID は、一回の実行中の間において決定的です。この使用方法として最も簡単なのは、静的な読み込み専用整数変数を各プロパティ名について宣言し、その整数変数を文字列の代わりに使用することです。これは、起動時に、更なる追加的な初期化コードを要することなく自動的に初期化されます。
アニメーターのプロパティ名に適した API は Animator.StringToHash、マテリアルとシェーダーのプロパティ名に適した API は Shader.PropertyToID です。
Unity 5.3 以降は、Physics クエリ API の割り当てなしのバージョンが提供されています。例えば RaycastAll コールは RaycastNonAlloc に置き換え、 SphereCastAll コールは SphereCastNonAlloc に置き換えることができます。 2D アプリケーションの場合は、全ての Physics2D クエリー API の割り当てなしのバージョンもあります。
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;
HTML の文字列方式 (#RRGGBBAA
) と Unity ネイティブの Color
や Color32
方式との間で色指定の方式の変換を行わなければならないアプリケーションでは、 Unity Community のスクリプトが一般的に使用されています。このスクリプトは速度が遅く、また文字列操作のために相当な量のメモリ割り当てを発生させるものでした。
Unity 5 以降は、この変換を効率的に実行できる ColorUtility API がビルトインで提供されています。可能な限り、こちらの API をご使用ください。
基本的には、完成版のコードでは Object.Find
と Object.FindObjectOfType
は一切使用しないことが推奨されます。これらの API を使用すると、 Unity はメモリ内の全てのゲームオブジェクトとコンポーネントの反復処理を行わなければならず、プロジェクトの規模が少し拡大すると効率性が大幅に低下します。
シングルトン オブジェクトのアクセサの場合はこの限りではありません。グローバル マネージャー オブジェクトは “instance” プロパティをアクセス可能にすることがよくあり、シングルトンの既存のインスタンスを検知する getter 内に 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
クラスを作成する。
UnityEngine.Debug
ロギング API は非開発版ビルドが基になったものではなく、呼び出されるとログファイルに書き込みを行います。非開発版ビルドにデバッグ情報を意図的に書き込むことはほとんどありませんので、以下のように、開発版限定のロギングコールをカスタムメソッドでラップすることをお勧めします。
public static class Logger {
[Conditional("ENABLE_LOGS")]
public void Debug(string logMsg) {
Debug.Log(logMsg);
}
}
これらのメソッドを [Conditional] 属性で decorate することで、 Conditional 属性の使用する define が、コンパイルされたソース内に decorate されたメソッドが含まれるかどうか決定します。
Conditional 属性に渡された define のどれも定義されていない場合は、 decorate されたメソッド に加えて、 その decorate されたメソッドへの全ての呼び出しがコンパイルされます。この効果は、メソッドとメソッドへの全ての呼び出しが #if … #endif
のプリプロセッサ ブロックでラップされた場合と全く同じになります。
Conditional
属性に関する詳細は、 MSDN ウェブサイト msdn.microsoft.com でご確認ください。