タイルマップデータを保存する良い方法は何ですか?


12

私はいくつかの大学の友人と2Dプラットフォーマーを開発しています。.txtファイルを使用してタイルマップを保存するXNA Platformer Starter Kitに基づいています。これは単純ですが、レベル設計で十分な制御と柔軟性を提供するものではありません。いくつかの例:コンテンツの複数のレイヤーに複数のファイルが必要な場合、各オブジェクトはグリッドに固定され、オブジェクトの回転、文字数の制限などが許可されません。したがって、レベルデータの格納方法について調査しています。およびマップファイル。

これは、タイルマップのファイルシステムストレージのみに関係し、ゲームの実行中にゲームで使用されるデータ構造には関係しません。タイルマップは2D配列に読み込まれるため、この質問はどのソースから配列を埋めるかに関するものです。

DBの理由:私の観点からは、データベースを使用してタイルデータを保存するデータの冗長性が少ないと思います。同じ特性を持つ同じx、y位置のタイルは、レベルごとに再利用できます。特定のレベルで使用されているすべてのタイルをデータベースから取得するメソッドを作成するのは十分簡単だと思われます。

JSON / XMLの推論:視覚的に編集可能なファイル。変更はSVNを介してはるかに簡単に追跡できます。しかし、繰り返しコンテンツがあります。

どちらかが他に比べて欠点(ロード時間、アクセス時間、メモリなど)がありますか?そして、業界で一般的に使用されているものは何ですか?

現在、ファイルは次のようになっています。

....................
....................
....................
....................
....................
....................
....................
.........GGG........
.........###........
....................
....GGG.......GGG...
....###.......###...
....................
.1................X.
####################

1-プレーヤーの開始点、X-レベル出口、。-空のスペース、#-プラットフォーム、G-宝石


2
どんな既存の「フォーマット」を使用していますか?「テキストファイル」と言うだけでは、バイナリデータを保存していないことになります。「十分な制御と柔軟性がありません」と言うとき、具体的にどのよう問題に直面していますか?なぜXMLとSQLiteの間にあるのでしょうか?それが答えを決定します。 blog.stackoverflow.com/2011/08/gorilla-vs-shark
Tetrad

読みやすいので、JSONを選択します。
デン

3
なぜ人々はこれらのことのためにSQLiteを使用することを考えるのですか?それはだリレーショナルデータベース。なぜ人々はリレーショナルデータベースが良いファイル形式を作ると思うのですか?
ニコルボーラス

1
@StephenTierney:なぜこの質問は突然XML対SQLiteからJSON対任意の種類のデータベースになったのですか?私の質問は、「タイルマップデータを保存する良い方法は何ですか?」だけではない理由です。このX対Yの質問はarbitrary意的で無意味です。
ニコルボーラス

1
@Kylotan:しかし、それはあまりうまくいきません。SQLコマンドを介してのみデータベースを編集できるため、編集は非常に困難です。テーブルは実際にはタイルマップを見て、何が起こっているのかを理解するのに効果的な方法ではないため、読みにくいです。また、ルックアップを実行できますが、ルックアップ手順は非常に複雑です。何かを機能させることは重要ですが、実際にゲームを開発するプロセスをそれほど難しくするのであれば、短い道を歩む価値はありません。
ニコルボーラス

回答:


13

更新された質問を読んで、ディスク上の「冗長データ」について最も懸念しているようですが、ロード時間と業界が使用するものについての二次的な懸念があります。

まず、なぜ冗長データが心配なのですか?XMLのような肥大化したフォーマットであっても、圧縮を実装してレベルのサイズを小さくすることは非常に簡単です。レベルデータよりも、テクスチャまたはサウンドでスペースを占有する可能性が高くなります。

第二に、バイナリ形式は、解析する必要のあるテキストベースの形式よりも高速にロードされる可能性が高くなります。二重に、メモリにドロップして、そこにデータ構造を置くことができる場合。ただし、それにはいくつかの欠点があります。1つは、バイナリファイルのデバッグがほとんど不可能なことです(特にコンテンツ作成の場合)。それらを比較したりバージョン管理したりすることはできません。ただし、サイズは小さくなり、ロードは高速になります。

一部のエンジンが行うこと(および理想的な状況と考えること)は、2つのロードパスを実装することです。開発では、何らかのテキストベースの形式を使用します。安定したライブラリを使用している限り、どの特定のフォーマットを使用するかは問題ではありません。リリースでは、バイナリ(より小さく、高速なロード、場合によってはデバッグエンティティの削除)バージョンのロードに切り替えます。レベルを編集するツールは両方を吐き出します。1つのファイル形式よりも多くの作業が必要になりますが、両方の長所を最大限に活用できます。

言われていることはすべて、あなたは銃を少し跳躍していると思います。

