Небольшая заметка про внедрение Авто-Тайлинга для своего движка на практике

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

Сразу напомню что у меня есть Дискорд сервер если кому интересно следить за обновлениями проекта более часто, пока что он чисто технический:

Начнем с самого простого – алгоритма авто-заполнения. Идея как правило предельно простая – у нас есть какая-то клетка и ее соседи по бокам. Обычно это называется шаблоном и эти данные описываются массивом 3x3 или больше. Если у соседей может быть два варианта значений (например "земля" и "вода", или "земля" и "пустота"), то всего таких разных шаблонов может быть 256 (значение центральной клетки мы выбрать не можем, остается 8 боковых клеток с одним из 2х возможных значений, т.е. всего 2^8 = 256). Но учитывая повторения и другие ограничения, на практике вариантов шаблонов сильно меньше.

Разные варианты соседей 3x3. Серые цвета это прозрачный фон в Aseprite.
Разные варианты соседей 3x3. Серые цвета это прозрачный фон в Aseprite.

Далее, для каждого из таких шаблонов у должен быть заранее заготовлен тайл с правильным вариантом (или несколько тайлов которые будут выбираться случайным образом). Например самый первый тайл на иллюстрации соответствует ситуации: [(0, 0, 0), (0, 1, 0), (0, 1, 0)]. И нам нужно просто для такого шаблона этот тайл найти.

Небольшая заметка про внедрение Авто-Тайлинга для своего движка на практике

Решать такую задачу можно по-разному. В простом случае с двумя вариантами значений для соседей можно записать 8 бит в одно i8 число (по одному бита на каждого соседа) и использовать его в качестве индекса для массива или ключа ассоциативного массива, где каждое значение ведет в нужный нам тайл (по указателю, хендлу или что у вас там в движке). Для примера выше ключ будет иметь значение 0b 000 00 010 (центральный бит из 3x3 шаблона мы, напомню, выкидываем) что соответствует числу/индексу 2 где будет храниться тайл. Но это самый простой вариант с бинарными соседями (т.е. масками). В других же случаях с более хитрыми правилами как правило нужно использовать полный перебор. Например, популярная система правил может использовать 3 разных варианта числовых значения: 1) ноль – что угодно 2) положительное число – соответствие значению, например "земля" 3) отрицательное число – что угодно кроме значения, т.е. не земля. В таком случае шаблон можно называть правилом и часто для ее решения нужно уже делать полный перебор правил с поиском максимального совпадения, что на самом деле тоже очень быстро и просто. (Можно конечно упороться и сделать какой нибудь оптимальный поиск через обратный индекс, но на практике это никому не нужно пока у вас не будет тысяч правил до и можно потом еще разбить правила на группы чтобы уменьшить перебор). Например шаблон [(0, -2, 0) (-2, 2, -2), (0, -2, 0)] может выглядеть так (здесь число 2 описывает стену):

Пример правила [(0, -2, 0) (-2, 2, -2), (0, -2, 0)]
Пример правила [(0, -2, 0) (-2, 2, -2), (0, -2, 0)]

Как видите сама по себе технически задача для авто-тайлинга предельно простая. На практике же, когда у вас реальный проект, то самая трудоемкая часть это написать такой инструмент для создания заготовок из атласов и правил сопоставления которых может быть много (и руками писать эти правила в коде не совсем удобно). На вход инструмент будет принимать атлас, а на выходе должен быть набор правил где каждое правило будет соответствовать одному тайлу из атласа. Тайлы при этом описываются прямоугольниками (min, max) в координатах внутри атласа, либо индексами этих прямоугольников. Зависит от вашего движка.

Пример атласа.
Пример атласа.

Собственно, как можно было догадаться по приведенным иллюстрациям, такой инструмент уже есть – LDTK. В первую очередь, LDTK – это редактор карт с открытым форматом, но в нем есть авто-тайлер который отдает наружу сгенерированные правила. Формат правил не задокументирован, но реверсится довольно просто. Пример того как можно сделать правила для атласа выше (есть и более сложный вариант UI где правила можно настроить тоньше):

А вот пример того как эти правила выглядят в сохраненном файле проекта LDTK:

{ "uid": 951, "size": 3, "tileIds": [140], "pattern": [0,0,0,-3,3,0,0,0,0], "flipX": false, "flipY": false, "outOfBoundsValue": 3, }

Здесь из важного: sizeразмер шаблона, tileIds – указатели на тайлы (можно например для разнообразия сделать несколько вариантов травы), pattern – шаблон, flipX/flipY флаги с помощью которых можно сказать движку отразить шаблон если нужно сделать симметричное правило (не забудьте также отразить сам тайл), outOfBoundsValue – если шаблон выходит за границы карты то значения клеток берутся из этого поля.

Код для поиска соотвествия крайне простой:

В данном коде <b>AutoTilePattern</b> это просто массив байтов: <b>type AutoTilePattern = [i8; 9];</b>
В данном коде AutoTilePattern это просто массив байтов: type AutoTilePattern = [i8; 9];

В результате получаем возможность использования правил из LDTK в своей игре без превапрительного "запекания".

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

Вот собственно и все. Никаких ручных хард-кодов правил или мучения с текстовыми файлами конфигов. Вся реализация – 100 строк от силы.

Видео по теме:

  • https://www.youtube.com/watch?v=mQRokJfkLY4
  • https://www.youtube.com/watch?v=nfjAznD_MaU
  • https://youtu.be/V7PzNrtIWUg
5454
8 комментариев

Это для геймдева)
Можешь туда перенести)

1
Автор

А как перенести?)

Комментарий недоступен

1
Автор

Свой на основе ECS из Bevy: https://bevyengine.org/