Hero background image
高度なプログラミングとコードアーキテクチャ
グラフィックスのレンダリングをさらに最適化するために、コードアーキテクチャを探求してください。今回は、Unityプロジェクトの最適化のヒントを紹介する連載の第4回目です。より少ないリソースでより高いフレームレートで動作させるためのガイドとしてご利用ください。これらのベストプラクティスを試したら、シリーズの他のページもぜひご覧ください:パフォーマンス向上のためのUnityプロジェクトの設定 ハイエンドグラフィックスのパフォーマンス最適化 PCおよびコンソールゲームのGPU使用量の管理 スムーズなゲームプレイのための物理演算パフォーマンスの向上
このページは機械翻訳されています。正確性のため、また情報源として原語バージョンを表示するには
UnityのPlayerLoopを理解しましょう

UnityのPlayerLoopには、ゲームエンジンのコアと対話するための関数が含まれています。この構造には、初期化とフレームごとの更新を処理する多くのシステムが含まれています。すべてのスクリプトは、このPlayerLoopに依存してゲームプレイを作成します。プロファイリングを行うと、プロジェクトのユーザーコードがPlayerLoopの下に表示され、EditorコンポーネントはEditorLoopの下に表示されます。

UnityのFrameLoopの 実行順序を理解することは重要です。すべてのUnityスクリプトは、いくつかのイベント関数を決められた順序で実行します。AwakeStartUpdateなど、スクリプトのライフサイクルを作成する関数の違いを学び、パフォーマンスを強化します。

例えば、Rigidbodyを扱うときにUpdateではなくFixedUpdateを使ったり、ゲーム開始前に変数やゲームの状態を初期化するときにStartではなくAwakeを使ったりします。これらを使って、各フレームで実行されるコードを最小限に抑えます。Awakeはスクリプトのインスタンスが生きている間に一度だけ呼び出され、常にStart関数の前に呼び出されます。つまり、他のオブジェクトと会話できることが分かっているオブジェクトを扱うときや、初期化されたオブジェクトに問い合わせるときは、Startを使用する必要があります。

イベント関数の具体的な実行順序については、スクリプトのライフサイクルのフローチャートを参照してください。

カスタム Update マネージャーの図
カスタム・アップデート・マネージャーの構築

プロジェクトに厳しいパフォーマンス要件がある場合(オープンワールドゲームなど)、UpdateLateUpdate、またはFixedUpdateを使用してカスタムUpdate Managerを作成することを検討してください。

UpdateまたはLateUpdateの一般的な使用パターンは、何らかの条件が満たされたときにのみロジックを実行することです。このため、この条件をチェックする以外、事実上何のコードも実行しないフレームごとのコールバックが多数発生する可能性があります。

UnityがUpdateやLateUpdateのようなメッセージメソッドを呼び出すときはいつも、インターオプコール、つまりC/C++側からマネージドC#側へのコールを行います。少数のオブジェクトについては、これは問題ではありません。何千ものオブジェクトがあると、このオーバーヘッドが大きくなってきます。

アクティブなオブジェクトがコールバックを必要とする場合は、この Update Manager にサブスクライブし、そうでない場合はアンサブスクライブします。このパターンによって、Monobehaviourオブジェクトへのインターオプ呼び出しの多くを減らすことができます。

実装例については、ゲームエンジン固有の最適化テクニックを参照してください。

毎フレーム実行されるコードの最小化

コードが毎フレーム実行されなければならないかどうかを検討してください。Update、LateUpdate、FixedUpdateから不要なロジックを削除できます。これらのUnityイベント関数は、毎フレーム更新しなければならないコードを置くのに便利な場所ですが、その頻度で更新する必要のないロジックを抽出することができます。

ロジックを実行するのは、状況が変化したときだけです。特定の関数シグネチャをトリガーするイベントの形で、オブザーバーパターンなどのテクニックを活用することを忘れないでください。

Updateを使用する必要がある場合は、nフレームごとにコードを実行します。これは、重いワークロードを複数のフレームに分散させる一般的な手法であるタイムスライシングを適用する方法の1つです。

この例では、ExampleExpensiveFunctionを3フレームに1回実行します。

コツは、他のフレームで実行される他の作業と、この作業を織り交ぜることです。この例では、Time.frameCount % interval == 1またはTime.frameCount % interval == 2のときに、他の高価な関数を「スケジュール」することができます。

あるいは、カスタム・アップデート・マネージャ・クラスを使用して、サブスクライブされたオブジェクトをnフレームごとに更新します。

高価な関数の結果をキャッシュ

2020.2より前のバージョンのUnityでは、GameObject.FindGameObject.GetComponentCamera.mainが高価な場合があるので、Updateメソッドで呼び出すのは避けた方が良いでしょう。

さらに、OnEnableと OnDisableに高価なメソッドを置くのは避けましょう。これらのメソッドを頻繁に呼び出すと、CPUスパイクの原因となります。

可能な限り、次のような高価な関数を実行します。 のような高価な関数を実行します。MonoBehaviour.Startなどの高価な関数を初期化フェーズで実行します。必要な参照をキャッシュし、後で再利用します。スクリプトの実行順序の詳細については、以前のUnity PlayerLoopのセクションをチェックしてください。

繰り返されるGetComponent呼び出しの非効率的な使用を示す例を示します:

void 更新()
{
Renderer myRenderer = GetComponent<Renderer>();
ExampleFunction(myRenderer);
}

代わりに、関数の結果がキャッシュされるため、GetComponentを1回だけ呼び出します。キャッシュされた結果は、GetComponent を再度呼び出すことなく Update で再利用できます。

