Який хороший спосіб зберігати дані мапи плитки?


13

Я розробляю 2D-платформу з деякими друзями-uni. Ми базуємо його на XNA Platformer Starter Kit, який використовує .txt файли для зберігання карти плиток. Хоча це просто, але це не дає нам достатнього контролю та гнучкості при рівні дизайну. Деякі приклади: для декількох шарів вмісту потрібно кілька файлів, кожен об'єкт закріплений на сітці, не дозволяє обертати об'єкти, обмежена кількість символів і т. Д. Тож я роблю кілька досліджень, як зберігати дані про рівень і файл карти.

Це стосується лише файлової системи зберігання плиткових карт, а не структури даних, яку повинна використовувати гра під час її запуску. Карта плиток завантажується у двовимірний масив, тому це питання стосується того, з якого джерела заповнити масив.

Обґрунтування БД: З моєї точки зору, я бачу менше надмірності даних, використовуючи базу даних для зберігання даних плиток. Плитки в одному і тому ж х, у положенні з тими ж характеристиками можна повторно використовувати від рівня до рівня. Здається, було б досить просто написати метод для отримання всіх плиток, які використовуються на певному рівні з бази даних.

Обґрунтування JSON / XML: Файли, візуально редаговані, зміни можна відстежувати через SVN набагато простіше. Але є повторний зміст.

Чи мають якісь недоліки (час завантаження, час доступу, пам'ять тощо) порівняно з іншими? А що зазвичай використовується у галузі?

Наразі файл виглядає так:

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

1 - початкова точка гравця, X - рівень виходу,. - Порожній простір, # - Платформа, G - Gem


2
Який існуючий "формат" ви використовуєте? Якщо сказати "текстові файли", це означає, що ви не зберігаєте двійкові дані. Якщо ви кажете "недостатньо контролю та гнучкості", які конкретно проблеми, з якими ви стикаєтесь? Чому перекидання між XML і SQLite? Це і визначить відповідь. blog.stackoverflow.com/2011/08/gorilla-vs-shark
Тетраду

Я б вибрав JSON, оскільки він читабельніший.
День

3
Чому люди думають про використання SQLite для цих речей? Це реляційна база даних ; чому люди думають, що реляційна база даних робить хороший формат файлів?
Ніколь Болас

1
@StephenTierney: Чому це питання раптом перейшло від XML проти SQLite до JSON порівняно з будь-якою базою даних? Моє запитання конкретно, чому це не просто "Який хороший спосіб зберігати дані мапи черепиці?" Це питання X і Y є довільним і безглуздим.
Ніколь Болас

1
@Kylotan: Але це працює не дуже добре. Це надзвичайно важко редагувати, оскільки ви можете редагувати базу даних лише за допомогою команд SQL. Важко читати, оскільки таблиці насправді не є ефективним способом перегляду карти календаря та розуміння того, що відбувається. І хоча він може робити пошук, процедура пошуку надзвичайно складна. Налагодити щось працює дуже важливо, але якщо це буде зробити процес фактичної розробки гри набагато складнішим, тоді не варто брати короткий шлях.
Нікол Болас

Відповіді:


14

Отож, читаючи ваше оновлене запитання, вам здається, що вас найбільше турбує "зайві дані" на диску, з деякими вторинними проблемами щодо часу завантаження та того, що використовує галузь.

По-перше, чому ви турбуєтесь про зайві дані? Навіть для такого роздутого формату, як XML, було б досить тривіально реалізувати стиснення та зменшити розмір ваших рівнів. Ви швидше займаєте простір текстурами чи звуками, ніж дані рівня.

По-друге, бінарний формат швидше завантажується швидше, ніж текстовий, який потрібно розбирати. Удвічі так, якщо ви можете просто залишити його в пам'яті і мати свої структури даних саме там. Однак у цьому є деякі недоліки. Для одного, бінарні файли поруч із неможливою налагодження (особливо для людей, що створюють вміст). Ви не можете їх відрізняти чи версувати. Але вони будуть меншими, і завантажуватимуться швидше.

Те, що деякі двигуни роблять (і що я вважаю ідеальною ситуацією), - це реалізація двох шляхів завантаження. Для розробки ви використовуєте якийсь текстовий формат. Насправді не важливо, який саме формат ви використовуєте, якщо ви використовуєте надійну бібліотеку. Для випуску ви переходите до завантаження двійкової (меншої, швидшої завантаження, можливо, позбавленої налагодження). Ваші інструменти для редагування рівнів виплюнули обидва. Це набагато більше роботи, ніж лише один формат файлу, але ви можете якось краще отримати обидва світи.

Враховуючи це, я думаю, ви трохи стрибаєте з пістолета.

Перший крок до цих проблем - це завжди докладно описати проблему, з якою ви стикаєтесь. Якщо ви знаєте, які дані вам потрібні, то ви знаєте, які дані потрібно зберігати. Звідси постає питання про тестування оптимізації.

Якщо у вас є випадок використання для тестування, ви можете протестувати кілька різних способів зберігання / завантаження даних, щоб виміряти те, що ви вважаєте важливим (час завантаження, використання пам'яті, розмір диска тощо). Не знаючи нічого з цього, важко бути більш конкретним.


9
  • XML: Легко редагувати вручну, але як тільки ви почнете завантажувати багато даних, він сповільнюється.
  • SQLite: Це може бути краще, якщо ви хочете отримати багато даних одночасно або навіть лише невеликі шматки. Однак, якщо ви не використовуєте це десь у грі, я думаю, що це надмірно для ваших карт (і, ймовірно, надскладне).

Моя рекомендація - використовувати нестандартний бінарний формат файлу. У моїй грі у мене просто є SaveMapметод, який проходить і зберігає кожне поле за допомогою BinaryWriter. Це також дозволяє вибрати його для стиснення, якщо ви хочете, і дає вам більше контролю над розміром файлу. тобто збережіть shortзамість, intякщо ви знаєте, що він не буде більшим, ніж 32767. У великому циклі збереження чогось як 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
    }
}

