Как сделать тайлы: пример из Fossil Hunters
Предлагаем вашему вниманию перевод статьи из блога Райана Миллера (Ryan Miller), технического директора небольшой канадской студии Reptoid Games. Райан рассказывает о том, каким путем была разработана система тайловой графики для Fossil Hunters.
В этой игре необходимо откапывать окаменелые части динозавров и самостоятельно собирать из них скелеты.
Попытка №1: Классика 16-4
Для начала мы попробовали очень классический метод, который я называю 16-4. Он используется давно и реализован в большинстве известных вам игр. Каждый тайл может иметь 1 из 16 возможных конфигураций, в зависимости от вида каждого из 4 соседних (север, восток, юг, запад).
16 конфигураций, 4 соседа = метод 16-4.
Это отличный вариант в случае 2d / пиксель арта, но мы пришли к мнению, что отсутствие поддержки внутреннего угла портит внешний вид. Углы, о которых идет речь, отмечены красным на рисунке ниже.
Мы могли бы создать дополнительные тайлы с внутренними углами, но это привело бы к экспоненциальному росту их общего количества. Если тайл определяется лишь 4 соседями, для его выбора необходимо предоставить 16 вариантов дизайна, а если тайл определяется 8 соседями (добавляются угловые), число вариантов возрастает до 81!
Давайте поищем другой вариант.
Попытка №2: Тайлы с границами
Чтобы избежать необходимости создания большого набора дизайнов, я остановился на методе, который называю «Тайлы с границами». Вы можете увидеть его реализацию в игре Spelunky. Здесь потребуется меньше ресурсов, чем для традиционного метода 16-4 (технически, достаточно только 1 тайла и 1 границы). Суть метода заключается в том, что сначала рисуют бесшовные тайлы, а их видимые края закрываются элементами-границами.
Дополнительным преимуществом этого метода является то, что он позволяет использовать тайлы разных размеров (не только 1×1), что делает тайлсет интереснее на вид и помогает бороться с монотонностью.
Мне особенно нравится эта статья, где метод объясняется более подробно.
Но я решил отказаться от него в итоге, потому что верхушки границ не соответствовали нашему художественному стилю: каждый тайл имел слишком пересеченную геометрию и обязательный «хребет» по краю.
Попытка №3: Воксельная сетка
Итак, результат второго метода выглядел слишком фрагментированным, поэтому я решил поискать вариант, в котором будет больше связей. Воксельная поверхность процедурно генерируется в соответствии с количеством «материи» под ней. Это решение кажется самым подходящим, потому что для процедурной генерации требуется создание весьма небольшого количества ресурсов, а поверхность получается аккуратной и ровной.
Но начав экспериментировать с вокселями, я выяснил для себя три вещи:
- У меня нет опыта в динамической генерации
- У меня есть опыт в разработке 3d-контента
- Я не могу точно контролировать внешний вид воксельной сетки.
Безусловно, воксели заслуживают внимания, и я знаю ребят, которые вытворяют невероятные вещи с процедурной генерацией сетки. Но в итоге мне пришлось отказаться от этого решения, так как оно не входило в число сильных сторон нашей команды.
Наконец-то успех!
Поиск баланса между красивой графикой и затратами на ее реализацию оказался более сложным, чем я предполагал. Не придется ли пожертвовать качеством графики, чтобы разработка продолжалась? И тут я внимательнее присмотрелся к методу 16-4.
Раз вся проблема в том, что для него требовалось много графических ресурсов, нет ли возможностей уменьшить их число?
Я мог бы убрать те тайлы, которые являлись повернутыми версиями других, и вместо этого прописать вращение в движке. Вместо 16 тайлов я получу 6. Не плохо, но думаю, что можно и лучше. Тем более, что внутренние углы опять остались без внимания.
Еще немного подумав, я обратил внимание, что на самом деле есть всего лишь три фигуры: углы, края и серединки. Смотрите сами.
На этих картинках заметно, что каждый тайл можно составить из 4 частей, в которых в различных комбинациях входят наши три фигуры.
Что, если вместо цельных тайлов мы будем использовать 4 части, соединенные вместе при помощи кода? И к ним теперь можно добавить внутренний угол. Думаю, мы нашли решение.
Наш победитель – метод 8-4-4
Вместо того, чтобы создавать все возможные варианты цельных тайлов (81 штуку, поскольку нам нужны внутренние углы), мы можем создать 4 сабтайла размером поменьше и составить из них нужные конфигурации.
Я уже упоминал, что сам придумываю все названия? В имени метода 8-4-4 упомянуты компоненты, используемые этой системой:
- Проверка 8 соседей (С, СВ, В, ЮВ, Ю, ЮЗ, З, СЗ)
- 4 графических ресурса (Середина, Внешний угол, Внутренний угол, Край)
- 4 сабтайла (СВ, ЮВ, ЮЗ, СЗ)
Конфигурация тайла в соответствии с таковыми у его ближайших соседей в этой системе изменяется простым набором команд:
- Для каждого из сабтайлов:
a. Проверить 3 соседних (например, для СВ проверить С, СЗ и З)
b. Подставить сабтайл с сеткой типа Середина, Внешний угол, Внутренний угол или Край
c. Правильно повернуть
Чтобы не было видно швов, края тайловых сеток необходимо точно выровнять. Это проще сделать при моделировании геометрии, а не при UV-развертке. Что подводит нас к следующему пункту…
Отображение текстур
UV-развертка не представляет сложности, но как только мы начинаем создавать карты текстур, становится ясно, что со швами придется помучиться.
Края сабтайлов надо выровнять друг к другу с самых разных позиций, или швов не избежать. Задача разрешимая, надо лишь потратить больше времени на текстурирование. Но графика станет беднее (см. ниже).
Ужасно не выглядит, но когда много одинаковых тайлов оказываются рядом, повторение заметно очень сильно. Я немного экспериментировал с заменой крупных внутренних кусков. Область 2×2 с сеткой типа Середина заменялась одиночным тайлом. Но этот метод оказался неудобным в плане создания ресурсов и ухудшал производительность.
На помощь приходят шейдеры
Вместо того, чтобы отображать текстуры на неразвернутые UV-шки сеток, я использовал трипланарную проекцию. Этот метод традиционно используется для отображения текстур на местности и для динамически генерируемых сеток, которые не ложатся на оригинальные UV-координаты.
В трипланарной проекции положение сторон многоугольника сравнивается с направлениями трехмерных координат X, Y, Z и в соответствии с ними выравнивается по координатам UV. Метод отлично подходит для полигонов с гранями, перпендикулярными X, Y или Z (например, для тайлов!).
На ShaderForge есть доступное руководство по этой теме, а еще вы можете скачать сделанное мною.
Как сомкнуть соседние поверхности
Как только в игре появились различные типы тайлов, обнаружилось, что между образованными ими поверхностями есть зазор. Технически это правильно, ведь при тайлинге разные наборы не учитывают поведение друг друга. Но мне хотелось, чтобы поверхности смыкались и перекрывались.
Я рассмотрел еще один набор угловых сеток, которые отвечали бы за переход к другим типам поверхности, но потом понял, что сабтайлы можно просто растянуть поверх соседей. Еще один плюс этого метода!
Инструменты редактирования
Какой геймер не любит хорошие редакторы уровней? Мы хотели, чтобы каждый тайл поверхности можно было нарисовать мышью прямо в игровом кадре.
Первым шагом к этому служит внутрикадровый перехват кликов. Из OnSceneGUI мы добываем класс Event.current и проверяем его значение – MouseClick или MouseDrag. Далее при помощи функции Raycast опускаем луч на воображаемую поверхность Plane и так получаем координаты виртуального пространства, где произошел клик. Теперь мы знаем, куда поместить тайл.
Осталось надо создать интерфейс, в котором разместится палитра для выбора тайлов. Я добавил в него еще немного других утилит.
Вот и все! Или не все?
Я закругляюсь. Данное решение идеально подошло нам, но это не значит, что плохи остальные. Вам необходимо самостоятельно найти, что больше подходит для вашей игры, в визуальном или техническом плане, и самостоятельно доводить решение до требуемого качества.
Спасибо за перевод :)
Интересная техника тайлинга использовалась в Don’t Starve. Там много разных тайлов, но я уверен, что “стыки” никто отдельно не рисовал. Есть идеи как такое реализовано?:) https://uploads.disquscdn.com/images/328a582b0106c458b3e99ef3ccd7c95f88341a335be99b0f9b04af60eb6eac72.jpg
Макс, а почему ты думаешь что это не отдельно-нарисованный тайл? или “собранный из разных кусков” тайл, куски отдельно прорисованы
Вариант с отдельно прорисованным не подходит, т.к. очень много разных тайлов и все они могут стыковаться друг с другом. Если прорисовывать каждый такой вариант, то это колоссальный объём работы.
Вот собранный тайл из разных сабтайлов, наверное, ближе к истине. И получается, что ближе всего к этому метод 16-4, который описан в статье. Интересно, что сабтайлы зависят от соседей тайлов. И опять приходим к большому объему работы :(
Вот картинка. По границе биомов, там где угловые тайлы, много разных вариаций, неужто их все прорисовывали отдельно?). https://uploads.disquscdn.com/images/1df155f44448bb2aa5516ff07f99948f0015ce2549c97af93dbd194ca29f7e3a.png