マップ全体の設計とタイル配列の設計


10

私は2D RPGに取り組んでいます。これは通常のダンジョン/タウンマップ(事前生成)をフィーチャーします。

私はタイルを使用しており、それを組み合わせてマップを作成します。私の当初の計画は、Photoshopまたは他のグラフィックプログラムを使用してタイルを組み立て、1つの大きな画像をマップとして使用できるようにすることでした。

ただし、配列を使用してエンジンでマップを構築する方法について人々が話している場所をいくつか読んだことがあります(そのため、xタイルの配列をエンジンに与え、それをマップとして組み立てます)。方法は理解できますが、実装がはるかに複雑になり、明確な利点がわかりません。

最も一般的な方法は何ですか、それぞれの長所/短所は何ですか?


1
主な問題はメモリです。1080pスクリーンを1回埋めるテクスチャは、約60MBです。したがって、画像はピクセルあたりの整数を使用し、タイルは(ピクセル/(タイルサイズ*タイルサイズ))あたりの整数を使用します。したがって、タイルが32、32の場合、タイルとピクセルを使用して、1024倍の大きさでマップを表すことができます。また、テクスチャスワッピング時間は、そのような多くのメモリのプッシュなどに悪影響を与えます...
ClassicThunder

回答:


18

まず、2D RPGは私の心臓に近く、親しみやすく、古いDX7 VB6 MORPGエンジンで動作している(笑いません。8年前のことですが、今:-))ゲーム開発に最初に興味を持ったのはこのためです。 。最近では、これらのエンジンの1つで取り組んだゲームをXNAを使用するように変換し始めました。

そうは言っても、私の推奨は、マップにレイヤー化されたタイルベースの構造を使用することです。使用するグラフィックAPIでは、ロードできるテクスチャのサイズに制限があります。グラフィックカードのテクスチャメモリ制限は言うまでもありません。したがって、これを考慮して、メモリにロードするテクスチャの量とサイズを最小化するだけでなく、ユーザーのハードドライブ上のアセットのサイズとロード時間を削減しながら、マップのサイズを最大化したい場合は、必ずタイルに行きたいです。

実装に関する限り、ここGameDev.SEと私のブログ(以下にリンクされています)のいくつかの質問でそれをどのように処理したかについて詳しく説明しました。ここで基本に進みます。また、いくつかの大きな事前レンダリングされた画像をロードするよりもタイルを有利にするタイルの機能にも注目します。不明な点がある場合はお知らせください。

  1. まず、タイルシートを作成する必要があります。これは、グリッドに配置されたすべてのタイルを含む大きな画像です。ロードする必要があるのは、これ(およびタイルの数によっては追加の1つ)だけです。画像は1つだけです。ゲーム内のマップごとに1つ、またはタイルごとに1つをロードできます。あなたのために働くどんな組織でも。
  2. 次に、その「シート」を取得して、各タイルを数値に変換する方法を理解する必要があります。これは、いくつかの単純な数学ではかなり簡単です。ここでの除算は整数除算であるため、小数点以下は切り捨てられます(必要に応じて切り捨てられます)。 セルを座標に変換して戻す方法。
  3. OK、タイルシートを一連のセル(数値)に分割したので、それらの数値を取得して、好きなコンテナーに接続できます。簡単にするために、2D配列を使用できます。

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. 次に、それらを描画します。これをLOTをより効率的にする方法の1つ(マップサイズに応じて)は、カメラが現在表示しているセルのみを計算し、それらをループすることです。これを行うには、カメラの左上隅(tl)と右下隅(br)のコーナーのマップタイル配列座標をフェッチします。次にtl.X to br.X、from tl.Y to br.Yをループし、ネストされたループでfrom を使用してそれらを描画します。以下のサンプルコード:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
    
  5. ジャックポット!これがタイルエンジンの基本です。オーバーヘッドの少ない1000x1000のマップでも簡単に作成できることがわかります。また、タイル数が255未満の場合は、バイト配列を使用して、セルあたり3バイトずつメモリを削減できます。バイトが小さすぎる場合は、おそらくushortで十分です。

注:私は世界座標の概念(カメラの位置の基準となるもの)を省略しました。これは、この回答の範囲外であるためです。これについては、GameDev.SEで確認できます。

私のタイルエンジンリソース
注:これらはすべてXNAを対象としていますが、ほとんどすべてに適用されます。ドローコールを変更する必要があるだけです。

  • この質問に対する私の答えは、ゲームでマップセルとレイヤーを処理する方法の概要を示しています。(3番目のリンクを参照してください。)
  • この質問に対する私の回答は、データをバイナリ形式で格納する方法を説明しています。
  • これは、私が取り組んでいたゲームの開発に関する最初の(技術的には2番目ですが、最初は「技術的」)ブログ投稿です。シリーズ全体には、ピクセルシェーダー、照明、タイルの動作、動きなどの楽しいものに関する情報が含まれています。私は実際に投稿を更新して、上記で投稿した最初のリンクへの私の回答の内容を含めました。ご不明な点がございましたら、こちらまたはこちらからコメントをお寄せください。喜んでお手伝いさせていただきます。

その他のタイルエンジンリソース

  • このサイトのタイルエンジンチュートリアルは、マップの作成に使用した基礎を教えてくれました。
  • 時間がないので、これらのビデオチュートリアルはまだ実際には見ていませんが、おそらく参考になります。:)ただし、XNAを使用している場合は、古くなっている可能性があります。
  • このサイトには、上記のビデオに基づいた(私が思うに)いくつかのチュートリアルがあります。チェックアウトする価値があるかもしれません。

