
これは、Unityプロジェクトの最適化のヒントを解説する一連の記事の5番目のものです。より少ないリソースで高いフレームレートで実行するためのガイドとして使用してください。これらのベストプラクティスを試したら、シリーズの他のページもぜひご覧ください:
物理は複雑なゲームプレイを生み出すことができますが、これにはパフォーマンスコストが伴います。これらのコストを理解すれば、シミュレーションを適切に管理するために調整できます。これらのヒントを使用して、目標フレームレート内に収め、Unityのビルトイン物理(NVIDIA PhysX)でスムーズな再生を実現してください。
Unity 6の開発者とアーティスト向けの最新の最適化ガイドをご覧ください:
物理で使用されるメッシュは、調理と呼ばれるプロセスを経ます。これにより、メッシュがレイキャスト、接触などの物理クエリで機能するように準備されます。
MeshColliderには、物理用にメッシュを検証するのに役立ついくつかのCookingOptionsがあります。メッシュにこれらのチェックが必要ないと確信している場合は、調理時間を短縮するためにそれらを無効にできます。
各MeshColliderのCookingOptionsでは、EnableMeshCleaning、WeldColocatedVertices、およびCookForFasterSimulationのチェックを外すだけです。これらのオプションは、ランタイムで手続き的に生成されたメッシュにとって価値がありますが、メッシュに適切な三角形がすでにある場合は無効にできます。
また、PCをターゲットにしている場合は、Use Fast Midphaseを有効に保つことを確認してください。これにより、シミュレーションの中間フェーズ中にPhysX 4.1からのより高速なアルゴリズムに切り替わります(これにより、物理クエリのために潜在的に交差する三角形の小さなセットを絞り込むのに役立ちます)。
詳細はCookingOptionsドキュメントで学べます。

ゲームプレイ中に手続き的にメッシュを生成している場合は、ランタイムでMesh Colliderを作成できます。ただし、メッシュに直接MeshColliderコンポーネントを追加すると、メインスレッドで物理が調理/ベイクされます。これにより、CPU時間が大幅に消費される可能性があります。
Physics.BakeMeshを使用して、MeshColliderで使用するためのメッシュを準備し、メッシュ自体と一緒に焼き付けたデータを保存します。このメッシュを参照する新しいMeshColliderは、この事前焼き付けデータを再利用します(メッシュを再度焼き付けるのではなく)。これにより、シーンのロード時間や後のインスタンス化時間を短縮できます。
パフォーマンスを最適化するために、C#ジョブシステムを使用してメッシュの調理を別のスレッドにオフロードできます。
複数のスレッドでメッシュを焼き付ける方法の詳細については、この例を参照してください。

Player Settingsで、可能な限りPrebake Collision Meshesをチェックしてください。プレイヤーとゲームメカニックオブジェクトが正しいレイヤーにあることを確認するために、Collision Matrixの設定を見直すことをお勧めします。
不要なレイヤーのトリガーからコールバックを削除することは大きな利点になる可能性があるため、Layer Collision Matrixを簡素化するようにしてください。Physics SettingsをProject Settings > Physicsを介して編集できます。
Collision Matrixのドキュメントで詳細を学びましょう。

物理エンジンは、固定時間ステップで実行されることによって機能します。プロジェクトが実行されている固定レートを確認するには、Edit > Project Settings > Timeに移動します。
Fixed Timestepフィールドは、各物理ステップで使用される時間デルタを定義します。例えば、0.02秒(20ms)のデフォルト値は、50fpsまたは50Hzに相当します。
Unityの各フレームは変数の時間を要するため、物理シミュレーションと完全に同期しているわけではありません。エンジンは次の物理時間ステップまでカウントアップします。フレームがわずかに遅くまたは速く実行される場合、Unityは経過時間を使用して、適切な時間ステップで物理シミュレーションを実行するタイミングを知ります。
フレームの準備に長い時間がかかる場合、これがパフォーマンスの問題につながる可能性があります。例えば、ゲームがスパイクを経験する場合(例えば、多くのGameObjectをインスタンス化したり、ディスクからファイルを読み込んだりする場合)、フレームの実行に40ms以上かかることがあります。デフォルトの20ms固定時間ステップでは、次のフレームで「追いつく」ために2つの物理シミュレーションが実行されることになります。
追加の物理シミュレーションは、フレームを処理するための時間をさらに追加します。低性能のプラットフォームでは、これがパフォーマンスの悪化を引き起こす可能性があります。
次のフレームの準備に時間がかかると、物理シミュレーションのバックログも長くなります。これにより、さらに遅いフレームと、フレームごとに実行する必要のあるシミュレーションが増えます。その結果、パフォーマンスがどんどん悪化します。
最終的に、物理更新の間隔が最大許可時間を超える可能性があります。このカットオフの後、Unityは物理更新をドロップし、ゲームがカクつきます。
物理に関するパフォーマンスの問題を避けるために:
必要に応じて、フレームの更新フェーズ中にシミュレーションモードを選択して物理ステップを手動でシミュレートします。これにより、物理ステップを実行するタイミングを制御できます。Time.deltaTimeをPhysics.Simulateに渡して、物理をシミュレーション時間と同期させます。このアプローチは、複雑な物理や非常に変動するフレーム時間を持つシーンで物理シミュレーションの不安定性を引き起こす可能性があるため、注意して使用してください。
Physics.Simulate ドキュメントで詳細を学びます。