これらの問題のステップ1は、実行中の問題を常に徹底的に説明することです。必要なデータがわかっている場合は、保存する必要があるデータがわかります。そこからは、テスト可能な最適化の質問です。

テストするユースケースがある場合、データを保存/ロードするいくつかの異なる方法をテストして、重要だと思われるもの(ロード時間、メモリ使用量、ディスクサイズなど)を測定できます。そのいずれも知らずに、より具体的にすることは困難です。


8
  • XML:手で簡単に編集できますが、大量のデータの読み込みを開始すると、速度が低下します。
  • SQLite:大量のデータを一度に取得したい場合や、小さなチャンクだけを取得したい場合は、これが良いかもしれません。ただし、これをゲームの他の場所で使用している場合を除き、マップが過剰に機能していると思われます(おそらく複雑すぎます)。

私の推奨事項は、カスタムバイナリファイル形式を使用することです。私のゲームでは、をSaveMap使用して各フィールドを通過して保存するメソッドがありますBinaryWriter。これにより、必要に応じて圧縮することもでき、ファイルサイズをより細かく制御できます。つまり、32767より大きくならないことがわかってshortいるint場合は、代わりにを保存します。大きなループでは、何かをのshort代わりに保存すると、intファイルサイズがかなり小さくなります。

また、このルートを使用する場合は、ファイルの最初の変数をバージョン番号にすることをお勧めします。

たとえば、マップクラス(非常に単純化された)を考えます。

class Map {
    private const short MapVersion = 1;
    public string Name { get; set; }

    public void SaveMap(string filename) {
        //set up binary writer
        bw.Write(MapVersion);
        bw.Write(Name);
        //close/dispose binary writer
    }
    public void LoadMap(string filename) {
        //set up binary reader
        short mapVersion = br.ReadInt16();
        Name = br.ReadString();
        //close/dispose binary reader
    }
}

さて、あなたはあなたのマップに新しいプロパティを追加したいとしましょう、と言うListPlatformオブジェクトが。これを選んだのは、もう少し複雑だからです。

まず、をインクリメントしMapVersionて追加しListます:

private const short MapVersion = 2;
public string Name { get; set; }
public List<Platform> Platforms { get; set; }

次に、saveメソッドを更新します。

public void SaveMap(string filename) {
    //set up binary writer
    bw.Write(MapVersion);
    bw.Write(Name);
    //Save the count for loading later
    bw.Write(Platforms.Count);
    foreach(Platform plat in Platforms) {
        //For simplicities sake, I assume Platform has it's own
        // method to write itself to a file.
        plat.Write(bw);
    }
    //close/dispose binary writer
}

次に、これが実際にメリットが得られる場所です。ロード方法を更新します。

public void LoadMap(string filename) {
    //set up binary reader
    short mapVersion = br.ReadInt16();
    Name = br.ReadString();
    //Create our platforms list
    Platforms = new List<Platform>();
    if (mapVersion >= 2) {
        //Version is OK, let's load the Platforms
        int mapCount = br.ReadInt32();
        for (int i = 0; i < mapCount; i++) {
            //Again, I'm going to assume platform has a static Read
            //  method that returns a Platform object
            Platforms.Add(Platform.Read(br));
        }
    } else {
        //If it's an older version, give it a default value
        Platforms.Add(new Platform());
    }
    //close/dispose binary reader
}

ご覧のとおり、このLoadMapsメソッドはマップの最新バージョンだけでなく、古いバージョンもロードできます!古いマップをロードすると、使用するデフォルト値を制御できます。


8

ショートストーリー

この問題に対する適切な代替策は、タイルごとに1ピクセルのビットマップにレベルを保存することです。RGBAを使用すると、4つの異なる次元(レイヤー、ID、回転、色合いなど)を1つの画像に簡単に保存できます。

長い話

この質問は、Notchが数か月前にいつLudum Dareのエントリをライブストリーミングしたかを思い出させてくれました。本当に面白いと思いました。

基本的に、彼はビットマップを使用してレベルを保存しました。ビットマップの各ピクセルは、世界の1つの「タイル」に対応していました(レイキャストゲームであるため実際にはタイルではありませんが、十分に近い)。彼のレベルの1つの例:

ここに画像の説明を入力してください

したがって、RGBA(チャネルごとに8ビット)を使用する場合、各チャネルを異なるレイヤーとして使用し、それぞれに最大256種類のタイルを使用できます。それで十分でしょうか?または、たとえば、チャネルの1つが、前述のようにタイルの回転を保持できます。また、この形式で動作するレベルエディタを作成することも非常に簡単です。

そして何よりも、他のTexture2DのようにXNAにロードして、ピクセルをループで読み取ることができるため、カスタムコンテンツプロセッサも必要ありません。