うわー、それは私が尋ねたよりも2倍多くの情報であり、私が尋ねなかったすべての質問にかなり答えます、すばらしい、ありがとう:)
Cristol.GdM

@Mikalichov-助けてくれてうれしい!:)
Richard Marskell-Drackir 2012年

通常、2D配列では、最初の次元をYとして使用し、次の次元をXとして使用します。メモリでは、配列は0,0-i、次に1,0-iの順になります。Xの最初のヘンでネストされたループを実行すると、実際には、読み取りの順次パスをたどるのではなく、メモリ内を前後にジャンプします。常に(y)の次に(x)の場合。
Brett W

@BrettW-有効なポイント。ありがとう。2D配列が.Netにどのように格納されるのか不思議に思いました(行優先順。今日何か新しいことを学びました!:-))。しかし、コードを更新した後、変数を切り替えただけで、あなたが説明しているものとまったく同じであることに気付きました。違いは、タイルが描画される順序にあります。現在のサンプルコードでは、上から下、左から右に描画しますが、ここで説明するコードでは、左から右、上から下に描画します。そこで、簡単にするために、元に戻すことにしました。:-)
Richard Marskell-Drackir 2012年

6

タイルベースのシステムと静的モデル/テクスチャシステムの両方を使用して世界を表すことができ、それぞれに異なる長所があります。片方がもう片方よりも優れているかどうかは、断片の使用方法と、スキルセットとニーズに最適なものに要約されます。

とはいえ、両方のシステムを別々に、または一緒に使用することもできます。

標準のタイルベースのシステムには、次の長所があります。

  • タイルの単純な2D配列
  • 各タイルには単一のマテリアルがあります
    • このマテリアルは、単一のテクスチャ、複数のテクスチャ、または周囲のタイルからブレンドされたものである可能性があります
    • そのタイプのすべてのタイルのテクスチャを再利用できるため、アセットの作成と手直しが軽減されます
  • 各タイルは合格または不合格にできます(衝突検出)
  • 地図の一部を簡単にレンダリングして識別する
    • キャラクター位置とマップ位置は絶対座標
    • 画面領域に関連するタイルをループするのが簡単

タイルの欠点は、このタイルベースのデータを構築するためのシステムを作成する必要があることです。各ピクセルをタイルとして使用する画像を作成し、テクスチャから2D配列を作成できます。独自のフォーマットを作成することもできます。ビットフラグを使用すると、かなり小さなスペースにタイルごとに大量のデータを格納できます。

ほとんどの人がタイルを使用する主な理由は、小さなアセットを作成して再利用できるためです。これにより、はるかに大きな画像を作成し、小さなピースを作成できます。小さな変更を加えるために世界地図全体を変更するわけではないので、手直しが減ります。たとえば、すべての芝生の色合いを変更したい場合。大きな画像では、すべての草を再描画する必要があります。タイルシステムでは、グラスタイルを更新するだけです。

全体的に見て、タイルベースのシステムでは、大きなグラフィックマップよりも大幅に手直しを減らすことができます。地上マップに使用しなくても、衝突にタイルベースのシステムを使用することになるかもしれません。内部でタイルを使用するからといって、空間を複数のタイルで使用する可能性がある環境のオブジェクトを表すためにモデルを使用できないわけではありません。

コード例を提供するには、環境/エンジン/言語の詳細を提供する必要があります。


ありがとう!私はC#の下で作業しており、ファイナルファンタジー6(または基本的にほとんどのSquare SNES RPG)の同様の(ただし、あまり才能のない)バージョンを使用する予定なので、床タイルと構造タイル(つまり、家)です。私の最大の懸念は、「そこに芝生のコーナーがあり、次に3つのまっすぐな水平、次に家のコーナーがある」というチェックに何時間も費やすことなく2Dアレイを構築する方法がわからないことです。仕方。
Cristol.GdM 2012年

Richardはコードの詳細に関してあなたに説明してくれたようです。私は個人的にはタイルタイプの列挙を使用し、ビットフラグを使用してタイルの値を識別します。これにより、平均整数に32以上の値を格納できます。タイルごとに複数のフラグを保存することもできます。したがって、Grass = true、Wall = true、Collision = trueなどを、個別の変数なしで言うことができます。次に、その特定のマップ領域のグラフィックのタイルシートを決定する「テーマ」を割り当てるだけです。
Brett W

3

マップの描画:マップはタイルインデックスの巨大な配列として表すことができるため、タイルはエンジンで簡単です。描画コードは単にネストされたループです:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

大きなマップの場合、マップ全体の1つの巨大なビットマップイメージは、メモリ内で多くのスペースを占める可能性があり、グラフィックカードが単一のイメージサイズでサポートするものよりも大きくなる可能性があります。ただし、グラフィックメモリはタイルごとに1回だけ割り当てるので(タイルがすべて1つのシートにある場合は、合計1つが最適です)、タイルに関する問題は発生しません。

衝突などのタイル情報を再利用することも簡単です。たとえば、キャラクターのスプライトの左上隅にあるテレインタイルを取得するには:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

岩や木などとの衝突をチェックする必要があるとします。(キャラクターの位置+キャラクターのスプライトサイズ+現在の方向)の位置にタイルを取得し、歩行可能または歩行不可能としてマークされているかどうかを確認します。

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