見出し画像

[翻訳・記事紹介]Roblox開発におけるパフォーマンス最適化について Part3(計算された視界・距離による処理の除外)

参照元リンク:https://devforum.roblox.com/t/real-world-building-and-scripting-optimization-for-roblox/3127146

前回の記事はこちら

Part3では記事のChapter 3/4/5を紹介していきます。


Chapter3 : 見える範囲を事前に計算する

見えないジオメトリ

残念ながら、Robloxでは壁の裏にあるジオメトリもレンダリングパフォーマンスに影響を与えます。

この後ろに他の30のZoneがレンダリングされている!
見えないジオメトリは取り除いた方が良いよね?

現在RobloxではStreamingを使えば自分の周りの空間から遠くにあるオブジェクトを取り除けるため、使用するのをかなり推奨しています。

ただしまだ改善の余地があり、このマップに関しても壁で見えない隣のゾーンが描画されるため、Streamingだけでは不十分でした。

Robloxはこの問題を解決する機能を開発中ですが、それができるまでは「事前計算された視界」を使って見えないゾーンを非表示にしました。

この技術は非常にシンプルです。マップはゾーンで構成されており、各ゾーンの間には大きな壁があります。
例えば、プレイヤーが赤いゾーンにいる場合、オレンジ色のゾーンだけが見えるはずで、黄色のゾーンは壁の後ろに隠れて見えないようにします。

赤枠のゾーンにいるときは黄色のエリアは見えなくなります。
別のアングル

Parentをnilにすることでマップを見えなくする

残念ながら、モデルやフォルダーに対して直接的な「表示」プロパティはありません。しかし、クライアント側で Parent プロパティを nil に一時的に設定することで、これをシミュレートできます。この手法を使って、プレイヤーが立っていないゾーンを完全に非表示にします。

注意:クライアントとサーバーの見え方を非同期にすることはとても上級向けのトピックです。主に知っておくべきことはクライアントサイドのプロパティははサーバーサイドで変更が行われない限り、状態を維持できるということです。

ゾーンマネージャー

このプロジェクトではZoneMangerというゾーンを管理するためのシンプルなシステムが含まれています。ゾーンのモデルはそれぞれ「Zone」とタグ付けされており、それぞれのモデルには境界を決めるための四角形のPartがあります。そしてAttributeにはそのゾーンにいるときにどのゾーンが見えるべきかを設定してあります。

実行時には、最終的な手法はシンプルです。
・ 起動時に、すべてのゾーンを CollectionService を使って大きなリストに追加します。
・ 毎フレームごとに、プレイヤーがどのゾーンにいるかをその境界部分を使って確認します。
・ ゾーンが変わった場合、見えないゾーンをすべてデペアレント(親から外す)し、見えるゾーンをすべてペアレント(親にする)します。

注意: 大量のインスタンスのペアレントを一度にセットする場合、特にUnionのようなインスタンスではCPUに負荷がかかることがあるため、頻度を抑える必要があります。

Chapter3のまとめ

見えないゾーンをクライアント側で nil に設定して隠す。

Chapter4:距離に応じた不要な処理の除外

このマップには、石ころや草むら、コインの穴の中のコインなど、非常に小さなディテールがたくさんあります。これらの小さなオブジェクトは、カメラから数ゾーン以上離れるとほとんど見えなくなることに気づくでしょう。Robloxは時々、遠くのモデルの三角形の数を減らすことができ、ストリーミングが有効な場合は、特定の範囲を超えたものをすべて削除できますが、自分でできるより信頼性の高い手法として「ディスタンスカリング(距離に応じた削除)」があります。

遠くに行くとほとんどこのサイズのオブジェクトは見えない!

Chapter3で行った手法に似た方法を使用して、マップ上のすべての小さなオブジェクトに CollectionService を使用して「Detail_Small」のタグを付けました。

その後、DistanceCullingManager が起動時にすべてのディテールオブジェクトを収集し、カメラから300スタッド以上離れている場合は削除します。

この方法では、見えないオブジェクトを nil にペアレントし、見えるようになったときに再びペアレントします。

ここでの一つの工夫は、すべてのフレームで距離チェックを実行しないことです。なぜなら、世界には約250個のディテールオブジェクトがあり、毎フレームチェックするのはコストがかかるからです。そこで、カメラが前回の更新から少し動くまでチェックを遅らせ、さらに更新を複数のフレームに分散させるため、1フレームあたり50個以上のチェックをしないようにしています。この処理にはコルーチンを使用しています。

