Весь дизайн карты против дизайна массива плиток

11

Я работаю над 2D RPG, которая будет включать обычные карты подземелий и городов (предварительно сгенерированные).

Я использую плитки, которые я затем объединю, чтобы сделать карты. Мой первоначальный план состоял в том, чтобы собрать плитки с помощью Photoshop или другой графической программы, чтобы получить одну большую картинку, которую я мог бы затем использовать в качестве карты.

Тем не менее, я читал в нескольких местах, где люди говорят о том, как они использовали массивы для построения своей карты в движке (так что вы предоставляете массив из x плиток вашему движку, и он собирает их в виде карты). Я могу понять, как это делается, но кажется, что это намного сложнее реализовать, и я не вижу очевидных преимуществ.

Какой метод наиболее распространен, и каковы преимущества / недостатки каждого из них?

Cristol.GdM
источник
1
Основная проблема - память. Текстура, которая заполняет экран 1080p за один раз, составляет около 60 МБ. Таким образом, изображение использует целое число на пиксель, а мозаичное - на целое (пиксель / (мозаичный размер * мозаичный размер)). Таким образом, если плитки имеют размер 32,32, вы можете представить карту в 1024 раза больше, используя плитки против пикселя. Кроме того, время смены текстур будет вредно, так как тратит столько памяти и т. Д.
ClassicThunder

Ответы:

19

Прежде всего, позвольте мне сказать, что 2D RPG близки и дороги моему сердцу и работают со старыми движками DX7 VB6 MORPG (не смейтесь, это было 8 лет назад, сейчас :-)) - это то, что впервые заинтересовало меня в разработке игр , Совсем недавно я начал конвертировать игру, над которой работал на одном из этих движков, для использования XNA.

Тем не менее, я рекомендую использовать для вашей карты структуру на основе плиток с наложением слоев. С любым графическим API, который вы используете, у вас будет ограничение на размер текстур, которые вы можете загрузить. Не говоря уже об ограничениях памяти текстуры видеокарты. Итак, учитывая это, если вы хотите максимизировать размер ваших карт, не только минимизируя количество и размер текстур, которые вы загружаете в память, но и уменьшая размер ваших ресурсов на жестком диске пользователя И время загрузки, вы определенно захочется пойти с плитками.

Что касается реализации, я подробно рассказал о том, как я справился с этим, по нескольким вопросам здесь, в GameDev.SE и в моем блоге (оба ссылки ниже), и это не совсем то, что вы спрашиваете, поэтому я просто перейти к основам здесь. Я также отмечу особенности плиток, которые делают их полезными при загрузке нескольких больших предварительно отрендеренных изображений. Если что-то не понятно, дайте мне знать.

  1. Первое, что вам нужно сделать, это создать лист плиток. Это просто большое изображение, которое содержит все ваши плитки, выровненные в сетке. Это (и, возможно, дополнительный в зависимости от количества плиток) будет единственное, что вам нужно загрузить. Всего 1 изображение! Вы можете загрузить по 1 на карту или по одному на каждую клетку в игре; какая организация работает для вас.
  2. Далее вам нужно понять, как вы можете взять этот «лист» и перевести каждую плитку в число. Это довольно просто с простой математикой. Обратите внимание, что деление здесь является целочисленным делением , поэтому десятичные разряды отбрасываются (или округляются вниз, если вы предпочитаете). Как преобразовать ячейки в координаты и обратно.
  3. Хорошо, теперь, когда вы разбили лист плиток на ряд ячеек (чисел), вы можете взять эти числа и вставить их в любой контейнер, который вам нравится. Для простоты вы можете просто использовать 2D-массив.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. Далее вы хотите нарисовать их. Один из способов сделать этот LOT более эффективным (в зависимости от размера карты) состоит в том, чтобы вычислять только те ячейки, которые в данный момент просматривает камера, и проходить через них. Вы можете сделать это, выбрав координаты массива мозаики карты в верхнем левом ( tl) и нижнем правом ( br) углах камеры . Затем выполните цикл от tl.X to br.Xи, во вложенном цикле, от, tl.Y to br.Yчтобы нарисовать их. Пример кода ниже:

    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.

Мои ресурсы Tile-Engine
Примечание: все они нацелены на XNA, но это в значительной степени относится ко всему - вам просто нужно изменить вызовы отрисовки.

  • Мой ответ на этот вопрос обрисовывает в общих чертах, как я обращаюсь с ячейками карты и наслоением в моей игре. (Смотрите третью ссылку.)
  • Мой ответ на этот вопрос объясняет, как я храню данные в двоичном формате.
  • Это первое (ну, технически второе, но первое «техническое») сообщение в блоге о разработке игры, над которой я работал. Вся серия содержит информацию о таких вещах, как пиксельные шейдеры, освещение, поведение листов, движение и все эти забавные вещи. Я фактически обновил пост, добавив в него содержание моего ответа по первой ссылке, которую я разместил выше, поэтому вы можете вместо этого прочитать ее (возможно, я что-то добавил). Если у вас есть какие-либо вопросы, вы можете оставить комментарий там или здесь, и я буду рад помочь.