Скажімо, ви хотіли додати нову властивість до своєї карти, скажімо, Listпро Platformоб’єкти. Я вибрав це, тому що це трохи більше.

Перш за все, ви збільшуєте MapVersionта додаєте List:

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

Потім оновіть метод збереження:

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метод може завантажувати не тільки останню версію карти, але й більш старі версії! Коли він завантажує старі карти, ви маєте контроль над типовими значеннями, які він використовує.


9

Коротка розповідь

Прекрасна альтернатива цій проблемі - зберігати свої рівні в растровій карті, один піксель на плитку. Використання RGBA дозволяє легко зберігати чотири різних розміри (шари, ідентифікатори, обертання, відтінок тощо) на одному зображенні.

Довга історія

Це питання нагадало мені, коли Notch в прямому ефірі перейшов його запис Ludum Dare кілька місяців тому, і я хотів би поділитися, якщо ви не знаєте, що він зробив. Я думав, що це дійсно цікаво.

В основному, він використовував растрові карти, щоб зберігати свої рівні. Кожен піксель у растровій карті відповідав одній "плитці" у світі (насправді це не плитка в його випадку, оскільки це була гра, яка була розстріляна, але досить близька). Приклад одного з його рівнів:

введіть тут опис зображення

Отже, якщо ви використовуєте RGBA (8 біт на канал), ви можете використовувати кожен канал як інший шар і до 256 типів плиток для кожного з них. Цього буде достатньо? Або, наприклад, один з каналів може утримувати обертання плитки, як ви згадали. Крім того, створення редактора рівнів для роботи з цим форматом теж має бути досить тривіальним.

І найкраще, що вам навіть не потрібен спеціальний процесор контенту, оскільки ви можете просто завантажити його в XNA, як і будь-який інший Texture2D і прочитати пікселі назад у циклі.

IIRC він використовував чисту червону крапку на карті для сигналізації ворогу, і залежно від значення альфа-каналу для цього пікселя він визначав би тип ворога (наприклад, значення альфа 255 може бути битою, а 254 буде зомбі чи щось інше).

Іншою ідеєю було б поділити свої ігрові об’єкти на ті, які закріплені в сітці, і ті, які можуть "дрібно" переміщатися між плитками. Зберігайте нерухомі плитки в растровій карті, а динамічні об'єкти - у списку.

Можливо, навіть існує спосіб мультиплікації ще більше інформації в ці 4 канали. Якщо хтось має якусь ідею з цього приводу, дайте мені знати. :)


3

Ви, мабуть, могли втекти майже будь-чим. На жаль, сучасні комп’ютери настільки швидкі, що цей, мабуть, відносно невеликий об'єм даних насправді не зробить помітної різниці у часі, навіть якщо він зберігається у роздутому та погано побудованому форматі даних, який прагне до читання величезної кількості обробки.