注意: ディスタンスカリングは、ゾーンマネージャーとの互換性が完全にあります。ゾーンが nil にペアレントされていても、ディテールをペアレントおよびデペアレントすることができます。

Chapter4のまとめ

• 非常に小さなディテールに「Detail_Small」タグを付け、300スタッド以上離れている場合には表示を消しましょう。

• コストのかかるループ処理は、毎フレーム処理するのではなく、複数のフレームにわたって更新できるように書くことを検討してください。

Chapter5 : コインの動きと当たり判定

*このプロジェクトではコインの近くに行くと吸い込まれるような仕様になっているため、
その空間的処理をするための方法になります。

コインやアイテムの取得は、シミュレーターゲームの楽しさを大きく左右する要素です。
しかし、サーバーでコインを生成し、サーバー側で衝突判定を行うと、コインが遅延したり、帯域幅を多く消費してしまう可能性があります。特に、スピニングアニメーションがある場合は、その問題が顕著になります。
一方で、コインをローカルで生成するだけだと、CPUの使用量が多すぎたり、プレイヤーにチートされる恐れがあります。

このプロジェクトでは、サーバーがイベントを送信してクライアントでコインを生成し、描画と取得のロジックを高速化するために2つの最適化を使用しています。

コインの生成と消去

コインの生成と消去は、リモートイベントを介して行います。コインはサーバー上で実際のオブジェクトとして存在せず、複製もされません。各コインはクライアントでローカルで生成され、シリアル番号を使用することでチートを防止します。

以下はコイン生成から消去のフローです。

  1. サーバー側:

    1. サーバーでコインはtable内のレコードとして作成されます。

    2. コインを所有するクライアントに対して、そのコインのユニークなシリアル番号とともに生成するためのイベントが発火されます。

  2. クライアント側:

    1. クライアントがコインイベントを受け取ると、コインがキューに入ります。これにより、コインの生成が数フレームにわたってビジュアルエフェクトと共に行われます。

    2. コインが生成キューを抜けると、コインモデルがローカルで複製され、実際の物理演算を使って少しの間跳ね回ります。

    3. その後、跳ね回る時間が経過すると、コインはアンカーされ、それ以上の衝突や物理処理時間を消費しなくなります。

    4. プレイヤーがコインを収集すると、アニメーションはクライアントで行われ、収集イベントがコインのシリアル番号とともにサーバーに送信されます。

  3. サーバー側:

    1. サーバーがプレイヤーがコインを収集したというイベントを受け取ったときにシリアル番号をチェックします、そのレコードを削除し、そのコインは「終了」とします。

    2. (このタイミングでサーバーでプレイヤーがコインを持ちすぎていると判断した場合、コインを削除するイベントを送信することもできます。)

コインの転がりと回転の動き

このプロジェクトでは、コインが収集可能になる前に少し跳ね回るようにしました。これを実現するために、コインは発射後に物理的に転がる小さな立方体にしました。見た目としては、毎フレーム、コインが見えない物理的な立方体の場所にレンダリングされます。

コインは2種類のオブジェクトで構成されており、
クライアントサイドの小さなAnchorされていない立方体とAnchorされているMeshです。

この処理は、BulkMoveTo APIを使用してすべてのコインメッシュを更新することで行われます。BulkMoveToを使用すると、クライアントで多くのパーツを毎フレーム安価なCPU使用率で移動させることが可能です。

高速なコインの当たり判定チェック

ワールドには数百個のコインが存在する可能性があるため、プレイヤーとすべてのコインの間で毎フレーム距離チェックを行うことは避けたいです。
そのため、「Spatial Hash」と呼ばれる加速構造を使用して衝突チェックを高速化しました。これは、Vector3オブジェクトをRobloxのテーブルのキーとして使用することで、コインを「セル」のグリッドに非常に迅速に割り当てることができるためです。

各セルは、その中にあるすべてのコインのリストを保持します。コインは移動するにつれてセルを更新します。
200個すべての距離を確認するのではなく、近くのコインの距離のみを確認します。
クライアント上のコインのロジックはすべてこのスクリプト内にあります。

補足: GetPartBoundsInRadiusを使って当たり判定をチェックすることもできますが、テストの結果この空間グリッドを使った方が高速化できました。

Chapter5のまとめ

  • イベントを使用してコインの生成と消去を管理する。

  • 物理演算を使用してコインを跳ねさせる。

  • BulkMoveToを使用してクライアントでスピンするコインを安価にレンダリングする。

  • 空間グリッドを使用して取得ロジックを非常に高速化する。


この記事が気に入ったらサポートをしてみませんか?