Apa cara yang baik untuk menyimpan data tilemap?

12

Saya sedang mengembangkan platformer 2D dengan beberapa teman uni. Kami telah mendasarkannya pada XNA Platformer Starter Kit yang menggunakan file .txt untuk menyimpan peta ubin. Meskipun ini sederhana, itu tidak memberi kita kontrol dan fleksibilitas yang cukup dengan desain level. Beberapa contoh: untuk beberapa lapisan konten diperlukan beberapa file, setiap objek diperbaiki ke grid, tidak memungkinkan untuk rotasi objek, jumlah karakter yang terbatas, dll. Jadi saya sedang melakukan penelitian tentang cara menyimpan data level dan file peta.

Ini hanya menyangkut penyimpanan sistem file peta ubin, bukan struktur data yang akan digunakan oleh game saat sedang berjalan. Peta petak dimuat ke dalam array 2D, jadi pertanyaan ini adalah tentang sumber dari mana untuk mengisi array.

Alasan DB: Dari sudut pandang saya, saya melihat redundansi data yang lebih sedikit menggunakan database untuk menyimpan data ubin. Ubin dalam posisi x, y yang sama dengan karakteristik yang sama dapat digunakan kembali dari level ke level. Sepertinya cukup sederhana untuk menulis metode untuk mengambil semua ubin yang digunakan pada level tertentu dari database.

Alasan JSON / XML: File yang dapat diedit secara visual, perubahan dapat dilacak melalui SVN jauh lebih mudah. Tetapi ada konten berulang.

Apakah ada kekurangan (waktu buka, waktu akses, memori dll) dibandingkan dengan yang lain? Dan apa yang biasa digunakan di industri?

Saat ini file terlihat seperti ini:

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

1 - Titik awal pemain, X - Level Keluar,. - Ruang kosong, # - Platform, G - Gem

Stephen Tierney
sumber
2
"Format" apa yang Anda gunakan? Hanya mengatakan "file teks" berarti Anda tidak menyimpan data biner. Ketika Anda mengatakan "tidak cukup kontrol dan fleksibilitas", apa, khususnya masalah yang Anda hadapi? Mengapa undian antara XML dan SQLite? Itu akan menentukan jawabannya. blog.stackoverflow.com/2011/08/gorilla-vs-shark
Tetrad
Saya akan memilih JSON karena lebih mudah dibaca.
Den
3
Mengapa orang berpikir tentang menggunakan SQLite untuk hal-hal ini? Ini adalah basis data relasional ; mengapa orang berpikir bahwa database relasional membuat format file yang bagus?
Nicol Bolas
1
@StephenTierney: Mengapa pertanyaan ini tiba-tiba berubah dari XML vs SQLite ke JSON vs segala jenis database? Pertanyaan saya secara khusus mengapa tidak adil, "Apa cara yang baik untuk menyimpan data tilemap?" Pertanyaan X vs. Y ini arbitrer dan tidak berarti.
Nicol Bolas
1
@ Silyl: Tapi itu tidak bekerja dengan baik. Sangat sulit untuk diedit, karena Anda hanya dapat mengedit database melalui perintah SQL. Sulit dibaca, karena tabel sebenarnya bukan cara yang efektif untuk melihat tilemap dan memiliki pemahaman tentang apa yang terjadi. Dan sementara itu bisa melakukan pencarian, prosedur pencarian sangat rumit. Membuat sesuatu berfungsi adalah penting, tetapi jika itu akan membuat proses pengembangan game menjadi jauh lebih sulit, maka tidak ada gunanya mengambil jalan pendek.
Nicol Bolas

Jawaban:

13

Jadi membaca pertanyaan Anda yang diperbarui, tampaknya Anda paling khawatir tentang "data yang berlebihan" pada disk, dengan beberapa kekhawatiran sekunder tentang waktu muat dan apa yang digunakan industri.

Pertama, mengapa Anda khawatir tentang data yang berlebihan? Bahkan untuk format kembung seperti XML, akan sangat sepele untuk menerapkan kompresi dan menurunkan ukuran level Anda. Anda lebih cenderung menghabiskan ruang dengan tekstur atau suara daripada data level Anda.