Unityの物理エンジンは2つのステップで実行されます:
スイープとプルーンブロードフェーズのデフォルト設定(編集 > プロジェクト設定 > 物理 > ブロードフェーズタイプ)は、一般的に平坦で多くのコライダーを持つワールドに対して誤検出を生成する可能性があります。シーンが大きくほとんど平坦な場合は、この問題を避けるために自動ボックスプルーニングまたはマルチボックスプルーニングブロードフェーズに切り替えてください。これらのオプションは、ワールドをグリッドに分割し、各グリッドセルがスイープとプルーンを実行します。
マルチボックスプルーニングブロードフェーズでは、ワールドの境界とグリッドセルの数を手動で指定できますが、自動ボックスプルーニングはそれを自動的に計算します。
物理プロパティの完全なリストはこちらで確認できます。

特定の物理ボディをより正確にシミュレートしたい場合は、そのRigidbody.solverIterationsを増やしてください。
これはPhysics.defaultSolverIterationsをオーバーライドし、編集 > プロジェクト設定 > 物理 > デフォルトソルバーイテレーションでも見つけることができます。
物理シミュレーションを最適化するには、プロジェクトのdefaultSolveIterationsに比較的低い値を設定してください。次に、詳細が必要な個々のインスタンスに対して高いカスタムRigidbody.solverIterations値を適用します。
Rigidbody.solverIterationsに関する詳細情報を取得します。

デフォルトでは、Unityは物理エンジンとTransformの変更を自動的に同期しません。代わりに、次の物理更新まで待つか、手動でPhysics.SyncTransformsを呼び出します。これが有効になっていると、そのTransformまたはその子のColliderやRigidbodyが物理エンジンと自動的に同期します。
手動で同期するタイミング
autoSyncTransformsが無効になっている場合、UnityはFixedUpdateの物理シミュレーションステップの前またはPhysics.Simulateを介して明示的に要求されたときにのみ変換を同期します。Transformの変更と物理更新の間に物理エンジンから直接読み取るAPIを使用する場合、追加の同期を行う必要があるかもしれません。例としては、Rigidbody.positionにアクセスすることや、Physics.Raycastを実行することが含まれます。
パフォーマンスのベストプラクティス
autoSyncTransformsは最新の物理クエリを保証しますが、パフォーマンスコストがかかります。各物理関連API呼び出しは同期を強制し、特に複数の連続クエリがある場合、パフォーマンスを低下させる可能性があります。これらのベストプラクティスに従ってください:
Physics.SyncTransformsについて詳しく知る。

接触配列は一般的に非常に高速であり、一般的な推奨は衝突コールバックを再利用するのではなく、それらを使用することです。ただし、特定の使用ケースがある場合は次のことを考慮してください。
コールバックMonoBehaviour.OnCollisionEnter、MonoBehaviour.OnCollisionStay、およびMonoBehaviour.OnCollisionExitはすべて衝突インスタンスをパラメーターとして受け取ります。この衝突インスタンスはマネージヒープに割り当てられ、ガーベジコレクションされる必要があります。
生成されるゴミの量を減らすために、Physics.reuseCollisionCallbacksを有効にします(プロジェクト設定 > 物理 > 衝突コールバックの再利用<2>でも見つかります)。これがアクティブな場合、Unityは各コールバックに対して単一の衝突ペアインスタンスのみを割り当てます。これにより、ガーベジコレクタの無駄が減り、パフォーマンスが向上します。
一般的な推奨事項は、パフォーマンスの利点のために常に衝突コールバックの再利用を有効にすることです。この機能は、コードが個々のCollisionクラスインスタンスに依存しているレガシープロジェクトに対してのみ無効にするべきです。個々のフィールドを保存するのが非現実的になります。
Physics.reuseCollisionCallbacksについて詳しく学びます。