イベント機能の実行順序については、こちらをご覧ください。

空のUnityイベントとデバッグ・ログ文の回避

ログステートメント(特にUpdate、LateUpdate、FixedUpdate)はパフォーマンスを低下させる可能性があるため、ビルドを行う前にログステートメントを無効にしてください。これを素早く行うには 条件属性を作ることを検討してください。

例えば、以下のようなカスタム・クラスを作成したいとします。

カスタムクラスでログメッセージを生成します。 プレイヤー設定>スクリプトのシンボル定義で ENABLE_LOGプリプロセッサを無効にすると、すべてのログステートメントが一挙に消えます。

文字列やテキストの扱いは、Unityプロジェクトでパフォーマンスの問題を引き起こす一般的な原因です。そのため、ログ文とその高価な文字列書式を削除することで、パフォーマンスが大きく向上する可能性があります。

同様に、空のMonoBehavioursはリソースを必要とするので、空白のUpdateまたはLateUpdateメソッドを削除する必要があります。テストにこれらの方法を使う場合は、プリプロセッサ・ディレクティブを使ってください:

#if UNITY_EDITOR
void 更新()
{
}
#endif

ここでは、不要なオーバーヘッドがビルドに入り込むことなく、Update in Editorをテストに使用できます。

10,000回のUpdateコールに関するこのブログ記事では、UnityがどのようにMonobehaviour.Updateを実行するかについて説明しています。

スタックトレースのロギングを無効にします。

プレーヤー設定の スタックトレースオプションを使用して、表示されるログメッセージのタイプを制御します。アプリケーションがリリースビルドでエラーや警告メッセージを記録している場合(例えば、クラッシュレポートを生成するため)、パフォーマンスを向上させるためにスタックトレースを無効にしてください。

スタックトレースのロギングについてはこちらをご覧ください。

文字列パラメータの代わりにハッシュ値を使用

Unityでは、AnimatorMaterialShaderのプロパティのアドレスに文字列名を使用することはありません。高速化のために、すべてのプロパティ名はプロパティIDにハッシュ化され、これらのIDはプロパティをアドレス指定するために使用されます。

アニメーター、マテリアル、シェーダーでSetメソッドやGetメソッドを使用する場合は、文字列値のメソッドではなく、整数値のメソッドを活用してください。文字列値メソッドは文字列のハッシュを実行し、ハッシュされた ID を整数値メソッドに転送します。

使用 を使用します。を使います。 Shader.PropertyToIDを使用します。

関連するのはデータ構造の選択で、これはフレームごとに何千回も反復するため、パフォーマンスに影響します。正しい構造を選択するための一般的なガイドとして、C#におけるデータ構造に関するMSDNガイドに従ってください。

オブジェクトプールのスクリプトインターフェース
オブジェクトをプール

Instantiateと Destroyはガベージコレクション(GC)スパイクを発生させる可能性があります。これは一般的に時間のかかる処理なので、GameObjectを定期的にインスタンス化して破棄する(銃で弾を撃つなど)のではなく、再利用やリサイクルが可能な事前割り当てオブジェクトのプールを使用します。

再利用可能なインスタンスは、CPUスパイクが目立ちにくい、メニュー画面やローディング画面のようなゲーム中の時点で作成します。このオブジェクトの「プール」をコレクションで追跡します。ゲームプレイ中は、必要なときに次の利用可能なインスタンスを有効にし、オブジェクトを破壊する代わりに無効にしてからプールに戻すだけです。これは、プロジェクト内の管理された割り当ての数を減らし、GC問題を防ぐことができます。

同様に、実行時にコンポーネントを追加することは避けてください。Unityは、実行時にコンポーネントを追加するたびに、重複するコンポーネントやその他の必要なコンポーネントをチェックする必要があります。必要なコンポーネントがすでにセットアップされたPrefabをインスタンス化する方がよりパフォーマンスが高いのでオブジェクトプールと組み合わせて使用してください。

関連して、Transformを移動させるときは Transform.SetPositionAndRotationを使用して、位置と回転の両方を一度に更新します。これにより、Transformを2回修正するオーバーヘッドを避けることができます。

実行時にGameObjectをインスタンス化する必要がある場合、最適化のために親オブジェクトと再配置する必要があります。

Object.Instantiate の詳細については、スクリプティング API を参照してください。

Unityでシンプルなオブジェクトプーリングシステムを作成する方法はこちらをご覧ください。

スクリプタブルオブジェクトプール
ScriptableObjects のパワーを活用しましょう。

MonoBehaviour の代わりに ScriptableObject に不変の値や設定を保存します。ScriptableObject はプロジェクト内に存在するアセットです。一度だけ設定する必要があり、GameObjectに直接アタッチすることはできません。

ScriptableObject にフィールドを作成して値や設定を保存し、MonoBehaviours で ScriptableObject を参照します。ScriptableObjectのフィールドを使用することで、そのMonoBehaviourを持つオブジェクトをインスタンス化するたびにデータが重複するのを防ぐことができます。

ScriptableObjects入門の チュートリアルをご覧ください。

ユニティ・キー・アート21 11
無料電子書籍を入手

これまでで最も包括的なガイドの1つで、PCとコンソール向けにゲームを最適化する方法について、80以上の実用的なヒントを集めています。当社のエキスパートであるサクセスとアクセラレート・ソリューションズのエンジニアが作成したこれらの詳細なヒントは、Unityを最大限に活用し、ゲームのパフォーマンスを向上させるのに役立ちます。

このコンテンツにご満足いただけましたか?