このページでは、一般的なパフォーマンスの問題とそれを軽減するためのベストプラクティスについて説明します。
スクリプトの計算
Luauコード内のコストのかかる操作は処理に時間がかかり、フレームレートに影響を与える可能性があります。並行して実行されない限り、Luauコードは同期的に実行され、スレッドが生成関数に遭遇するまでメインスレッドをブロックします。
一般的な問題
テーブル構造に対する集中的な操作 - シリアル化、非シリアル化、深いクローンのような複雑な操作は、特に大規模なテーブル構造において高いパフォーマンスコストがかかります。特にこれらの操作が再帰的または非常に大きなデータ構造を反復処理する場合はそうです。
高頻度のイベント - RunServiceのフレームベースのイベントに高コストの操作を結びつけることは、頻度を制限しない場合、これらの操作が毎フレーム繰り返され、計算時間の不必要な増加を引き起こすことがあります。これらのイベントには以下が含まれます:
軽減策
- RunServiceのイベントでのコードの呼び出しは控えめにし、高頻度の呼び出しが必要な場合(例えばカメラを更新する場合)に制限します。その他のコードは、他のイベントやループ内でより頻繁に実行できます。
- task.wait()を使用して大きなまたは高コストのタスクを分割し、複数のフレームに作業を分散させます。
- 不必要に高コストの操作を特定して最適化し、データモデルへのアクセスを必要としない計算コストの高いタスクにはmultithreadingを使用します。
- 一部のサーバーサイドスクリプトは、バイトコードではなくマシンコードにスクリプトをコンパイルする簡単なフラグであるnative code generationの恩恵を受けることができます。
MicroProfilerスコープ
| スコープ | 関連する計算 |
| RunService.PreRender | PreRenderイベントで実行されるコード |
| RunService.PreSimulation | Steppedイベントで実行されるコード |
| RunService.PostSimulation | Heartbeatイベントで実行されるコード |
| RunService.Heartbeat | Heartbeatイベントで実行されるコード |
MicroProfilerを使用したスクリプトのデバッグについての詳細は、特定のコードをタグ付けし、debug.profilebeginやdebug.profileendのようなさらなる特異性を高めるための関数を含むdebugライブラリを参照してください。スクリプトによって呼び出される多くのRoblox APIメソッドには、有用な信号を提供できる独自のMicroProfilerタグもあります。
スクリプトのメモリ使用量
メモリリークは、ガベージコレクターがもはや使用されていない時にメモリを適切に解放できないスクリプトを書くと発生する可能性があります。リークは特にサーバー側で広がる傾向があります、なぜならそれらは何日もオンラインである可能性があるため、クライアントセッションははるかに短いからです。
Developer Console内の以下のメモリ値は、さらなる調査が必要な問題を示す可能性があります:
- LuaHeap - 高いまたは増加する消費量は、メモリリークを示唆します。
- InstanceCount - 一貫して増加するインスタンス数は、コード内のいくつかのインスタンスへの参照がガベージコレクションされていないことを示唆します。
- PlaceScriptMemory - スクリプトごとのメモリ使用量の内訳を提供します。
一般的な問題
接続されたコネクションを放置する - エンジンは、インスタンスに接続されたイベントおよび接続されたコールバック内の任意の値に対してガベージコレクションを行いません。したがって、接続されたイベントと、接続されたインスタンス内のコード、接続された関数、および参照された値のアクティブな接続は、イベントが発火した後であっても、メモリガベージコレクターのスコープ外となります。
インスタンスが破壊されるとイベントが切断されますが、一般的な誤解は、これがPlayerオブジェクトに適用されるというものです。ユーザーがゲームを離れた後、エンジンは自動的に彼らの代表的なPlayerオブジェクトとキャラクターモデルを破壊しないため、Playerオブジェクトおよびキャラクターモデルの下のインスタンスへの接続は、スクリプト内でそれらを切断しない限り引き続きメモリを消費します。 これは、何百人ものユーザーがゲームに参加したり離れたりする中で、サーバー上で非常に重要なメモリリークを引き起こす可能性があります。
テーブル - オブジェクトをテーブルに挿入したまま、もはや必要でないときにそれらを削除しないと、特にユーザーデータを追跡するテーブルにおいて不必要なメモリ消費が引き起こされます。例えば、以下のコードサンプルは、ユーザーが参加するたびにユーザー情報を追加するテーブルを作成します:
例local playerInfo = {}Players.PlayerAdded:Connect(function(player)playerInfo[player] = {} -- 一部の情報end)これらのエントリをもはや必要でないときに削除しない場合、テーブルはサイズが増え続け、セッションに新しいユーザーが参加するたびにより多くのメモリを消費します。このテーブルを反復処理するコードも、テーブルが大きくなると計算コストが高くなります。
軽減策
メモリリークを防ぐために使用された値をクリーンアップするには:
すべての接続を切断する - コードベースを確認し、各接続が次のいずれかの方法でクリーンアップされていることを確認します:
- Disconnect()関数を使用して手動で切断します。
- Destroy()関数でイベントに属するインスタンスを破壊します。
- 接続の起点となるスクリプトオブジェクトを破壊します。
プレイヤーオブジェクトとキャラクターを離脱後に削除する - Workspace.PlayerCharacterDestroyBehaviorを有効にして、ユーザーが離れた後にプレイヤーオブジェクトとキャラクターモデルを自動的に破壊します。代わりに手動でクリーンアップすることもできます:
例 プレイヤーおよびキャラクターのクリーンアップlocal Players = game:GetService("Players")Players.PlayerAdded:Connect(function(player)player.CharacterRemoving:Connect(function(character)task.defer(character.Destroy, character)end)end)Players.PlayerRemoving:Connect(function(player)task.defer(player.Destroy, player)end)
物理計算
過剰な物理シミュレーションは、サーバーとクライアントのどちらにおいても、フレームごとの計算時間の増加の主な原因となることがあります。
一般的な問題
過剰な物理時間ステップの頻度 - デフォルトでは、ステップ動作はアダプティブモードで、物理は60Hz、120Hz、または240Hzでステップします。これは、物理メカニズムの複雑性によって異なります。
すべての物理アセンブリが240Hz(フレームごとに4回)でステップするように強制される固定モードも利用可能で、これにより各フレームの計算量が大幅に増加します。
シミュレートされたオブジェクトの複雑さの過剰な数 - シミュレートされる3Dアセンブリの数が多いほど、各フレームの物理計算に時間がかかります。ゲームには、不必要にシミュレーションされるオブジェクトが含まれたり、必要以上に多くの制約や関節を持つメカニズムが含まれることがあります。
過度に精密な衝突検出 - メッシュパーツには、衝突を検出するためのCollisionFidelityプロパティがあり、異なるパフォーマンスへの影響を持つさまざまなモードが提供されます。メッシュパーツの精密衝突検出モードは最もコストが高く、エンジンが計算するのに時間がかかります。
軽減策
シミュレーションを必要としないパーツを固定する - 物理によって駆動される必要のないすべてのパーツを固定します。これは静的NPCのためです。
アダプティブ物理ステッピングを使用する - アダプティブステッピングは、物理メカニズムの物理計算のレートを動的に調整し、必要に応じて物理更新をより少なく行います。
メカニズムの複雑さを減らす
- 可能な限り、アセンブリ内の物理制約や関節の数を最小限にします。
- 自己衝突がメカニズム内で発生しないように、ダメージを防ぐ限界や衝突制約を適用します。
メッシュの精密な衝突忠実度の使用を減らす
小さなまたは相互作用がほとんどないオブジェクトの場合、ボックス忠実度を使用します。
小または中程度のサイズのオブジェクトの場合、形状に応じてボックスまたはハル忠実度を使用します。
大きくて非常に複雑なオブジェクトの場合、可能であれば見えないパーツを使用してカスタム衝突を構築します。
衝突が不要なオブジェクトに対しては、衝突を無効にし、ボックスまたはハル忠実度を使用します。衝突ジオメトリはメモリにまだ保存されています。
Studioでデバッグ目的で衝突ジオメトリをレンダリングするには、3Dビューポートの右上隅にあるVisualization OptionsウィジェットからCollision fidelityをオンにします。
あるいは、ExplorerにCollisionFidelity=PreciseConvexDecompositionフィルターを適用し、精密忠実度でのすべてのメッシュパーツのカウントを表示し、それらを簡単に選択できるようにします。
精密さとパフォーマンス要件のバランスを取る衝突忠実度オプションの選び方についての詳細な手順については、物理およびレンダリングパラメータを設定するを参照してください。
MicroProfilerスコープ
| スコープ | 関連する計算 |
| physicsStepped | 全体の物理計算 |
| worldStep | 各フレームで行われる離散的な物理ステップ |
物理メモリ使用量
物理移動と衝突検出はメモリを消費します。メッシュパーツは、メッシュの衝突境界を評価するためのアプローチを決定するCollisionFidelityプロパティを持っています。
一般的な問題
デフォルトおよび精密衝突検出モードは、他の2つの忠実度の低い衝突形状と比較して、かなり多くのメモリを消費します。
PhysicsPartsの下で高いメモリ消費レベルがある場合、ゲーム内のオブジェクトの衝突忠実度を減らす必要があるかもしれません。
軽減策
衝突忠実度に使用されるメモリを減らすには:
- CollisionFidelity設定を使用して衝突の忠実度を減らします。Boxは最もメモリのオーバーヘッドが低く、DefaultおよびPreciseは一般的により高価です。
- 小さな固定パーツの衝突忠実度をBoxに設定するのは一般的に安全です。
- 非常に複雑で大きなメッシュの場合、ボックス衝突忠実度を持つ小さなオブジェクトを使用して独自の衝突メッシュを作成することを検討してください。
人型キャラクター
Humanoidは、プレイヤーキャラクターおよび非プレイヤーキャラクター(NPC)に幅広い機能を提供するクラスです。強力ですが、Humanoidはかなりの計算コストがかかります。
一般的な問題
- NPCにすべてのHumanoidStateTypesを有効にしたままにする - 特定のHumanoidStateTypesを有効にするとパフォーマンスにコストがかかる場合があります。NPCに不要なものは無効にしてください。例えば、NPCが梯子を登る予定がない場合は、Climbing状態を無効にするのが安全です。
- Humanoidsが必須でない場合に使用する - 移動しない静的NPCは、一般的にHumanoidクラスを必要としません。
- サーバーから多くのNPCにアニメーションを再生する - サーバーで実行されるNPCアニメーションは、サーバー上でシミュレーションされ、クライアントに複製する必要があります。これは不必要なオーバーヘッドにつながります。
- 不要なサイズおよびスケールの変更を行うこと - サイズ/スケールの変更はFastClusterの再構築を引き起こします。これが見られる場合は、ゲームプレイ中にこれを減らすよう努めてください。同様に、他のプロパティの変更もFastClusterが再構築される可能性があるため、一般的にこれらの変更をできるだけ減らすようにしてください。
軽減策
- NPCのアニメーションをクライアントで再生する - 多くのNPCがいるゲームでは、クライアントでAnimatorを作成し、ローカルでアニメーションを実行することを検討してください。これにより、サーバーへの負荷が軽減され、不必要な複製が不要になります。また、近くのキャラクター用のアニメーションのみを再生するなど、さらなる最適化を可能にします。
- Humanoidに対するパフォーマンスに優しい代替手段を使用する - NPCモデルは、必ずしもヒューマノイドオブジェクトを含む必要はありません。
- 静的NPCの場合は、単純なAnimationControllerを使用してください。移動が必要ないが、アニメーションを再生する必要があります。
- 移動するNPCの場合は、NPCの複雑さに応じて独自の移動コントローラを実装し、アニメーションにはAnimationControllerを使用します。
- 未使用のヒューマノイド状態を無効にする - 各ヒューマノイドに必要な状態のみを有効にするためにHumanoid:SetStateEnabled()を使用します。
- 頻繁に再生されるNPCモデルをプールする - NPCを完全に破壊するのではなく、非アクティブなNPCのプールに送信します。新しいNPCが再生する必要があるときは、プールからNPCの1つを単に再アクティブにすれば済みます。このプロセスはプーリングと呼ばれ、キャラクターのインスタンス化の回数を最小限に抑えます。
- ユーザーが近くにいるときのみNPCをスポーンする - ユーザーが範囲外にいるときにNPCをスポーンせず、ユーザーが範囲を離れたときにカットします。
- インスタンス化された後にアバターヒエラルキーに変更を加えない - アバターヒエラルキーへの特定の変更は、重要なパフォーマンスに影響を与えます。利用可能な最適化がいくつかあります:
- カスタム手続きアニメーションの場合、JointInstance.C0およびJointInstance.C1プロパティを更新しないでください。代わりに、Motor6D.Transformプロパティを更新します。
MicroProfilerスコープ
| スコープ | 関連する計算 |
| stepHumanoid | ヒューマノイド制御と物理 |
| stepAnimation | ヒューマノイドとアニメーターのアニメーション |
| updateInvalidatedFastClusters | アバターのインスタンス化または修正に関連 |
レンダリング
クライアントが各フレームで費やす時間の相当部分は、現在のフレーム内のシーンをレンダリングすることにあります。サーバーはレンダリングを行わないため、このセクションはクライアント専用です。
描画コール
描画コールは、エンジンからGPUへ何かをレンダリングするための指示セットです。描画コールには重要なオーバーヘッドがあります。一般的に、フレームごとの描画コールが少ないほど、フレームのレンダリングに費やされる計算時間が少なくなります。
現在の描画コールの数を見るには、StudioのRender Stats ⟩ Timing項目を使用します。クライアントでは、ShiftF2を押してRender Statsを見ることができます。
あるフレーム内で描画する必要があるオブジェクトの数が増えるほど、GPUへの描画コールが増加します。ただし、Roblox Engineは、同一のメッシュを同じテクスチャ特性を持つものに圧縮するためにインスタンス化プロセスを利用しています。具体的には、同じMeshContentを持つ複数のメッシュが、次の場合に単一の描画コールで処理されます:
- SurfaceAppearancesが同一であれば、そうでない場合はTextureContentsが同一である場合。
- SurfaceAppearanceおよびMeshPart.TextureIDが存在しない場合、材料が同一であること。
その他の一般的な問題
過剰なオブジェクト密度 - もし密度の高い大量のオブジェクトが集中している場合、そのシーンエリアをレンダリングするためにより多くの描画コールが必要です。マップのある部分を見たときにフレームレートが下がる場合、これはそのエリアのオブジェクト密度が高すぎることを示す良い信号です。
デカール、テクスチャ、粒子のようなオブジェクトは、バッチ処理が難しく、追加の描画コールを招きます。シーン内のこれらのオブジェクトタイプに特に注意を払ってください。特に、ParticleEmittersへのプロパティ変更はパフォーマンスに劇的な影響を与える可能性があります。
インスタンス化の機会を逃すこと - シーンには、同じメッシュが何度も複製されることが多いですが、各メッシュのコピーは異なるメッシュまたはテクスチャアセットIDを持っていることがよくあります。これはインスタンス化を妨げ、不必要な描画コールにつながります。
この問題の一般的な原因は、全体のシーンを一度にインポートすることです。個別のアセットがRobloxにインポートされ、その後にインポート後に複製されるべきです。
以下のようなシンプルなスクリプトで、異なるメッシュIDを持つ同じ名前のメッシュパーツを特定できます:
for _,descendant in workspace:GetDescendants() doif descendant:IsA("MeshPart") thenprint(descendant.Name .. ", " .. descendant.MeshId)endend出力(スタックラインが有効な場合)は以下のように表示されるかもしれません。繰り返し行は同じメッシュの再利用を示し、良いことです。ユニークな行は必ずしも悪いわけではありませんが、命名スキームによってはゲーム内の重複メッシュを示す可能性があります:
LargeRock, rbxassetid://106420009602747 (x144) -- goodLargeRock, rbxassetid://120109824668127LargeRock, rbxassetid://134460273008628LargeRock, rbxassetid://139288987285823LargeRock, rbxassetid://71302144984955LargeRock, rbxassetid://90621205713698LargeRock, rbxassetid://113160939160788LargeRock, rbxassetid://135944592365226 -- all possible duplicates過剰なオブジェクトの複雑さ - 描画コールの数ほどではないものの、シーン内の三角形数はフレームのレンダリングにかかる時間に影響を与えます。非常に大きな数の非常に複雑なメッシュを持つシーン、または多くのメッシュでMeshPart.RenderFidelityプロパティがPreciseに設定されているシーンは一般的な問題です。
過剰なシャドウのキャスティング - シャドウの処理は高コストなプロセスであり、高い数と密度の光オブジェクトがシャドウをキャストする場合、またはシャドウの影響を受ける小さなパーツが多数ある場合は、パフォーマンスに問題が生じる可能性があります。
高い透明度のオーバードロー - 部分的に透明なオブジェクトを近接させると、エンジンは重なったピクセルを複数回レンダリングする必要があり、これがパフォーマンスに影響を与えます。この問題を特定して修正するには、レイヤー付き透明の削除を参照してください。
不要なスキンメッシュパーツの移動 - ヒューマノイドなしのモデルの一部であるスキンメッシュパーツは、空間的に整理されたFastClustersを使用してグループ化されます。これらのメッシュパーツが移動すると、これらの空間クラスターから継続的に追加および削除される必要があり、クラスターの再構築が強いられ、パフォーマンスに影響を与えます。
- 効果的な回避策として、モデル内にヒューマノイドを埋め込むことができます。ヒューマノイドの存在がデフォルトの空間クラスター動作を上書きし、モデル全体に対して単一の統一されたFastClusterの使用を義務付けます。その結果、位置の更新はクラスターの再構築を必要としなくなり、パフォーマンスボトルネックが軽減されます。この手法は、期待される移動を持つメッシュパーツのみに予約するべきで、メモリオーバーヘッドを引き起こし、空間的最適化の恩恵を台無しにする可能性があります。この種の変更を行った後は、常にゲームのプロファイリングを実施することをお勧めします。詳細についてはヒューマノイドパフォーマンスのヒントを参照してください。
Model内の部分が多すぎる - モデル内の部分が多すぎると、部分のプロパティが変更される可能性によりフルビルドの必要性から再構築がより頻繁に発生する可能性があります。FastClusterを使用するモデル内のパーツの適切な調整を見つけます。
軽減策
同一のメッシュのインスタンス化と一意のメッシュの数を減らす - すべての同一メッシュが同じ基盤アセットIDを持つようにすれば、エンジンはそれらを単一の描画コールで認識しレンダリングできます。すべてのメッシュを1回のみマップにアップロードし、再利用のためにStudioで複製できるようにし、大規模なマップを丸ごとインポートしないようにしてください。これにより、同一メッシュに異なるコンテンツIDが割り当てられ、エンジンによってユニークなアセットとして認識されることがあります。 パッケージは、オブジェクトの再利用のための便利なメカニズムです。
カリング - カリングは、描画されない最終フレームに影響を与えないオブジェクトの描画コールを排除するプロセスを表します。デフォルトでは、エンジンはカメラの視野の外にあるオブジェクトの描画コールをスキップします(フラスタムカリング)し、他のオブジェクトによって視界から隠されたパーツ、メッシュ、および地形(オクルージョンカリング)を除外します。特定のシナリオ、特に屋内環境では、部屋またはポータルシステムを実装し、オブジェクトを手動でカットして描画コールや全体的な計算負荷をさらに減らすことができるかもしれません。
モデルの詳細度を減らす - インスタンスストリーミングを有効にし、ワールドモデルのLevelOfDetailプロパティをSLIMに設定して、カメラからの距離が増すにつれて最適化された軽量SLIMメッシュを表示します。
アバターの詳細度を減らす - インスタンスストリーミングを有効にし、Workspace.EnableSLIMAvatarsプロパティを設定して、カメラからの距離が増すにつれて、任意のアクセサリーまたは衣服のレイヤーを持つプラットフォームアバターモデルを最適化された軽量アバター表現として表示します。
レンダリング忠実度を減らす - MeshPart.RenderFidelityをAutomaticまたはPerformanceに設定します。これにより、メッシュはより複雑でない代替物にフォールバックすることができ、描画される多角形の数を減らすことができます。
適切なパーツおよび光オブジェクトのシャドウキャスティングを無効にする - Robloxエンジンは、クライアントのグラフィックス品質レベルが低下するにつれてシャドウの質を自動的に劣化させ、最終的に品質レベルが4未満でシャドウを完全に無効にします。ただし、パーツや光オブジェクトでシャドウキャスティングプロパティを選択的に無効にすることで、シャドウが有効な状態でのパフォーマンスを改善し、シャドウが有効な状態を維持する可能性を高めます。編集時または実行時に行うことができる最適化の例をいくつか示します:
BasePart.CastShadowプロパティを使用して、小さなパーツでシャドウをキャストするのを無効にします。これは、ユーザーのカメラから遠いパーツに適用する際に特に効果的です。
可能な場合は移動するオブジェクトのシャドウを無効にします。
シャドウをキャストする必要がない場合は、光のインスタンスでLight.Shadowsを無効にします。
光のインスタンスの範囲と角度を制限します。
光のインスタンスの数を減らします。
特定の範囲外にある光を無効にすることや、屋内環境に対して部屋ごとに光を無効にすることを検討します。
MicroProfilerスコープ
| スコープ | 関連する計算 |
| Prepare and Perform | 全体のレンダリング |
| Perform/Scene/computeLightingPerform | ライトグリッドとシャドウの更新 |
| LightGridCPU | ボクセルライトグリッドの更新 |
| ShadowMapSystem | シャドウマッピング |
| Perform/Scene/UpdateView | レンダリング準備と粒子の更新 |
| Perform/Scene/RenderView | レンダリングおよび後処理 |
ネットワーキングと複製
ネットワーキングと複製は、サーバーと接続されたクライアント間でデータが送信されるプロセスを説明します。クライアントとサーバー間では毎フレーム情報が送信されますが、大きなデータ量はより多くの計算時間を必要とします。
一般的な問題
過剰なリモートトラフィック - RemoteEventまたはRemoteFunctionオブジェクトを通じて大量のデータを送信するか、非常に頻繁に呼び出すと、各フレームで受信パケットの処理に大量のCPU時間が消費されることがあります。一般的な誤りには以下が含まれます:
- 複製する必要のないデータを毎フレーム複製する。
- 幹部入力に基づいてデータを複製し、スロットルするメカニズムを持たないこと。
- 必要以上のデータを送信する。例えば、アイテムを購入した際にプレイヤーの全 inventory を送信するのではなく、購入したアイテムの詳細のみを送信します。
複雑なインスタンスツリーの作成または削除 - サーバーでデータモデルに変更が加えられると、それが接続されたクライアントに複製されます。これは、ランタイムでの大規模インスタンス階層(マップなど)の作成や破棄が非常にネットワーク集約的であることを意味します。
ここでの一般的な原因は、Animation Editorプラグインによってリグのアニメーションデータが複雑なことです。ゲームが公開される前にこれらを削除しなければ、アニメーションモデルが定期的にクローンされ、大量のデータが不必要に複製されます。
サーバーサイドTweenService - TweenServiceがサーバーサイドでオブジェクトをツイーンする場合、ツイーンされたプロパティは各クライアントに毎フレーム複製されます。これは、クライアントのレイテンシが変化する間、ツイーンが揺れる結果を招くだけでなく、不必要なネットワークトラフィックを引き起こします。
軽減策
不必要な複製を減らすために、次の戦略を実施できます:
- リモートイベントを通じて大量のデータを一度に送信することを避ける。必要なデータのみを低頻度で送信します。例えば、キャラクターの状態については、毎フレームではなく、状態が変更される際に複製します。
- マップのような複雑なインスタンスツリーをチャンクアップして、複製作業を複数のフレームに分散させます。
- アニメーションメタデータをクリーンアップする、特にリグのアニメーションディレクトリをインポート後に削除します。
- 不必要なインスタンスの複製を制限します、特にサーバーが生成されるインスタンスを知る必要がない場合。これには以下が含まれます:
- 爆発や魔法の呪文の発生のような視覚効果。サーバーは結果を決定するための位置を知る必要がある間、クライアントはローカルにビジュアルを作成できます。
- 一人称アイテムビューモデル。
- クライアントではなくサーバーでオブジェクトをツイーンします。
MicroProfilerスコープ
| スコープ | 関連する計算 |
| ProcessPackets | 受信したネットワークパケットの処理(イベント呼び出しやプロパティの変更など) |
| Allocate Bandwidth and Run Senders | サーバー上での関連する出力イベント |
アセットのメモリ使用量
作成者がクライアントメモリ使用量を改善するために利用できる最も高い影響力のメカニズムは、インスタンスストリーミングを有効にすることです。
インスタンスストリーミング
インスタンスストリーミングは、必要ないデータモデルの部分を選択的にロード解除します。これにより、読み込み時間が大幅に短縮され、クライアントがメモリ圧力に晒されたときにクラッシュを防ぐ能力が向上します。
メモリに問題が発生し、インスタンスストリーミングが無効になっている場合は、特に3Dワールドが大きい場合には、ゲームをアップデートしてサポートすることを検討してください。インスタンスストリーミングは3D空間内の距離に基づいているため、大きなワールドは自然により効果を得ることができます。
インスタンスストリーミングが有効な場合、その攻撃性を高めることができます。例えば、以下を検討します:
- 可能な限りEnum.ModelStreamingMode.Persistentの使用を減らします。互換性のために使用している場合は、スクリプトを更新する必要があります。
- Workspace.StreamingMinRadiusとWorkspace.StreamingTargetRadiusを減らします。
ストリーミングオプションとその利点についての詳細は、ストリーミングプロパティを参照してください。
その他の一般的な問題
アセットの重複 - 同じアセットを複数回アップロードし、異なるアセットIDを取得することは一般的な誤りです。これにより、同じコンテンツがメモリに複数回読み込まれることがあります。
過剰なアセットボリューム - アセットが同一でない場合でも、同じアセットを再利用し、メモリを節約する機会が逃されるケースがあります。
オーディオファイル - オーディオファイルはメモリ使用量に意外な寄与をする場合があります。特に、ゲームのある部分に必要なものだけを読み込むのではなく、すべてを一度にクライアントに読み込む場合です。戦略については、読み込み時間を参照してください。
高解像度のテクスチャ - テクスチャのグラフィックメモリ消費は、ディスク上のテクスチャサイズとは関係なく、テクスチャ内のピクセル数に依存します。例えば、1024x1024ピクセルのテクスチャは512x512ピクセルのテクスチャの4倍のグラフィックメモリを消費します。
Robloxにアップロードされた画像は固定形式にトランスコードされるため、ピクセルあたりのバイト数が少ないカラーモデルで画像をアップロードしてもメモリの利益はありません。さらに、アップロード前に画像を圧縮したり、必要のない画像からアルファチャネルを削除すると、ディスク上の画像サイズは小さくなりますが、メモリ使用量は改善されません。
ゲームが読み込まれると、エンジンは自動的に低品質のテクスチャから開始し、デバイスメモリの利用可能量、カメラからの距離、テクスチャが占める画面スペースの量、その他の要因に基づいて品質を引き上げます。それでも、戦略的にテクスチャのサイズを調整することで、ゲーム内のメモリ使用量を改善できます。
軽減策
アセットを一度だけアップロードする - 同じアセットIDをオブジェクト全体で再利用し、同じアセット(特にメッシュや画像)が複数回個別にアップロードされないようにします。
重複アセットを見つけて修正する - 複数のIDで複数回アップロードされている同一のメッシュパーツやテクスチャを探します。
- アセットの類似性を自動的に検出するAPIはありませんが、場所内のすべての画像アセットIDを集め(手動またはスクリプトで)、ダウンロードして外部比較ツールを使用して比較することができます。
- メッシュパーツについては、ユニークなメッシュIDを特定し、サイズで整理して重複を手動で特定するというのが最良の戦略です。
- 異なる色用の別々のテクスチャを使用するのではなく、単一のテクスチャをアップロードし、SurfaceAppearance.Colorプロパティを使用してさまざまな色合いを適用します。
マップ内のアセットを個別にインポートする - 全体のマップを一度にインポートするのではなく、マップ内のアセットを個別にインポートし、再構成します。インポータはメッシュの重複を行わないため、大規模なマップをインポートすると、各タイルが別のアセット(重複していた場合でも)として扱われます。これによりパフォーマンスとメモリの問題が発生する可能性があります。
画像のピクセル数を必要最低限に制限する - 画像がスクリーン上で大きな物理的スペースを占有していない限り、通常は512x512ピクセル以下である必要があります。ほとんどのマイナーな画像は256x256ピクセル未満であるべきです。
トリムシートを使用して3Dマップ内のテクスチャ再利用を最大化する - トリムシートの作成方法についてのステップと例については、トリムシートを作成するを参照してください。
また、スプライトシートを使用して多数の小さなUI画像を単一の画像として読み込むことを検討するかもしれません。次に、ImageLabel.ImageRectOffsetとImageLabel.ImageRectSizeを使用してシートの部分を表示します。
読み込み時間
多くのゲームはカスタムロード画面を実装し、ContentProvider:PreloadAsync()メソッドを使用してアセットを要求し、画像、音声、メッシュがバックグラウンドでダウンロードされます。
このアプローチの利点は、ゲームの重要な部分がポップイン無しで完全に読み込まれることを確実にできる点です。しかし、一般的な誤りは、このメソッドを過剰に活用して、実際に必要な以上のアセットを先に読み込むことです。
悪いプラクティスの例は、Workspace全体を読み込むことです。これにより、テクスチャのポップインを防ぐかもしれませんが、読み込む時間が大幅に増加します。
同様のプラクティスは、すべての要求されたアセットが読み込みを完了したことを確認するためにContentProvider.RequestQueueSizeを使用することです。しかし、これには同じ問題が伴い、読み込み時間を著しく増加させる上に、その変動的な性質により信頼できない方法です。
必要な場合にのみContentProvider:PreloadAsync()を使用し、以下の状況を含めることをお勧めします:
- ロード画面での画像。
- ゲームメニュー内の重要な画像、ボタンの背景やアイコンなど。
- スタートエリアやスポーンエリア内の重要なアセット。
大量のアセットを読み込む必要がある場合は、ロードをスキップボタンを提供することをお勧めします。