静的コライダーは、Rigidbodyを持たないColliderコンポーネントを持つGameObjectです。
「静的」という用語とは反対に、静的コライダーを移動できることに注意してください。そのためには、物理ボディの位置を単純に変更します。位置の変更を蓄積し、物理更新の前に同期します。静的コライダーを移動するために、Rigidbodyコンポーネントを追加する必要はありません。
ただし、静的コライダーが他の物理ボディとより複雑に相互作用するようにしたい場合は、キネマティックRigidbodyを与えます。Transformコンポーネントにアクセスするのではなく、Rigidbody.positionとRigidbody.rotationを使用して移動します。これにより、物理エンジンからのより予測可能な動作が保証されます。
注:個々の静的コライダー2Dをランタイムで移動または再構成する必要がある場合は、Rigidbody 2Dコンポーネントを追加し、静的ボディタイプに設定します。コライダー2Dが独自のRigidbody 2Dを持っていると、シミュレーションが速くなります。ランタイムでコライダー2Dのグループを移動または再構成する必要がある場合は、各GameObjectを個別に移動するよりも、単一の隠れた親Rigidbody 2Dの子としてすべてを持つ方が速くなります。
リジッドボディについての詳細情報を取得します。
特定の距離と特定の方向で3Dプロジェクト内のコライダーを検出して収集するには、レイキャストやBoxCastのような他の物理クエリを使用します。注意してください、
複数のコライダーを配列として返す物理クエリ(OverlapSphereやOverlapBoxなど)は、マネージヒープ上にこれらのオブジェクトを割り当てる必要があります。これは、ガーベジコレクタが最終的に割り当てられたオブジェクトを収集する必要があることを意味し、間違ったタイミングで発生するとパフォーマンスが低下する可能性があります。
このオーバーヘッドを削減するために、NonAllocバージョンのクエリを使用してください。例えば、OverlapSphereを使用してポイント周辺のすべての潜在的なコライダーを収集する場合は、代わりにOverlapSphereNonAllocを使用してください。
これにより、コライダーの配列(結果パラメーター)をバッファとして渡すことができます。NonAllocメソッドは、ガーベジを生成せずに動作します。そうでなければ、対応する割り当てメソッドのように機能します。
NonAllocメソッドを使用する際には、十分なサイズの結果バッファを定義する必要があることに注意してください。バッファは、スペースが不足しても成長しません。
2D 物理演算
上記のアドバイスは、Unityの2D物理システムでは適用されないことに注意してください。なぜなら、2D物理システムのメソッドには「NonAlloc」サフィックスがないからです。代わりに、複数の結果を返すメソッドを含むすべての2D物理メソッドは、配列またはリストを受け入れるオーバーロードを提供します。例えば、3D物理システムにはRaycastNonAllocのようなメソッドがありますが、2Dの同等のものは、配列またはListをパラメーターとして受け取るRaycastのオーバーロードバージョンを単に使用します。
var results = new List();
int hitCount = Physics2D.Raycast(origin, direction, contactFilter, results);
オーバーロードを使用することで、特別なNonAllocメソッドを必要とせずに2D物理システムで非割り当てクエリを実行できます。
NonAllocメソッドのドキュメントで詳細を学んでください。
Physics.Raycastを使用してレイキャストクエリを実行できます。ただし、大量のレイキャスト操作(例:10,000エージェントの視線を計算するなど)がある場合、これにはかなりのCPU時間がかかる可能性があります。
C#ジョブシステムを使用してクエリをバッチ処理するには、RaycastCommandを使用してください。これにより、メインスレッドから作業がオフロードされ、レイキャストが非同期で並行して実行できるようになります。
RaycastCommandsのドキュメントで例を参照してください。
物理デバッグウィンドウ(ウィンドウ > 分析 > 物理デバッガー)を使用して、問題のあるコライダーや不一致をトラブルシューティングします。これは、互いに衝突できるGameObjectの色分けされたインジケーターを示します。
詳細については、物理デバッガーのドキュメントを参照してください。


Unityのベストプラクティスハブから、さらに多くのベストプラクティスやヒントを見つけてください。業界の専門家やUnityのエンジニア、テクニカルアーティストが作成した30以上のガイドから選択し、Unityのツールセットやシステムを使って効率的に開発する手助けをします。