Что такое хороший способ для хранения данных карты тайла?

13

Я разрабатываю 2D-платформер с друзьями из университета. Мы основали его на XNA Platformer Starter Kit, который использует файлы .txt для хранения карты тайлов. Хотя это очень просто, это не дает нам достаточного контроля и гибкости при проектировании уровней. Несколько примеров: для нескольких слоев контента требуется несколько файлов, каждый объект прикреплен к сетке, не допускает вращения объектов, ограниченного числа символов и т. Д. Поэтому я провожу некоторое исследование о том, как хранить данные уровня и файл карты.

Это касается только хранения файловой системы карт тайлов, а не структуры данных, которая будет использоваться игрой во время ее работы. Карта листов загружается в 2D-массив, поэтому вопрос заключается в том, из какого источника заполнить массив.

Обоснование БД: С моей точки зрения, я вижу меньшую избыточность данных при использовании базы данных для хранения данных тайлов. Плитки в одной и той же позиции x, y с одинаковыми характеристиками можно повторно использовать от уровня к уровню. Кажется, что было бы достаточно просто написать метод для извлечения всех плиток, которые используются на определенном уровне, из базы данных.

Причины для 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
@ Килотан: Но это не очень хорошо работает. Это невероятно сложно редактировать, потому что вы можете редактировать базу данных только с помощью команд 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 позволяет легко сохранять на одном изображении четыре разных измерения (слои, идентификаторы, вращение, оттенок и т. Д.).

Длинная история

Этот вопрос напомнил мне о том, когда Нотч в прямом эфире записал свою запись Ludum Dare несколько месяцев назад, и я хотел бы поделиться, если вы не знаете, что он сделал. Я думал, что это было действительно интересно.

В основном он использовал растровые изображения для хранения своих уровней. Каждый пиксель в растровом изображении соответствовал одной «плитке» в мире (на самом деле он не был плиткой, так как это была лучевая игра, но достаточно близкая). Пример одного из его уровней:

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

Поэтому, если вы используете RGBA (8 бит на канал), вы можете использовать каждый канал в качестве отдельного слоя и до 256 типов плиток для каждого из них. Будет ли этого достаточно? Или, например, один из каналов может содержать вращение для плитки, как вы упомянули. Кроме того, создание редактора уровней для работы с этим форматом должно быть довольно тривиальным.

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

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

Другой идеей было бы разделить ваши игровые объекты на те, которые зафиксированы в сетке, и те, которые могут «плавно» перемещаться между плитками. Сохраняйте фиксированные плитки в растровом изображении, а динамические объекты - в списке.

Может даже быть способ мультиплексировать еще больше информации в эти 4 канала. Если у кого-то есть идеи по этому поводу, дайте мне знать. :)

Дэвид Гувея
источник
3

Вы могли бы сойти с рук почти все. К сожалению, современные компьютеры настолько быстры, что эти предположительно относительно небольшие объемы данных не будут иметь заметной разницы во времени, даже если они хранятся в раздутом и плохо построенном формате данных, который требует огромного объема обработки для чтения.

Если вы хотите сделать «правильную» вещь, вы создаете двоичный формат, подобный предложенному Drackir, но если вы сделаете другой выбор по какой-то глупой причине, он, вероятно, не вернется и не укусит вас (по крайней мере, не в плане производительности).

AAAAAAAAAAAA
источник
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.

инженер
источник