Kedua, format biner lebih dari mungkin akan memuat lebih cepat daripada yang berbasis teks yang Anda harus parsing. Ragu jadi jika Anda hanya bisa menjatuhkannya di memori dan memiliki struktur data Anda di sana. Namun, ada beberapa kelemahannya. Untuk satu, file biner hampir tidak mungkin untuk debug (terutama untuk orang-orang pembuat konten). Anda tidak dapat membedakan atau membuat versinya. Tetapi mereka akan lebih kecil, dan memuat lebih cepat.

Apa yang dilakukan beberapa mesin (dan apa yang saya anggap sebagai situasi ideal) adalah menerapkan dua jalur pemuatan. Untuk pengembangan, Anda menggunakan semacam format teks. Tidak masalah format spesifik apa yang Anda gunakan, selama Anda menggunakan pustaka yang solid. Untuk rilis, Anda beralih ke memuat versi biner (lebih kecil, memuat lebih cepat, mungkin dilucuti dari entitas debug). Alat Anda untuk mengedit level memuntahkan keduanya. Ini lebih banyak pekerjaan daripada hanya satu format file, tetapi Anda bisa mendapatkan yang terbaik dari kedua dunia.

Semua yang dikatakan, saya pikir Anda sedikit melompat pistol.

Langkah pertama untuk masalah ini selalu menggambarkan masalah yang Anda hadapi secara menyeluruh. Jika Anda tahu data apa yang Anda butuhkan, maka Anda tahu data apa yang perlu Anda simpan. Dari sana itu pertanyaan optimasi yang dapat diuji.

Jika Anda memiliki use case untuk diuji, maka Anda dapat menguji beberapa metode berbeda untuk menyimpan / memuat data untuk mengukur apa pun yang Anda anggap penting (waktu pemuatan, penggunaan memori, ukuran disk, dll.). Tanpa mengetahui semua itu, sulit untuk lebih spesifik.

Tetrad
sumber
8
  • XML: Mudah diedit dengan tangan tetapi begitu Anda mulai memuat banyak data, ia melambat.
  • SQLite: Ini mungkin lebih baik jika Anda ingin mengambil banyak data sekaligus atau bahkan hanya potongan kecil. Namun, kecuali Anda menggunakan ini di tempat lain dalam gim Anda, saya pikir ini terlalu sulit untuk peta Anda (dan mungkin terlalu rumit).

Rekomendasi saya adalah menggunakan format file biner khusus. Dengan permainan saya, saya hanya memiliki SaveMapmetode yang melewati dan menyimpan setiap bidang menggunakan a BinaryWriter. Ini juga memungkinkan Anda memilih untuk mengompresnya jika Anda mau dan memberi Anda kontrol lebih besar atas ukuran file. yaitu Hemat shortdaripada intjika Anda tahu itu tidak akan lebih besar dari 32767. Dalam lingkaran besar, menyimpan sesuatu sebagai shortganti intdapat berarti ukuran file yang jauh lebih kecil.

Juga, jika Anda menggunakan rute ini, saya sarankan variabel pertama Anda dalam file menjadi nomor versi.

Pertimbangkan misalnya, kelas peta (sangat disederhanakan):

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
    }
}

Sekarang, katakanlah Anda ingin menambahkan properti baru ke peta Anda, mengatakan Listdari Platformobjek. Saya memilih ini karena sedikit lebih terlibat.

Pertama-tama, Anda menambahkan MapVersiondan menambahkan List:

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

Kemudian, perbarui metode penyimpanan:

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
}

Lalu, dan di sinilah Anda benar-benar melihat manfaatnya, perbarui metode memuat:

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
}

Seperti yang Anda lihat, LoadMapsmetode ini tidak hanya dapat memuat versi terbaru dari peta, tetapi juga versi yang lebih lama! Saat memuat peta yang lebih lama, Anda memiliki kontrol atas nilai default yang digunakannya.

Richard Marskell - Drackir
sumber
8

Cerita pendek