IIRCは、マップ上の純粋な赤い点を使用して敵に信号を送り、そのピクセルのアルファチャネルの値に応じて敵のタイプを決定します(たとえば、255のアルファ値はコウモリであり、254はゾンビか何か)。

もう1つのアイデアは、ゲームオブジェクトをグリッドに固定されたオブジェクトと、タイル間を「細かく」移動できるオブジェクトに分割することです。ビットマップに固定タイルを保持し、リストに動的オブジェクトを保持します。

これらの4つのチャネルにさらに多くの情報を多重化する方法もあります。誰かがこれについて何か考えを持っているなら、私に知らせてください。:)


3

おそらくほとんど何でも逃げることができます。残念ながら、現代のコンピューターは非常に高速であるため、巨大な量の処理を必要とする巨大な構成のデータ形式で保存されたとしても、この比較的少量のデータが実際に顕著な時間差を生じることはありません。

「正しい」ことをしたい場合は、Drackirが提案するようなバイナリ形式を作成しますが、何らかのばかげた理由で別の選択をした場合、おそらく戻って来ません(少なくともパフォーマンスに関しては)。


1
ええ、それは間違いなくサイズに依存します。約161,000タイルのマップがあります。それぞれに6つの層があります。私はもともとXMLでそれを持っていましたが、それは82MBであり、ロードするのに永遠にかかりました。今、私はそれをバイナリ形式で保存し、圧縮前に約5mbで、約200msでロードします。:)
リチャードマースケル-Drackir

2

この質問をご覧ください:ゲームデータの「バイナリXML」ですか?また、JSON、YAML、またはXMLを選択しますが、これにはかなりのオーバーヘッドがあります。SQLiteに関しては、レベルの保存には決して使用しません。リレーショナルデータベースは、関連するデータ(たとえば、リレーショナルリンク/外部キーがある)を大量に保存する場合や、大量の異なるクエリを要求する場合に便利です。タイルマップを保存しても、これらの種類の物事と不必要な複雑さを追加します。


2

この質問の根本的な問題は、互いに関係のない2つの概念をまとめることです。

  1. ファイルストレージパラダイム
  2. データのメモリ内表現

あなたは、いくつかの任意の形式のプレーンテキストとしてファイルを保存することができ、JSONは、そのLuaのスクリプト、XML、任意のバイナリフォーマットなどどれもしますが必要です、そのデータのあなたのインメモリ表現は、任意の特定の形を取ること。

ファイルストレージパラダイムをメモリ内表現に変換するのは、コードをロードするレベルの仕事です。たとえば、「データベースを使用してタイルデータを保存すると、データの冗長性が低くなります」と言います。冗長性を減らしたい場合は、レベルローディングコードに注意する必要があります。これは、インメモリ表現で処理できるものでなければなりません。

ファイル形式を気にする必要はありません。そして、ここに理由があります:それらのファイルはどこかから来なければなりません。

これらを手書きで書くか、レベルデータを作成および編集する何らかのツールを使用します。手書きの場合、必要な最も重要なことは、読みやすく変更しやすい形式です。データの冗長性は、あなたのフォーマットでさえ考慮する必要があるものではありません。、あなたのファイルを編集する時間を。データの冗長性を提供するために、思いついたメカニズムを手動で実際に使用する必要がありますか?レベルローダーで処理する方が時間を有効に活用できませんか?

それらを作成するツールを使用している場合、実際の形式は読みやすくするために(せいぜい)重要なだけです。これらのファイルには何でも入れることができます。ファイル内の冗長データを省略したい場合は、それが可能な形式を設計し、ツールがその機能を正しく使用するようにします。タイルマップ形式には、RLE(ランレングスエンコーディング)圧縮、複製圧縮、その他の圧縮技術を含めることができます。、あなたのタイルマップ形式。

問題が解決しました。

リレーショナルデータベース(RDB)は、多くのデータフィールドを含む大規模なデータセットで複雑な検索を実行する問題を解決するために存在します。タイルマップで行う唯一の種類の検索は、「位置X、Yでタイルを取得」です。どの配列でもそれを処理できます。リレーショナルデータベースを使用してタイルマップデータを保存および維持することは、非常にやり過ぎであり、何の価値もありません。

インメモリ表現に何らかの圧縮を組み込んだとしても、パフォーマンスとメモリフットプリントの点でRDBを簡単に倒すことができます。はい、実際にその圧縮を実装する必要があります。ただし、メモリ内のデータサイズがRDBを考慮するほど懸念される場合は、特定の種類の圧縮を実際に実装する必要があります。RDBよりも高速で、同時にメモリが少なくなります。


1
  • XML:使い方は本当に簡単ですが、構造が深くなるとオーバーヘッドが面倒になります。
  • SQLite:構造が大きいほど便利です。

本当に単純なデータの場合、XMLの読み込みと解析に既に慣れているのであれば、おそらくXMLのルートに行くだけでしょう。

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