Битовый способ отображения тайловых карт

Автор оригинала: Sam Driver
  • Перевод


Техника автоматического выбора нужного тайла из тайловой карты.

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

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

Тайлы, учитывающие своих соседей


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



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

Однобитная карта


Представим, что какими-нибудь хитрыми техниками мы создали схему уровня платформера, состоящую только из каменных блоков и «воздуха» между ними. Уровень можно представить как однобитное изображение, в котором состояние каждого пикселя определяется единственным битом (1 — каменный блок, 0 — «воздух»). Вот увеличенный пример части такого уровня с добавленными линиями сетки:



Набор тайлов (тайлсет)


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



Оцениваем соседей


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



Значение для каждой точки находится исследованием её соседей и прибавлением значений для тех из них, в которых есть камень. Например, если у исследуемой точки сосед сверху тоже заполнен камнем, то ей присваивается значение 1. Если заполнены камнем соседи сверху и снизу, то точке присваивается значение 1 + 4, то есть 5.

Можно заметить, что присваиваемые значения направлений такие же, что и значения в позициях в двоичных числах, и это неудивительно: оба типа значений — это способы представления возможных комбинаций четырёх позиций, каждая из которых может быть во «включенном» или «выключенном» состояниях (камень или «воздух»).

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



Сложение тайлов


Представленный на изображении способ расположения тайлсета не случаен: он расположен таким образом, чтобы каждый тайл соответствовал тайлу карты, которому должно быть назначено значение. Присвоив значения всем точкам карты, мы просто ищем значение в тайлсете и размещаем в этой точке подходящий тайл:



Отлично!

Двигаемся дальше


Часть первая: избавляемся от «воздуха»


Приведённый выше пример хорошо работает для висящих в воздухе платформ, но на самом деле не полностью обрабатывает два типа тайлов.

Представим, что вместо платформера мы работаем над двухмерной стратегией с видом сверху, в которой есть два типа тайлов — трава и вода. В таком случае тайловое изображение будет присутствовать в каждой точке карты, в ней не будет пустых мест, как в платформере. Это значит, что для определения подходящего тайла у каждой точки карты должно быть значение, сгенерированное на основании её соседей.

Мы можем использовать точно такую же систему оценки соседей, как и прежде, но нам потребуется способ, позволяющий при изучении точки определить, что в ней находится — трава или вода. Это реализовать очень просто — достаточно только добавить ещё одно значение самой точке, по тому же шаблону «2 в степени n» из других значений:



Давайте решим, что в случае наличия воды мы будем прибавлять к значению точки, а при наличии травы — не будем. То есть тайл травы, окружённый со всех сторон травой, будет иметь значение 0. Тайл травы с водой сверху и справа, имеет значение 1 + 8 = 9. Тайл воды, окружённый со всех сторон травой, будет иметь значение 16. Тайл воды, окружённый со всех сторон водой, имеет значение 1 + 2 + 4 + 8 + 16 = 31

Часть вторая: добавляем вариативности


Как обеспечить обработку других типов рельефа?

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

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



В двоичной системе использовался шаблон «2 в степени n», в новой же будем применяться шаблон «3 в степени n».

В троичной системе каждое положение имеет три возможных состояния: трава, вода, лес, или 0, 1, 2. Когда в текущей точке находится трава, мы игнорируем значение (умножаем его на 0). Когда в точке находится вода, мы прибавляем заданное значение (умножаем его на 1). В случае леса мы прибавляем удвоенное значение (умножаем его на 2).

То есть в случае тайла леса сверху и справа от которого находится вода, снизу лес, а слева трава: 81 * 3 + 1 * 2 + 3 * 1 + 9 * 3 + 27 * 0 = 275

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

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

Комментарии 11

    0

    Многовато видов тайлов получается. Для 10 видов поверхностей понадобится 100 000 видов.
    Можно разбить тайл на 4 треугольника. Тогда понадобится только до 400 видов.

      0
      Действительно многовато.
      Я когда-то делал что-то подобное, но немного другим способом.
      У меня тайлы были немного больше сетки, учитывал соседей только однотипных тайлов, без комбинаций типа «вода-трава» и тому подобное. Просто я послойно отрисовывал каждый тип тайла: сначала вода, затем песок, трава, горы, деревья и т.д. Получался неплохой результат и количество заранее заготовленных тайлов не такое большое.
      0
      А что мешает просто делать выпуклые (в смысле с рельефом, немного выходящим за габариты тайла) тайлы и обрабатывать их по отдельности предварительно сортируя по заполненности. Т.е. сначала рисуем воду которая заменяет воздух, а потом камни с просветами. Чем заполнены просветы нам уже не принципиально. Лес реже травы и рисуется так же поверх неё. И не надо иметь 100500 тайлов.
      А вообще задача напоминает Wang Tile(не соображу сходу как обозвать понятнее)
        0
        Wiki (Ошибся веткой.)
          +1
          Покоцаный Marching Squares.
            +2
            Что бы не плодились лишние тайлы обычно используют «переходный тип земли». То есть что бы свести вместе «землю», «воду» и «камень» не рисуют переходные типы «земля-вода», «земля-камень» и «камень-вода», которые при добавлении какой-нибудь «пустыни» тут же должны быть дополнены целым рядом новых тайлов, а считают, что все эти типы стыкуются через некий промежуточный тип земли, и тогда достаточно иметь наборы «земля-переходный тип», «камень-переходный тип» и «вода-переходный тип», и так далее. Такой подход совершенно точно используется в WarCraft II или Heroes of Might & Magic 2

              0
              И В StarCraft (I). Там точно есть такие (дополнительные тайлы на возвышенности могут быть только в окружении базовой возвышенности).
                0
                и в аркануме
                0
                Если вы захотите иметь большое количество тайлов вам придётся очень много рисовать.
                Я когда то сделал смешивание по глубине. На каждый тип поверхности нужна одна карта цвета, и одна глубины. И вы смешиваете их кросс фейдом(cross fade). Вот как оно выглядело: twitter.com/OliverPDev/status/852152125463420928
                  0
                  Кстати это работает не только с тайлами. Подобную технику видел для смешивания переходных текстур в 3D. Выглядит весьма неплохо.
                  0
                  Да, именно почти так мы и делаем, когда генерим астероиды в Demolition Sheep :) Но у нас есть ещё один слой — слой добываемой породы, который делается более мелкими тайлами, которые строятся в точности по такому же алгоритму. В итоге получается красиво

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

                  Самое читаемое