Alternatif rapi untuk masalah ini adalah menyimpan level Anda dalam bitmap, satu piksel per ubin. Menggunakan RGBA ini dengan mudah memungkinkan empat dimensi yang berbeda (lapisan, id, rotasi, warna, dll.) Disimpan pada satu gambar.

Cerita panjang

Pertanyaan ini mengingatkan saya pada saat Notch menyiarkan entri Ludum Dare-nya beberapa bulan yang lalu, dan saya ingin berbagi jika Anda tidak tahu apa yang dia lakukan. Saya pikir itu sangat menarik.

Pada dasarnya, dia menggunakan bitmap untuk menyimpan levelnya. Setiap piksel dalam bitmap berhubungan dengan satu "ubin" di dunia (bukan benar-benar ubin dalam kasusnya karena itu adalah permainan raycast tetapi cukup dekat). Contoh salah satu levelnya:

masukkan deskripsi gambar di sini

Jadi jika Anda menggunakan RGBA (8-bit per saluran), Anda dapat menggunakan masing-masing saluran sebagai lapisan yang berbeda, dan hingga 256 jenis ubin untuk masing-masingnya. Apakah itu cukup? Atau misalnya, salah satu saluran dapat menahan rotasi ubin seperti yang Anda sebutkan. Selain itu, membuat editor level untuk bekerja dengan format ini juga harus cukup sepele.

Dan yang terbaik dari semuanya, Anda bahkan tidak memerlukan pengolah konten khusus karena Anda bisa memuatnya ke XNA seperti Texture2D lainnya dan membaca pikselnya kembali dalam satu lingkaran.

IIRC ia menggunakan titik merah murni pada peta untuk memberi sinyal musuh, dan tergantung pada nilai saluran alpha untuk piksel itu akan menentukan jenis musuh (misalnya nilai alpha 255 mungkin kelelawar sementara 254 akan menjadi zombie atau yang lainnya).

Gagasan lain adalah dengan membagi objek permainan Anda ke dalam objek yang diperbaiki dalam kotak, dan yang dapat bergerak "dengan halus" di antara ubin. Simpan ubin tetap dalam bitmap, dan objek dinamis dalam daftar.

Bahkan mungkin ada cara untuk menggandakan lebih banyak informasi ke dalam 4 saluran tersebut. Jika seseorang memiliki ide tentang ini, beri tahu saya. :)

David Gouveia
sumber
3

Anda mungkin bisa lolos dengan hampir semua hal. Sayangnya komputer modern begitu cepat sehingga jumlah data yang relatif kecil ini mungkin tidak benar-benar membuat perbedaan waktu yang nyata, bahkan jika disimpan dalam format data yang kembung dan tidak dibangun dengan baik sehingga membutuhkan banyak sekali pemrosesan untuk dibaca.

Jika Anda ingin melakukan hal yang "benar", Anda membuat format biner seperti yang disarankan Drackir, tetapi jika Anda membuat pilihan lain karena alasan konyol, itu mungkin tidak akan kembali dan menggigit Anda (setidaknya tidak berdasarkan kinerja).

aaaaaaaaaaaa
sumber
1
Ya, itu pasti tergantung pada ukurannya. Saya memiliki peta sekitar 161.000 ubin. Masing-masing memiliki 6 lapisan. Saya awalnya memilikinya dalam XML, tapi 82MB dan butuh selamanya untuk memuat. Sekarang saya menyimpannya dalam format biner dan sekitar 5MB sebelum kompresi dan memuat sekitar 200 ms. :)
Richard Marskell - Drackir 11/11/11
2

Lihat pertanyaan ini: 'Binary XML' untuk data game? Saya juga akan memilih JSON atau YAML atau bahkan XML, tetapi itu memiliki overhead yang cukup banyak. Adapun SQLite, saya tidak akan pernah menggunakannya untuk menyimpan level. Database relasional berguna jika Anda berharap untuk menyimpan banyak data yang terkait (misalnya memiliki tautan relasional / kunci asing) dan jika Anda berharap untuk mengajukan sejumlah besar pertanyaan yang berbeda, menyimpan tilemaps sepertinya tidak melakukan semacam ini. hal-hal dan akan menambah kompleksitas yang tidak perlu.