Якщо ви хочете зробити «правильну» річ, ви робите двійковий формат, як запропонував Дракір, але якщо ви зробите інший вибір з якоїсь дурної причини, він, ймовірно, не повернеться і не вкусить вас (принаймні, не виконуючи ефективність).


1
Так, це точно залежить від розміру. У мене є карта, яка становить близько 161 000 плиток. Кожен з яких має 6 шарів. Спочатку я був у XML, але він був 82 Мб і завантажувався назавжди . Тепер я зберігаю його у двійковому форматі і становить близько 5 Мб перед стисненням і завантажується приблизно за 200 мс. :)
Річард Марскелл - Дракір

2

Дивіться це запитання: "Бінарний XML" для даних про ігри? Я б також вибрав JSON або YAML або навіть XML, але це дуже багато витрат. Що стосується SQLite, я ніколи не використовував би це для зберігання рівнів. Реляційна база даних зручна, якщо ви плануєте зберігати багато пов’язаних даних (наприклад, має реляційні посилання / зовнішні ключі), і якщо ви очікуєте задати велику кількість різних запитів, зберігання плиткових карт, схоже, не робить таких видів речі і додавали б зайвої складності.


2

Основна проблема цього питання полягає в тому, що воно поєднує два поняття, які не мають нічого спільного між собою:

  1. Парадигма зберігання файлів
  2. Представлення даних у пам'яті

Ви можете зберігати свої файли як звичайний текст у будь-якому довільному форматі, JSON, скрипт Lua, XML, довільний бінарний формат тощо. І нічого з цього не вимагатиме, щоб ваше представлення цих даних у пам'яті приймало будь-яку конкретну форму.

Завдання вашого коду завантаження рівня перетворити парадигму зберігання файлів у ваше представлення пам'яті. Наприклад, ви говорите: "Я бачу менше надмірності даних, використовуючи базу даних для зберігання даних плиток". Якщо ви хочете менше резервування, це має виглядати ваш код завантаження рівня. Це те, що ваше уявлення про пам'ять повинно бути в змозі впоратися.

Це НЕ то , що потреби формату файлу мати справу з. І ось чому: ці файли мають звідкись надходити.

Або ви збираєтеся писати це вручну, або ви будете використовувати якийсь інструмент, який створює та редагує дані рівня. Якщо ви пишете їх вручну, то найголовніше, що вам потрібно, це формат, який легко читати та змінювати. Надмірність даних не те , що ваш формат навіть потрібно враховувати, тому що ви будете витрачати більшу частину вашого часу редагування файлів. Ви дійсно повинні вручну використовувати всі механізми, які ви створили, щоб забезпечити надмірність даних? Хіба не було б краще використовувати свій час, щоб просто навантажувач вашого рівня впорався?

Якщо у вас є інструмент, який їх створює, то фактичний формат має значення (у кращому випадку) лише для зручності читання. Ви можете помістити в ці файли що завгодно . Якщо ви хочете опустити зайві дані у файлі, просто спроектуйте формат, який може це зробити, і змусьте ваш інструмент правильно використовувати цю здатність. Ваш формат картографічної мапи може включати стиснення RLE (кодування довжиною виконання), стиснення дублювання, будь-які методи стиснення, які ви хочете, тому що це ваш формат мапи плитки.

Проблема вирішена.

Реляційні бази даних (RDB) існують для вирішення проблеми виконання складних пошуків у великих наборах даних, що містять багато полів даних. Єдиний вид пошуку, який ви коли-небудь робите на карті плитки, - це "отримати плитку в положенні X, Y". Будь-який масив може впоратися з цим. Використовувати реляційну базу даних для зберігання та збереження даних мапи плиток було б надзвичайно надмірним, і насправді нічого не варто.

Навіть якщо ви вбудуєте певне стиснення в уявлення про пам'ять, ви все одно зможете легко перемогти RDB з точки зору продуктивності та пам’яті пам’яті. Так, вам доведеться реально реалізувати це стиснення. Але якщо розмір даних в пам'яті викликає таке занепокоєння, що ви б вважали RDB, то, ймовірно, ви хочете реально реалізувати конкретні види стиснення. Ви будете швидше, ніж RDB і одночасно знизите пам'ять.


1
  • XML: дійсно простий у використанні, але накладні витрати набридають, коли структура стає глибокою.
  • SQLite: зручніше для більших структур.

Для дійсно простих даних, я б, ймовірно, просто пішов XML-маршрут, якщо ви вже знайомі з завантаженням та розбором XML.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.