2,000万タイルのマップでゲームのメモリが不足します。どうすれば回避できますか?


11

余分な巨大なマップをロードしているときに、ロードメソッドがメモリ例外をスローし、マップタイルの新しいインスタンスが作成されます。少なくともサーバーアプリ(および可能であればクライアント)でマップ全体を処理してもらいたいのですが。この問題を解決するにはどうすればよいですか?

UPD:ここでの問題は、使用する空きメモリがまだあるときにゲームがクラッシュしないようにする方法です。チャンクでマップを分割することに関しては、それは良いアプローチですが、私の場合には望みのものではありません。

UPD2:最初に私は行って、タイルクラスのすべての新しいインスタンスにテクスチャを割り当てました。今では、約4分の1のスペースで済みます。皆さんのおかげで、今はまだ塊に分割することを考えずに巨大なマップを実行できます。

UPD3:コードを書き直して、タイルプロパティの配列がより速く機能するか、メモリ消費が少ないかどうかを確認した後(オブジェクトプロパティとしての各タイルのプロパティよりも)、それを試すのに多くの時間がかかっただけでなく、パフォーマンスは向上せず、ゲームのデバッグが非常に困難になりました。


8
プレイヤーの周りの領域のみをロードします。
MichaelHouse

4
タイルあたり4バイトで2,000万のタイルは約80 MBに過ぎないため、タイルがそれほど小さくないように思えます。魔法のようにメモリを増やすことはできないため、データを小さくする方法を考えなければなりません。だから、これらのタイルの内容を見せてください。
Kylotan

読み込み方法は何をしますか?コードを投稿します。コンテンツパイプラインを使用しますか?テクスチャをメモリにロードしますか、それともメタデータだけをロードしますか?どのメタデータ?XNAコンテンツタイプは通常、管理対象外のDirectXのものを参照するため、管理対象オブジェクトは非常に小さくなりますが、管理対象外の側では、DirectXプロファイラーを実行していない限り、表示されないものの負荷が発生する可能性があります。stackoverflow.com/questions/3050188/...
オスカーDuveborn

2
システムがメモリ不足の例外をスローする場合は、何を考えていても、使用するのに十分な空きメモリがありません。追加のメモリを有効にするために提供できる秘密のコード行はありません。各タイルの内容とそれらを割り当てる方法は重要です。
Kylotan

3
空きメモリがあると思うのはなぜですか?64ビットOS内で32ビットプロセスを実行していますか?
Dalin Seivewright、2007

回答:


58

1.5Gbに達するとアプリがクラッシュします。

これは、各タイルのサイズが〜80バイトであることを意味するため、タイルを正しく表現していないことを強く示唆しています。

理解する必要があるのは、タイルのゲームプレイの概念とユーザーに表示される視覚的なタイルの間に分離が必要であるということです。これら2つの概念は同じものではありません

テラリアを例にとってみましょう。最小のTerrariaの世界は、4200x1200タイル(500万タイル)を占めます。さて、その世界を表すのにどれだけのメモリが必要ですか?

さて、各タイルには、前景レイヤー、背景レイヤー(背景の壁)、ワイヤーが行く「ワイヤーレイヤー」、家具アイテムが行く「家具レイヤー」があります。各タイルはどのくらいのメモリを使用しますか?繰り返しますが、私たちは視覚的にではなく、概念的に話しているだけです。

前景のタイルは、符号なしのshortに簡単に格納できます。前景タイルのタイプは65536を超えないため、これより多くのメモリを使用しても意味がありません。背景タイルの種類は256種類に満たないため、背景タイルは簡単に符号なしバイトになる可能性があります。ワイヤーレイヤーは純粋にバイナリです。タイルにはワイヤーが含まれるか、含まれません。つまり、タイルごとに1ビットです。そして、家具の層は、家具の可能な異なる部分の数に応じて、符号なしバイトになる可能性があります。

タイルごとの合計メモリサイズ:2バイト+ 1バイト+ 1ビット+ 1バイト:4バイト+ 1ビット。したがって、小さなTerrariaマップの合計サイズは20790000バイト、つまり最大20MBです。(注:これらの計算はTerraria 1.1に基づいています。それ以降、ゲームは大幅に拡張されましたが、最新のTerrariaでもタイルの場所ごとに8バイト、または〜40MBに収まる可能性があります。それでもかなり許容範囲です)。

この表現をC#クラスの配列として保存しないでください。それらは整数の配列またはそれに似たものでなければなりません。C#構造体も機能します。

