ゲームを実行するには、プロジェクトのリソースをメモリにロードしてレンダリングするか、そこに格納されているデータを操作する必要があります。また、ゲームを実行できるデバイスごとに、ゲームで利用可能なメモリ容量に関する厳しい制限があります。これにより、ゲームが特定の時間内にロードできるリソースの数とサイズが両方制限されます。言うまでもなく、モバイル デバイスではこれらの上限が低くなっています。ただし、ゲーム プロジェクトの対象範囲が広がるにつれて、64 ビット PC およびコンソールでもメモリがますます貴重になっています。
ゲームがメモリ不足にならないようにすることが重要です。ゲームに必要なメモリ容量がオペレーティング システムから取得できる容量を超えている場合は、エンジンが対応するチャンスを得る前に、オペレーティング システムによって即座にゲームがクラッシュして終了する可能性があります。
ゲームの負担が大きくなってクラッシュするまで、メモリ使用量を無視することはしないでください。代わりに、より事前対策的なアプローチを使用してプロジェクトの開始時点から予算およびベスト プラクティスを確立し、開発サイクル全体で実際の使用量を監視します。こうすることにより、リリース前の最終段階でパニックにならずに済みます。
このページには、ゲームに必要なメモリ サイズを削減するためのヒントおよび技術がいくつか示されています。これらは、次の 3 つのカテゴリに分類されます。
メモリ使用量を最適化および最小化する前に、達成しようとしている目標を明確にしておくと役立ちます。
全体的な予算は、ユーザがサポートするハードウェア デバイスの仕様に応じて変わります。ここから、オペレーティング システムで使用するメモリを差し引く必要があります。次に、他のアプリで使用されるメモリ容量に対応する、大きな割合を差し引きます。ゲームがデバイスで実行されている唯一のアプリであると想定することはできません。最後に、「上限」に少し余裕を持たせるために、さらに追加の割合を差し引きます。予測できないクラッシュからの保護方法に応じて、全体的な目標値はデバイスの利用可能メモリ サイズの 4 分の 1 またはそれ以下になることがあります。
ゲームの全体的な予算を総体的に把握できたら、予算全体をサブシステムごと、または種類のリソース(テクスチャ、オーディオ、メッシュなど)ごとに小さな予算に分割すると便利です。ここに示した値は、開発するゲームの種類に応じて異なる可能性があります。たとえば、ゲームの鑑賞性を高める目的で意図的に低品質のポリゴン アートを使用し、複雑な音楽やオーディオ キューを含める必要がある場合、ビジュアル重視のゲームよりも予算全体に対して大きな割合の予算をオーディオ リソースに割り当てなければならないことがあります。これらの予算を絶対的なルールにする必要はありません。プロジェクトを通して交渉によって定めることができます。ただし、複数のチーム メンバーがプロジェクトのさまざまな領域で作業している場合は、大まかな予測値を事前に設定しておくと、チーム内のコラボレーションをさらに円滑にすることができます。
ランタイムでゲームが実際に使用するメモリ容量に関する明確なデータが存在しない場合は、解決しなければならない問題の領域を特定できなくなります。データがなければ、間違った領域に時間と労力を費やしてしますことがあります。たとえば、実際は巨大で極めて詳細なモデルが問題の引き金となっている場合でも、テクスチャ品質を積極的に下げてしまうことがあります。
ランタイムでゲームで使用されるメモリを評価することも重要です。なぜなら、リソースのメモリ サイズは、ディスク上のリソースのサイズと大幅に異なることがあるためです。たとえば、プロジェクト フォルダ内のイメージ テクスチャは圧縮された .png 形式でディスクに格納されていることが多いため、ディスク上のサイズが小さくなることがあります。ところが、ランタイムのメモリではテクスチャ データが圧縮解除されて、ファイル サイズから予測される値よりもはるかに多くの容量を使用します。
ゲーム内のメモリ使用量に関する情報を取得するためのコンソール コマンドがいくつかあります。
memory_tree: ゲーム内のさまざまなサブシステムによって割り当てられたすべてのメモリを Log Console に階層的なツリー形式で出力します。
memory_resources: ゲームにロードされたリソースが使用するメモリのリストを、タイプ別に編成して Log Console に出力します。
perfhud コマンドには、便利なメモリ統計にアクセスするための 2 つのモードがあります。
perfhud memory: 合計システム メモリ、利用可能なシステム メモリ、および使用済みシステム メモリを単純に比較します。
perfhud lua: 組み込みのガベージ コレクション システムで使用される割合を含む、Lua 環境で使用されるメモリを表示します。次の「Lua 環境およびスクリプトを最適化する」セクションを参照してください。
Stingray エディタのビューポートにこれらの情報をビジュアルに表示することもできます。ビューポートで View オーバーレイをクリックし、コンテキスト メニューから Performance HUD を選択します。
Stingray エディタから実行時のゲームにコマンドを送信する方法の詳細については、「ステータス バーからコマンドを送信する」を参照してください。コマンド自体の詳細については、「コンソール コマンド」を参照してください。
起動時にゲームがクラッシュする場合、特に利用可能メモリが少ないモバイル デバイスでテストする場合に最も可能性の高い原因は、ブート パッケージが極端に多くのリソースをロードしようとしていることです。
ブート パッケージがそれに含めるリソースを決定するためにワイルドカードを使用している場合は、ロードされるリソースの一部が実際には必要でない可能性もあります。次のような行を記述することによってプロジェクト内のすべてのタイプのすべてのリソースをロードしている場合は、特にその可能性が高くなります。
* = ["*"]
ワイルドカードと一致するものの、ランタイムには必要とされないアセットの古いコピーやテスト レベルがプロジェクトに多数ロードされている場合は、プロジェクトからそのような不要なリソースを削除することにより、メモリを節約できます。
または、古いコピーを保持しておきたい場合は、ブート パッケージから自動ローディングの行を除去し、不要なリソースを除外するようにブート パッケージを再設定することもできます。
「ブート パッケージについて」および「リソース パッケージを定義する」も参照してください。
ゲームのメモリ使用量をコントロールするために用意されている最も強力かつ柔軟なオプションは、パッケージのリソースを複数の独立したパッケージに分割し、必要に応じて、ゲーム中にこれらのパッケージをメモリに動的に出し入れすることです。
異なる時点でさまざまなリソース セットが必要になるプロジェクトでは、一般にこの方法を利用できます。たとえば、ゲームに複数のレベルがある場合は、各レベルで使用されるアセットごとに独立したリソース パッケージを作成できます。このようにすると、各レベルは、利用可能なメモリを他のレベルと共有しなくても、ゲームのメモリ予算全体を使用することができます。レベルが 1 つしかない場合も、そのレベルの独立した複数のパーツ、複数のキャラクタなどにリソース パッケージを使用できる可能性があります。
この種類のシステムを設定する方法の詳細については、「ランタイムでコンテンツをロードおよびロード解除する」のトピックを参照してください。
リソース パッケージをメモリに出し入れするためのカスタム システムを設定する場合も、次に示す他の技術を適用して、ロードする個々のリソースを最適化することができます。
これまで、ほとんどのゲームでメモリを最も多く使用してきたのはテクスチャです。もちろん、ゲームのビジュアルを魅力的にする上で、テクスチャは大きな役割を果たしています。したがって、ビジュアル面の品質とメモリ サイズの適切なバランスを取るのは困難なことがあります。画面サイズや利用可能メモリが大きく異なる複数のプラットフォームをサポートする必要がある場合は、特にそうです。画面が大きいデバイスでは、イメージ品質を重視しなければならない可能性があります。逆に小さい画面では、品質を下げて小さなテクスチャを使用しても、レンダリング品質にはっきりとした違いは生じません。
次のセクションでは、プロジェクトのメモリと品質のバランスを最適化するのに役立つヒントをいくつか示します。
テクスチャを最適化するには、まず、適切なイメージ サイズを判別します。ゲーム内のすべてのテクスチャのサイズを 4096x4096 ピクセルにすると、すべてのシステムですぐにメモリ不足になります。
テクスチャの適正サイズに関する明確なルールは存在しません。最終的には、次の条件に応じて判断します。
ゲーム内でレンダリングするときにテクスチャが使用する画面領域の最大サイズ。このサイズは、テクスチャが適用されるモデルのサイズや、カメラがモデルに近づく距離によって決まります。たとえば、山は巨大かもしれませんが、遠く離れている場合は、小さなテクスチャを使用しても違いはわかりません。同様に、プレイヤーが新聞紙や本などの小さなオブジェクトに近づいてじっくり見ることができる場合、またはそのようにする必要がある場合は、これらに対して大きなテクスチャが必要になることがあります。
オブジェクトに対して要求されるビジュアル面の品質または忠実度。たとえば、レベルのパーツがプレイヤーに短時間だけ表示される場合や、ゲームプレイが原因でプレイヤーが他の場所に注目する可能性がある場合は、濃淡にむらのあるアーティファクトが生じても許容できる可能性があります。
また、各マテリアルに割り当てるテクスチャ マップごとに異なるサイズを使用して、実験することもできます。たとえば、粗いマップやメタル質のマップにカラー マップや法線マップよりも小さなイメージを使用しても、通常は、レンダリング時のマテリアルの表示方法で品質が目に見えて低下することはありません。
圧縮テクスチャのストリーミングに対しては、ピクセル サイズを 4 の倍数にする必要があります。
Stingray の Texture Manager を使用して、ターゲット プラットフォームごとに Stingray でのテクスチャのコンパイル方法をコントロールすることができます。
調整が必要な最も重要な設定は、テクスチャに適用される圧縮の種類を定義する Output Format です。
iOS 用の PVR、他のプラットフォーム用の DXT5 などの圧縮アルゴリズムを適用することにより、通常は、ゲーム内におけるテクスチャのメモリ消費量を大幅に下げることができますが、圧縮によってイメージの品質が若干劣化します。
単純なカラー マップを含むテクスチャでは、テクスチャ圧縮によって生じるエラーおよび劣化は一般にほとんどわかりません。ただし、法線マップを圧縮すると、サーフェスに極めて明確な不規則性が生じる傾向があるため、法線マップを圧縮しないようにすることができます。
また、複数のプラットフォームで最大のミップ マップ ステップを破棄しなければならないこともあります。たとえば、1024x1024 ピクセルのテクスチャあるにもかかわらず、iOS の場合のみテクスチャを 256x256 に制限する必要がある場合は、iOS をコンパイルするときのみ、最大の 2 つのミップ マップ ステップを破棄するように Texture Manager を設定することができます。こうすると、iOS データから解像度が 1024x1024 および 512x512 のイメージが除去されて、元々 256x256 のピクセル イメージであるかのように効果的に機能させることができます。ただし、他のプラットフォームでは、フル解像度としてそのまま表示されます。
ボリューム マップや立方体マップでは必要なメモリ量が 6 倍になるため(6 は立方体の側面の数)、ミップ マップのコストが特に大きくなります。したがって、レベルの反射プローブをベイク処理する場合は、生成されたテクスチャからミップ マップ ステップの一部または全部を除去しなければならないことがあります。
Texture Manager の使用法の詳細については、「テクスチャを使用する」を参照してください。
既定により、Stingray は他のすべてのリソースと同様にテクスチャを処理します。テクスチャはリソース パッケージ内の他のすべてのリソースとともにメモリにロードされ、メモリに永続的に保管されます。ロード解除されるのは、リソース パッケージがロード解除されるときのみです。つまり、テクスチャが画面外にある、または非常に遠くにあるために各テクスチャのミップ レベルが実際は使用されていない場合でも、各テクスチャのすべてのミップ レベルがゲーム内のメモリを使用しています。
テクスチャ データが実際にレンダリングに必要な場合に限ってこれらのデータをメモリにストリーミングするように、Stingray を設定することができます。テクスチャ ストリーミングを使用すると、ゲーム内で使用するテクスチャのメモリ使用量を増やしたり、最大解像度を小さくしたりしなくても、これのテクスチャの数を増やすことができます。
このシステムの仕組みの詳細については、「テクスチャ ストリーミング」を参照してください。
エンジンは、テクスチャ ストリーミング システムが使用するための 16 MB のメモリを起動時に予約します。ゲーム内でテクスチャ ストリーミングを使用しない場合は、ターゲット プラットフォームごとにプロジェクトの settings.ini ファイル内の streaming_buffer_size キーを 0 に設定して、このメモリの割り当てを回避することができます。「Stingray エンジンの settings.ini ファイルのリファレンス」を参照してください。
レベルのライティングをベイク処理するときに、ゲーム中にロードする必要がある追加のテクスチャが生成されます。一般に、ベイク処理を行うと、CPU および GPU の使用率を下げても高品質のレンダリングを実現できるようになりますが、ランタイムのメモリ要件が厳しくなるという代償が常に伴います。
ライトマップがメモリを使用しすぎる場合は、次のアイデアを試すことができます。
何もベイク処理をしないようにします。動的ライティングのみを、シェーディング環境で提供されるグローバルな拡散反射光マップと組み合わせて使用します。こうすることで、ライトマップ テクスチャをまとめて取り除くことができます。「グローバル環境ライティング」を参照してください。
ライト ベイク処理はシーン内のいくつかの重要なオブジェクトのみに制限し、他については動的な直接ライティングおよびグローバルにベイク処理された拡散ライティングを使用します。
Light Baking ダイアログでライトマップ解像度設定を引き下げます。これにより生成されたテクスチャはサイズが小さくなりますが、ディティールとシャープネスは犠牲になります。「Stingray ベイカーでベイク処理する」を参照してください。
この方法でライトマップの解像度を低くした際に、ベイク処理されたライティングで十分なディテールを得ることができない場合は、直接光を使用せず、間接光のみでのベイク処理を試すことができます。解像度を下げることによる影響は、間接光の場合はあまり目立たなくなります。ただし、いずれにしてもぼやけて低周波になる可能性があります。
プロジェクト内の他のテクスチャ リソースと同様に、Texture Manager でライトマップのテクスチャ圧縮およびミップ マップ生成の設定を制御できます。上記の「テクスチャを最適化する」を参照してください。
プロジェクトに追加する各モデル アセット内の頂点数およびデータ チャネル数が多いほど、ゲームにこのメッシュをロードするのに使用されるメモリ容量が増えます。モデリング ツール(3ds Max や Maya など)でメッシュを最適化して、頂点数を減らしたり、ゲームに不要なすべてのチャネル(余分な UV セットなど)を除去したりして、メモリの一部を節約することができる可能性があります。
目標とすべき頂点数に関する厳しいルールはありません。テクスチャ サイズの場合と同じように、ゲーム内でレンダリングするときにモデルのビジュアル面の品質とメモリ要件のバランスを取る必要があります。モデルのビジュアル面の品質は、モデルのサイズ、カメラからの距離、およびゲームプレイやシーンに対する重要性によって決まります。モデルによっては、映画セットの装飾のように処理できる場合があります。プレイヤーに特定の建物またはオブジェクトの背面が見えない場合は、頂点数が最小のフラットな平面にすることができます。
メッシュ サイズが大きすぎて処理を開始できない場合を除き、テクスチャを最適化した場合のようにメモリ消費量に大きな違いが生じることはない可能性があります。ただし、ゲーム内に同時に存在するメッシュが増えるほど、わずかな最適化で大きな成果が得られます。
テクスチャおよびメッシュの次にゲーム内でメモリ使用量が最大となることが多いリソース タイプは、オーディオ ファイルです。
上記のテクスチャやメッシュと同様に、サウンド品質とのトレードオフによってメモリ要件を緩和することができます。ただし、ディスクからオーディオを直接ストリーミングする、Wwise エンジンで内部的に割り当てられるメモリ プール サイズを調整するなど、追加で使用できる他の補足技術が多数存在します。
詳細については、「オーディオのメモリ使用量を最適化する」を参照してください。
物理的なサブシステム内で実行できる主なメモリ最適化は、アクターおよびムービーが不要なすべてのユニットでアクターおよびムービーを作成しないことです。ユニットをスポーンすると、いつでも、アクターおよびムービー用の追加容量がメモリ内に予約されます。
アクターを作成するのは、衝突検出で考慮しなければならない物理オブジェクトを表すユニットに限定してください。
また、ムービーを作成するのは、コントロール可能なキャラクタまたはオブジェクトを表すユニットに限定してください。
ゲーム内で物理特性を使用する必要がない場合は、Stingray のワールドでそれを無効にして、メモリを節約することができます。そのためには、このワールドを構築する stingray.Application.new_world() の呼び出し内で stingray.Application.DISABLE_PHYSICS を渡します。Appkit を使用している場合は、core/appkit/lua/simple_project.lua ファイル内でこの操作を行います。
Asset Browser でアニメーション クリップを選択すると、Property Editor に、コンパイル時にこのクリップに適用できる一連の圧縮設定が表示されます。これらの圧縮設定を有効にすると、ランタイムでメモリの一部を節約できますが、アニメーション カーブが少し不正確になります。
各クリップの圧縮設定を微調整するには、位置、回転、またはスケール カーブの圧縮を個別に有効および無効にするとともに、許容値を調整してデータ圧縮のアグレッシブさと許容されるエラーの大きさとの間のバランスをコントロールします。
詳細については、「アニメーション クリップのプロパティ」、「 アニメーションの圧縮」および「アニメーションの最適化」も参照してください。
Lua 環境で使用されるメモリ量を低く抑えるには:
stingray.Raycast など、完全なユーザデータ オブジェクトを多数作成しないようにします。
Vector3Box や QuaternionBox などの「ボックス」オブジェクトを使用して、ゲーム中に Vector3 や Quaternion などの一時的なオブジェクトを複数のフレームにわたって保存する場合は、初期化時に再利用可能なプール内にこれらのボックスを作成して、ゲーム中にヒープ上に新しいインスタンスが繰り返し作成されないようにします。「オブジェクトのライフタイムとユーザデータのバインド」も参照してください。
組み込みのガベージ コレクション システムで極端に多くのストレインが配置されないようにするには、Lua でコーディングする場合の一般的なベスト プラクティスに従います。たとえば、ループ内で文字列を連結しないで、文字列の各部をテーブルに挿入して一度に連結します。
Lua 環境のガベージ コレクションを設定するのに慣れている場合は、stingray.Script オブジェクトが提供する関数を使用してこの操作を実行できます。
Lua メモリの問題に関するこのブログの投稿も参照してください。
上記のオプションを実行してゲーム内に各種のリソース タイプおよびサブシステムで消費されるメモリを削減した後も、レベルで使用されるメモリ容量が多すぎる場合は、レベルが不必要にメモリを消費していないか、または知らないうちにメモリを消費していないか徹底的に確認してください。
ランタイムにフローまたは Lua 内で非常に多数のユニットをスポーンして、後でスポーン解除しないことは避けてください。ユニットの余分なインスタンスがメモリ内の容量を追加で大量に使用することはないため、これらは自由にスポーンできます。ただし、銃弾や矢などを発射する場合のように、数千個のインスタンスをスポーンする場合は、メモリ使用量が増えます。特に、物理的なアクターが含まれる場合などです。
絶対に表示されないユニットを調べて、地面の下や他のメッシュの内側に隠します。可能であれば、これらをレベルおよびリソース パッケージから除去します。