Roy T.
sumber
2

Masalah mendasar dengan pertanyaan ini adalah bahwa ia mengonfigurasi dua konsep yang tidak ada hubungannya dengan satu sama lain:

  1. Paradigma penyimpanan file
  2. Representasi dalam memori dari data

Anda dapat menyimpan file Anda sebagai teks biasa dalam format sewenang-wenang, JSON, skrip Lua, XML, format biner sewenang-wenang, dll. Dan tidak satu pun dari itu yang mengharuskan representasi dalam memori Anda dari data tersebut mengambil bentuk apa pun.

Ini adalah tugas kode pemuatan level Anda untuk mengubah paradigma penyimpanan file menjadi representasi di dalam memori Anda. Misalnya, Anda berkata, "Saya melihat redundansi data yang lebih sedikit menggunakan database untuk menyimpan data ubin." Jika Anda ingin redundansi lebih sedikit, itu adalah sesuatu yang harus dilihat oleh kode pemuatan level Anda. Itu adalah sesuatu yang harus bisa ditangani oleh representasi di memori Anda.

Ini bukan sesuatu yang perlu diperhatikan oleh format file Anda. Dan inilah alasannya: file-file itu harus berasal dari suatu tempat.

Entah Anda akan menulis ini dengan tangan, atau Anda akan menggunakan semacam alat yang membuat dan mengedit data level. Jika Anda menulisnya dengan tangan, maka hal terpenting yang Anda butuhkan adalah format yang mudah dibaca dan dimodifikasi. Redundansi data bukan sesuatu yang perlu dipertimbangkan oleh format Anda, karena Anda akan menghabiskan sebagian besar waktu Anda mengedit file. Apakah Anda ingin benar-benar harus menggunakan mekanisme apa pun yang Anda buat secara manual untuk menyediakan redundansi data? Bukankah lebih baik menggunakan waktu Anda untuk membuat loader level Anda menanganinya?

Jika Anda memiliki alat yang membuatnya, maka format yang sebenarnya hanya penting (paling-paling) demi keterbacaan. Anda dapat meletakkan apa pun di file ini. Jika Anda ingin menghilangkan data yang berlebihan dalam file, cukup desain format yang dapat melakukannya, dan buat alat Anda menggunakan kemampuan itu dengan benar. Format tilemap Anda dapat mencakup kompresi RLE (run-length encoding), kompresi duplikasi, teknik kompresi apa pun yang Anda inginkan, karena ini adalah format tilemap Anda .

Masalah terpecahkan.

Relational Databases (RDB) ada untuk menyelesaikan masalah melakukan pencarian kompleks pada dataset besar yang berisi banyak bidang data. Satu-satunya jenis pencarian yang pernah Anda lakukan di peta ubin adalah "dapatkan ubin di posisi X, Y". Setiap array dapat mengatasinya. Menggunakan database relasional untuk menyimpan dan memelihara data tilemap akan sangat berlebihan, dan tidak benar-benar bernilai apa pun.

Bahkan jika Anda membangun beberapa kompresi ke dalam representasi di dalam memori Anda, Anda masih dapat mengalahkan RDB dengan mudah dalam hal kinerja dan jejak memori. Ya, Anda harus benar-benar menerapkan kompresi itu. Tetapi jika ukuran data dalam memori sedemikian rupa sehingga Anda akan mempertimbangkan RDB, maka Anda mungkin ingin benar-benar menerapkan jenis kompresi tertentu. Anda akan lebih cepat daripada RDB dan lebih rendah di memori pada saat yang sama.

Nicol Bolas
sumber
1
  • XML: sangat mudah digunakan, tetapi overhead menjadi menjengkelkan ketika struktur semakin dalam.
  • SQLite: lebih nyaman untuk struktur yang lebih besar.

Untuk data yang sangat sederhana, saya mungkin hanya akan pergi rute XML, jika Anda sudah terbiasa dengan pemuatan dan penguraian XML.

Insinyur
sumber