次に、マップの一部を描画するとき(強調点に注意)、Terrariaはこれらの概念的なタイルを実際のタイルに変換する必要があります。各タイルは、前景画像、背景画像、オプションの家具画像を実際に選択し、ワイヤー画像を持っている必要があります。XNAには、さまざまなスプライトシートなどが付属しています。

概念マップの表示部分を実際のXNAスプライトシートタイルに変換する必要があります。一度にすべてを変換しようとするべきではありません。保存する各タイルは、「IはタイルタイプXです」というインデックスでなければなりません。Xは整数です。その整数インデックスを使用して、表示に使用するスプライトをフェッチします。また、XNAのスプライトシートを使用して、個々のクワッドを描画するだけでなく、これを高速化します。

次に、タイルが表示される領域をさまざまなチャンクに分割する必要があります。これにより、カメラが動いたときに常にスプライトシートを作成する必要がなくなります。そのため、64x64のチャンクをスプライトシートとして使用できます。プレーヤーの現在のカメラ位置から見える64x64の世界のチャンクが、描画するチャンクです。他のチャンクにはスプライトシートさえありません。チャンクが画面から落ちた場合は、そのシートを破棄します(注:実際に削除するのではなく、そのままにして、後で表示される可能性がある新しいチャンクに再指定します)。

少なくともサーバーアプリ(および可能であればクライアント)でマップ全体を処理してもらいたいのですが。

お使いのサーバが知っているか、タイルの視覚的な表現を気にする必要はありません。気にする必要があるのは、概念的な表現だけです。ユーザーがここにタイルを追加すると、そのタイルインデックスが変更されます。


4
+1すばらしい答え。私以上のものに投票する必要があります。
MichaelHouse

22

地形をリージョンまたはチャンクに分割します。次に、プレーヤーに表示されるチャンクのみをロードし、表示されないチャンクをアンロードします。これはコンベヤベルトのようなものと考えることができます。プレイヤーが移動するときに、一方の端でチャンクをロードし、もう一方の端でチャンクをアンロードします。常にプレーヤーの前に留まります。

インスタンス化などのトリックも使用できます。1つのタイプのすべてのタイルがメモリに1つのインスタンスを持ち、必要なすべての場所に複数回描画される場合。

このようなより多くのアイデアをここで見つけることができます:

彼らはそれをどうやってやったのか:Terrariaの何百万ものタイル

大きなタイルマップ上のエリアとダンジョンを「ゾーニング」


問題は、そのようなアプローチはクライアントアプリには適していますが、サーバーにはあまり適していません。世界のいくつかのタイルが消えて連鎖反応が始まると(それはよく起こります)、マップ全体がそのためにメモリ内にあるはずであり、メモリ不足になると問題になります。
user1306322

6
各タイルには何が含まれていますか?タイルごとにどのくらいのメモリが使用されますか?それぞれが10バイトであっても、190メガバイトしかありません。サーバーは、必要のないテクスチャやその他の情報をロードするべきではありません。2,000万タイルのシミュレーションが必要な場合は、それらのタイルから可能な限りストリップして、連鎖反応に必要な情報のみを含むシミュレーションセットを形成する必要があります。
MichaelHouse

5

Byte56の答えはいいです、そして私はそれにコメントとしてこれを書き始めましたが、それはあまりに長くなり、答えでより役立つかもしれないいくつかのヒントを含みます。

このアプローチは、サーバーにとってもクライアントにとってもまったく同じです。実際、サーバーがクライアントよりもはるかに多くの領域に注意を払うように求められることを考えると、おそらくそれはより適切です。サーバーは、クライアントがロードする必要があるものとは異なる考えを持っていますが(接続されているすべてのクライアントが気にするすべてのリージョンを気にする)、ワーキングリージョンのセットを管理することも同様に可能です。

このような巨大なマップをメモリに収めることができたとしても(ほとんどの場合はできません)、そうしたくはありません。すぐに使用する必要のないデータをロードすることには絶対的なゼロ点があり、非効率的で低速です。たとえそれをメモリに収めることができたとしても、ほとんどの場合、理にかなった時間ですべてを処理することはできません。

特定の領域をアンロードすることへのあなたの異議は、あなたの世界の更新がすべてのタイルを反復して処理しているためだと思いますか?これは確かに有効ですが、特定のパーツのみをロードすることを妨げるものではありません。代わりに、リージョンがロードされたときに、失われた更新を適用して最新の状態にします。これは、処理の点でもはるかに効率的です(メモリの小さな領域に大きなバーストを集中させます)。領域の境界を越える可能性のある処理効果(たとえば、溶岩流または別の領域に持ち込まれる水流)がある場合は、これらの2つの領域を結合して、一方がロードされたときにもう一方もロードされるようにします。理想的には、これらの状況は最小限に抑えられます。そうしないと、「すべての領域を常にロードする必要がある」場合にすぐに気付くでしょう。


