パフォーマンスの問題に様々な原因があるように、コードの最適化方法も様々です。通常、 CPU の最適化を行うに当たっては、事前にアプリケーションの綿密なプロファイリングを行うことが強く推奨されます。ただし、どんな場合にも適用できる CPU 最適化もいくつかあります。
Unity は、アニメーター、マテリアル、シェーダーのプロパティーを内部的に表す際、文字列による名は使用しません。速度を高めるために、全てのプロパティー名はプロパティ ID 内でハッシュ化され、プロパティーを示すにはこの ID が使用されます。
したがって、Set や Get メソッドをアニメーター、マテリアル、シェーダーなどに使用する場合は、string 型ではなく int 型のメソッドを使用してください。string 型メソッドは単純に、文字列をハッシュにした ID を int 型メソッドに転送します。
文字列のハッシュから作成されたプロパティー ID は、1回の実行中の間において有効です。この使用方法として最も簡単なのは、静的な読み込み専用の int 変数を各プロパティー名について宣言し、その int 変数を文字列の代わりに使用することです。これは、起動時に、追加の初期化コードなしに自動的に初期化されます。
アニメーターのプロパティー名に適した 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 プロパティーをアクセス可能にすることがよくあり、シングルトンの既存のインスタンスを検知するためにゲッター関数内に 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 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 を参照してください。