Другие Ресурсы Плитки

  • Учебник по движку плиток с этого сайта дал мне основу, которую я использовал для создания своих карт.
  • На самом деле я еще не смотрел эти видеоуроки, потому что у меня не было времени, но они, вероятно, полезны. :) Они могут быть устаревшими, если вы используете XNA.
  • На этом сайте есть еще несколько учебных пособий, которые (я думаю) основаны на приведенных выше видео. Возможно, стоит проверить.
Ричард Марскелл - Дракир
источник
Вау, это вдвое больше информации, чем я просил, и в значительной степени отвечает на все вопросы, которые я не задавал, отлично, спасибо :)
Cristol.GdM
@Mikalichov - Рад, что смог помочь! :)
Ричард Марскелл - Дракир
Обычно с двумерным массивом вы хотите использовать первое измерение в качестве Y и следующее в качестве X. В памяти массив будет последовательно 0,0-i, а затем 1,0-i. Если вы делаете вложенные циклы сначала с помощью X, то вы фактически перепрыгиваете назад и вперед в памяти, а не по последовательному пути чтения. Всегда для (у), затем для (х).
Бретт W
@BrettW - действительная точка. Спасибо. Меня удивило, как двумерные массивы хранятся в .Net (порядок рядов. Сегодня узнал что-то новое! :-)). Однако после обновления моего кода я понял, что он точно такой же, как вы описываете, только с переключенными переменными. Разница в том, в каком порядке тянутся плитки. Мой текущий пример кода рисует сверху вниз, слева направо, тогда как то, что вы описываете, рисует слева направо, сверху вниз. Итак, ради простоты я решил вернуть его обратно к оригиналу. :-)
Ричард Марскелл - Дракир
6

Для представления мира могут использоваться как системы на основе плиток, так и статическая модель / система текстур, и каждая из них имеет свои сильные стороны. Является ли один лучше другого сводится к тому, как вы используете кусочки, и что лучше всего подходит для ваших навыков и потребностей.

При этом обе системы могут использоваться как по отдельности, так и вместе.

Стандартная система на основе плитки имеет следующие преимущества:

  • Простой 2D массив плиток
  • Каждая плитка имеет один материал
    • Этот материал может быть одной текстурой, множеством или смешанным с окружающими плитками
    • Может повторно использовать текстуры для всех плиток этого типа, уменьшая создание активов и переработку
  • Каждая плитка может быть проходимой или нет (обнаружение столкновения)
  • Легко визуализировать и идентифицировать части карты
    • Положение символа и положение на карте являются абсолютными координатами
    • Простой цикл по соответствующим плиткам для области экрана

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

Основная причина, по которой большинство людей делают плитки, заключается в том, что они позволяют им создавать небольшие активы и повторно использовать их. Это позволяет создать гораздо более крупную картинку меньшими кусочками. Это уменьшает количество переделок, потому что вы не меняете всю карту мира, чтобы внести небольшие изменения. Например, если вы хотите изменить оттенок всей травы. В большом изображении вам придется перекрасить всю траву. В системе плиток вы просто обновляете плитку (и) травы.

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

Вам нужно будет предоставить больше подробностей о вашей среде / движке / языке, чтобы предоставить примеры кода.

Бретт W
источник
Спасибо! Я работаю под C # и собираюсь использовать аналогичную (но менее талантливую) версию Final Fantasy 6 (или, в основном, большинства RPG-игр Square SNES), поэтому плитки для пола И строительные плитки (то есть дома). Больше всего меня беспокоит то, что я не вижу, как построить 2D-массив, не тратя часы на проверку «есть угол травы, затем три прямые горизонтали, затем угол дома, затем…», с множеством возможных ошибок вдоль путь.
Cristol.GdM
Похоже, Ричард рассмотрел вас с точки зрения специфики кода. Я лично использовал бы перечисление типов тайлов и использовал битовые флаги для определения значения тайла. Это позволяет хранить более 32 значений в среднем целом числе. Вы также сможете хранить несколько флагов на плитку. Таким образом, вы можете сказать Grass = true, Wall = true, Collision = true и т. Д. Без отдельных переменных. Затем вы просто назначаете «темы», которые определяют лист листов для графики для этой конкретной области карты.
Бретт 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))

Для больших карт одно огромное растровое изображение для всей карты может занимать много места в памяти и может быть больше, чем то, что поддерживает видеокарта для одного размера изображения. Но у вас не возникнет никаких проблем с плитками, потому что вы выделяете графическую память только один раз для каждой плитки (или оптимально, если все они находятся на одном листе)

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

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

Предположим, вам нужно проверить наличие столкновений с камнями / деревьями и т. Д. Вы можете получить плитку в позиции (позиция персонажа + размер спрайта персонажа + текущее направление) и проверить, помечен ли он как пройденный или не пройденный.

Джимми
источник