3

XNAゲームで使用した効率的な方法は次のとおりです。

  • 各タイル/オブジェクトの画像ではなく、スプライトシートを使用し、そのマップに必要なものだけをロードします。
  • マップをより小さい部分に分割します。たとえば、マップがこのサイズの4倍の場合は500 x 200タイルのチャンクマップ、または半分にできるものなどです。
  • このチャンクをメモリにロードし、マップの表示されている部分のみを表示します(たとえば、表示されているタイルに加えて、プレーヤーが動いている方向ごとに1タイル)。
  • ビューポートをクリアし、ピクセルをバッファーからコピーします(これはダブルバッファーと呼ばれ、移動をスムーズにします)。
  • charが移動すると、マップの境界を追跡し、次のチャンクが近くに来たらロードし、現在のチャンクに追加します。
  • プレイヤーがこの新しいマップに完全に入ると、最後のマップをアンロードできます。

それがそれほど大きくなく、提示されたロジックを使用する場合は、この方法で単一の大きなマップを使用することもできます。

もちろん、テクスチャのサイズはバランスをとる必要があり、解像度はこれに大きな代償を払っています。低解像度で作業してみてください。必要に応じて、構成設定が設定されている場合は高解像度パックをロードしてください。

毎回このマップの関連部分のみをループし、必要なものだけをレンダリングする必要があります。これにより、パフォーマンスが大幅に向上します。

サーバーはゲームをレンダリングする(最も遅い操作の1つ)必要がないため、巨大なマップをより速く処理できます。そのため、サーバーのロジックは、より大きなチャンクまたはマップ全体の使用であり、ロジックのみを処理することができます。プレーヤーの周囲のエリアの2倍のプレーヤービューポートのように、プレーヤーの周囲。

ここでのアドバイスは、私は決してゲーム開発のエキスパートではなく、私のゲームは卒業の最終プロジェクト(私が最高のスコアを持っていた)のために作られたため、すべての点でそれほど正しくないかもしれませんが、http://www.gamasutra.comやXNAクリエーターサイトcreators.xna.com(現時点ではhttp://create.msdn.com)などのWebおよびサイトを調査して、知識とスキルを収集し、それが私にとってうまく機能した。


2

どちらか

  • より多くのメモリを購入する
  • データ構造をよりコンパクトに表す
  • 一度にすべてのデータをメモリに保持しないでください。

私はwin7 64ビットで8 GbのRAMを使用していますが、1.5 Gbに達するとアプリがクラッシュします。だから私が何かを逃していない限り、もっと多くのRAMを買う意味はありません。
user1306322 2012

1
@ user1306322:タイルが2000万個あり、メモリの使用が約1.5GBである場合、タイルはタイルあたり80バイトです。これらのものに正確に何を格納していますか?
Nicol Bolas

@NicolBolas私が言ったように、いくつかの整数、バイト、およびtexture2d。また、1.5 GBをすべて使用するわけではなく、このメモリコストに達するとアプリがクラッシュします。
user1306322 2012

2
@ user1306322:それはまだ必要以上に大きいです。の大きさはtexture2d?各タイルには固有のタイルがありますか、それともテクスチャを共有していますか?また、どこでこれを言ったのですか?あなたは確かにそれをあなたの質問に入れませんでした、それは情報があるべき場所です。特定の状況をよりよく説明してください。
Nicol Bolas

@ user1306322:率直に言って、私の回答が反対票を投じられた理由がよくわかりません。私は、メモリ不足の状況に対する3つの救済策を列挙しました。もちろん、それらすべてが必ずしも当てはまるわけではありません。しかし、私はまだそれらが正しいと思います。ただし、仮想マシンのケースを含めるために、「購入」ではなく「メモリを拡張する」と書いた方がいいでしょう。私の回答が冗長ではなく簡潔だったため、私は反対票を投じられていますか?ポイントはわかりやすいので、これ以上説明しなくても理解できると思います!
トーマス

1

ここでの問題は、使用するメモリがまだあるときにゲームがクラッシュしないようにする方法です。チャンクでマップを分割することに関しては、それは良いアプローチですが、私の場合には望みのものではありません。

更新に応じてInt32.MaxValue、サイズを超える配列を割り当てることはできません。チャンクに分割する必要があります。配列のようなファサードを公開するラッパークラスにカプセル化することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.