что такое тайлы в майнкрафт
Приветствую вас, дорогие любители бесконечных слотов на серверах MineCraft!
В нем вы найдете много интересной информации..
. о том, почему не рекомендуется делать больше 100 слотов.
. о том, почему сервера лагают.
В этой статье я вкратце опишу вам, как работают наши сервера и откуда берутся ЛААААГИ!
А так же почему администрация не ставит на сервера кучу очень полезных модов и плагинов.
Казалось бы, все нормально и тут нет ничего преступного, все сделано довольно хорошо и добавить нечего. Проблема тут такая: всё это обрабатывается в одном основном потоке. В последних версиях в Mojang почитали немного про многопоточные штуки и научились сохранять чанки на диск в отдельном потоке. Безусловно это прорыв, потому что это было чертовски узкое место, давным-давно сервер сохранялся по 15 минут и на это время полностью вис, теперь такого нет. Тем не менее, проблема не решена.
При этом есть ещё и Bukkit!
Как не нужно писать большие сервера
Те, кто мог видеть мою прошлую статью (а она довольно related к данной теме), знают, что вот уже больше полутора лет я разрабатываю собственную реализацию сервера Minecraft, рассчитанную, в первую очередь, на высокие нагрузки. Тем не менее, в своей работе мы используем так же и стандартный сервер (Bukkit) для нескольких мини-серверов, просто чтобы было разнообразие. И вот, столкнувшись с очередной версией сервера, которая стала раз в 5 хуже предыдущих, я уже не выдержала, и решила написать эту статью.
Статья больше похожа на рассказ, чем на обучающий материал, так что вряд ли вы почерпнете из неё полезных навыков кодинга, но, надеюсь, кому-то она покажется интересной или даже полезной. Но если вы ходите увидеть кучу кода и примеров, то не открывайте статью, она не об этом. Об этом, надеюсь, будет следующая статья.
Вам не нужно знать ничего о майнкрафте и особенно о его сервере, в данной статье я хочу просто рассказать, как работает оригинальный сервер Minecraft, а так же его «обвязка» — Bukkit, рассказать, почему такая система не работает и не должна. Я не претендую на идеальные знания о разработке серверов и не утверждаю, что мой сервер написан правильно и лучше всех. Я просто делюсь своим опытом, основанным на двух годах работы с сервером от всем известной Mojang и на полутора годах разработки своего сервера. Вся представленная здесь информация является моим личным мнением, а статья предназначена для расширения кругозора или даже обучения и может быть интересна как новичкам, так и продвинутым профессионалам.
Вы спросите, в чем же тут проблема? Так многие делают: основная логика приложения в одном потоке, это очень удобно программировать, не нужно заботиться о синхронизации и прочих проблемах параллельных приложений. Проблема тут в том, что если на сервере больше 40 человек, вместо стандартных 20 циклов он делает уже 15, если 70 человек, то 10, если 100 — то проседает до невероятных значений. Это при том, что у меня вообще-то мощный 6-ядерный Core i7 и 64Gb оперативной памяти! И куда мне теперь деть эти ресурсы, если из 12 потоков заняты от силы два?
Не буду пустословить, приведу пример:
На сервере 223 игрока, при этом радиус видимости выбран достаточно маленький, в памяти находится 46577 чанков, 524 «срочных» блока, 87 блоков редстоуна и поршней, 11240 Entity предметов, 4274 Entity животных, 19 тележек и лодок, 717 других Entity, игроки, которые тоже являются Entity и требуют соответствующей обработки.
Количество тайлов и обновлений света мой сервер не выводит в информации (мне это не нужно), но можете поверить, их много.
Одна лишь обработка животных ужасно тяжелый процесс — они регулярно выполняют поиск пути, поиск других Entity вокруг, у них есть AI (в последних версиях довольно продвинутый), поэтому обработать 4 тысячи животных — уже большая работа.
Обойти 3 миллиона блоков (примерно столько обрабатывается случайных блоков при таком количестве чанков) — тоже не тривиальная задача.
11 тысяч предметов надо сдвинуть, сделать некоторые другие действия, отправить обновления об их положении игрокам и прочее.
И всё это нужно успеть сделать за 50 миллисекунд, иначе все начнет тормозить, ведь скорость рассчитывается в циклах. Если сервер делает меньше циклов в секунду, чем должен, то, например, мобы начинают ходить медленно и рывками. Плюс расчётов в циклах очевиден — если сервер подвиснет или произойдёт огромная сборка мусора (сервер ведь на Java), то не получится так, что тележка, едущая на полной скорости на следующем цикле превратится в быстро движущийся маленький объект, и придётся рассчитывать её движение по более сложным алгоритмам.
При этом есть ещё и Bukkit!
Bukkit — это такой «враппер» для ванильного сервера. Он добавляет API для создания плагинов, он супер-удобен для разработчиков плагинов и сделан действительно качественно. Но, грубо говоря, всё становится только хуже. Если игрок присылает пакет, что он немного сдвинулся или повернул голову… создаётся событие и посылается по всем плагинам, которые его обрабатывают. А при этом функция обработки движения и так довольно сложная. При ломании блока или установке происходит то же самое, а так же при около сотни других действий, которые создаёт игрок или сам сервер, включая махание рукой, смена состояния редстоуна, перетекание воды, спавн моба, AI, тысячи их… То есть, как бы система хорошая, но создаёт кучу дополнительных вызовов при обработке всего.
К счастью, некоторые разработчики плагинов научились вытаскивать тяжелую логику своих плагинов в отдельный поток. Яркими и хорошими примерами служат плагины OreObfuscator и Dynmap. Первый «чистит» посылаемые игроку блоки от лишних данных, чтобы игрок не мог читами смотреть сквозь стены. Он делает это в отдельном потоке, складывая пакеты в очередь и обрабатывая их отдельно от логики сервера. Второй генерирует динамическую карту для браузера, тоже очень качественно сделан. В общем, хвала им, что не нагружают основной поток ещё сильнее.
Так же есть плагин, который уменьшает количество вещей, которые обрабатывает сервер за цикл. Объединяет лежащие рядом предметы, выгружает мобов, ограничивает обработку чанков. Это очень круто, ни один сервер не обходится без этого плагина — NoLagg.
Как делать правильно (по-моему)
Мы долго мучились со всем этим, когда полтора года назад наш онлайн вырос до 100 человек, а скорость работы просела до 0.5-1 цикла в секунду. Мы пытались делать оптимизации сервера, правили код, пытались убрать как можно больше лишнего, изменили в некоторых местах работу не по циклам а по секундам (например, в печи. В Bukkit это потом тоже добавили… через несколько месяцев). В конце концов мы достигли ужасной нестабильности сервера и решили плюнуть на всё это.
Единственным вариантом, который сможет обеспечить нам комфортный онлайн такого количества игроков, какой мы хотели, был масштабируемый сервер. Не думаю, что нужно объяснять, что поток у процесса сущность не масштабируемая, может работать только на одном ядре процессора одновременно и его производительность ограничена производительностью ядра. Ядра в процессорах сейчас довольно производительные, но и работы много, да и процессоры сейчас делают многоядерными, не то время, чтобы не делать что-то многопоточным.
Разделить уже существующий сервер на несколько потоков не представляется возможным. Многопоточное программирование — штука тонкая, сложная, требующая большого знания кода, с которым работаешь, и в существующее приложение практически не встраиваемая. Код надо писать с нуля.
Так родился сервер, в основе которого было заложено как можно больше потоков: мир делится на куски по 64х64 чанка и каждый такой кусок обрабатывает чанки в одном потоке, один поток для обработки срочных блоков, один поток для редстоуна и поршней, один поток для мобов, один поток для предметов, один поток для тележек, один поток обрабатывает другие Entity и прочую информацию о мире, один поток пересчитывает свет, четыре потока по разным частям света сохраняют мир на диск, один поток рендерит карту, один поток занимается обслуживанием сервера и команд консоли, обновляет статистику. Для игроков используется система, которая позволяет обработку пакетов ставить либо на отдельный поток для каждого игрока, либо на пул потоков, либо на отдельные потоки для каждого игрока. При этом всё можно разделить на ещё несколько потоков: обрабатывать один и тот же тип объектов хоть в 20 разных потоках. А так же Netty (NIO) в качестве сетевого движка, в отличии от стандартного I/O.
Разработка стабильной версии такого сервера, не обладающего всем функционалом, стоило примерно 8 месяцев работы одной меня без обладания опытом. Весь код рассчитан на асинхронный доступ ко всем данным. Но оно того стоило — совсем недавно мы поставили рекорд в 559 человек, которые не просто стояли в одном месте, лагали и снимались на фрапс, а проходили очень большой ивент с редстоуном, и при этом чувствовали себя комфортно.
Мораль сей басни такова: если вы рассчитываете на то, что ваш проект будет хоть сколько-нибудь популярным и думаете, что хоть чуть-чуть теоретически возможно, что на сервере будет хоть сколько-нибудь много человек… не поскупитесь на создание масштабируемой архитектуры.
Жду ваши гнилые помидоры, предложения по улучшению этой статьи, а так же предложения по тому, что бы вы хотели увидеть в следующей статье, которая когда-нибудь будет.
Поток мыслей может содержать орфографические ошибки, т.к. писалось на одном дыхании.
Tile Entitie#
Гайд перевел и дополнил пользователь форума — AustereTony.
В данной статье я предоставляю свой перевод гайда с сайта shadowfacts по работе с TileEntity. Исходники примера доступны тут.
Существует распространённый миф что TileEntity это плохо, особенно для производительности. Это не так. Они могут негативно влиять на производительность если они реализованы не умело, как в прочем и любые другие объекты.
Вспомогательные средства#
Прежде чем мы создадим тайл, мы добавим класс, который упростит их создание в будущем.
В первую очередь создадим класс BlockTileEntity :
Теперь когда у нас есть удобная основа самое время создать блок.
Наш блок расширяет BlockTileEntity и содержит обобщение TileEntityCounter (который предстоит создать), так как этот тайл принадлежит этому блоку.
В конструкторе мы просто напросто вызываем суперконструктор и передаём туда все параметры, которые будем указывать при создании блока.
В методе getTileEntityClass() вернём TileEntityCounter.class (мы ещё создадим его). Это позволит зарегистрировать его ассоциировав с именем блока.
Разделение: клиент и сервер#
Как было подмечено выше, перед тем как работать со счётчиком тайла, мы должны удостовериться, что действия происходят на сервере. Делаем мы это потому как в Майнкрафте клиент и сервер полностью разделены и некоторые методы вызываются для обеих сторон.
В многопользовательской игре множество клиентов подключены к одному серверу. В этом случае разделение между сторонами очевидно, но в одиночной игре всё немного сложнее. В многопользовательской игре сервер, к которому происходит подключение, отражает физический сервер и все отдельные подключенные клиенты являются физическими клиентами.
В одиночной игре клиент и сервер тоже разделены даже несмотря на то, что они исполняются на одном компьютере (на одной JVM, но в разных потоках). В одиночной игре клиент подключается к локальному, приватному серверу, функции которого схожи с физическим сервером. В этом случае серверный поток отражает логический сервер, а клиентский поток отражает логический клиент, так как обе логические стороны выполняются на одной физической стороне.
Поле World.isRemote используется для проверки стороны, на которой происходит выполнение (будь она логической или физической). Оно равно true для физического клиента в многопользовательской игре и для логического клиента в одиночной игре. Это поле равно false для физического сервера в многопользовательской игре и для логического сервера в одиночной.
Создание тайла для блока#
Теперь когда у нас есть блок, мы должны создать тайл для него.
Создадим класс TileEntityCounter :
NBT формат#
Данный формат используется для хранения данных в виде пар ключ-значение, которые легко сериализуется в байты и сохраняется на диск. Вы можете ознакомиться с классом NBTTagCompound для представления о типах данных, которые он может хранить. Ванильный код содержит множество хороших примеров по сохранению и чтению сложных структур данных.
Регистрация#
Тайл нашего блока нужно зарегистрировать. Делать мы это будем там же, где регистрируем наши блоки.
Регистрацию будем производить через вызов GameRegistry.registerTileEntity() и передавать в него класс тайла и его регистрационное имя. Это требуется для того, что бы при загрузке Майнкрафт определял какой TileEntity к какому блоку принадлежит и пересоздавал его загружая из NBT.
Финал#
Нажав ПКМ по нижней стороне несколько раз вы увидите это:
Клик ПКМ по другим сторонам выведет в чат сообщение с текущим значением счётчика.
Tile Entity#
Тайлы бывают двух типов: обновляющиеся и не обновляющиеся. Не обновляющиеся тайлы просто хранят какую-то информацию и практически не нагружают игровую систему. А вот обновляющиеся тайлы обновляются каждый игровой тик (обычно 20 раз в секунду). Последние влияют на производительность сильнее и требуют очень аккуратной реализации.
В конструкторе мы просто вызываем конструктор суперкласса super(Properties.create(Material.ROCK)) и передаём туда все пропертисы.
В методе hasTileEntity мы говорим игре, что блок имеет тайл. В createTileEntity мы возвращаем новый экземпляр тайла.
Разделение: клиент и сервер#
Как было подмечено выше, перед тем как работать со счётчиком тайла, мы должны удостовериться, что действия происходят на сервере. Делаем мы это потому, что в Майнкрафте клиент и сервер полностью разделены и некоторые методы вызываются для обеих сторон.
В многопользовательской игре множество клиентов подключены к одному серверу. В этом случае разделение между сторонами очевидно. Сервер, к которому происходит подключение, отражает физический сервер и все отдельные подключенные клиенты являются физическими клиентами. Но в одиночной игре всё немного сложнее.
В одиночной игре клиент и сервер тоже разделены даже несмотря на то, что они исполняются на одном компьютере (на одной JVM, но в разных потоках). В одиночной игре клиент подключается к локальному, приватному серверу, функции которого схожи с физическим сервером. В этом случае серверный поток отражает логический сервер, а клиентский поток отражает логический клиент, так как обе логические стороны выполняются на одной физической стороне.
Поле World.isRemote используется для проверки стороны, на которой происходит выполнение (будь она логической или физической). Оно равно true для физического клиента в многопользовательской игре и для логического клиента в одиночной игре. Это поле равно false для физического сервера в многопользовательской игре и для логического сервера в одиночной.
Создание тайла#
Теперь когда у нас есть блок, мы должны создать тайл для него. Но, для начала, я советую сделать класс-обертку над тайлом, в которой будут прописаны методы синхронизации:
Создадим класс CounterTile :
NBT формат#
Данный формат используется для хранения данных в виде пар ключ-значение, которые легко сериализуются в байты и сохраняются на диск. Вы можете ознакомиться с классом CompoundNBT для представления о типах данных, которые он может хранить. Ванильный код содержит множество хороших примеров по сохранению и чтению сложных структур данных.
Формат пакетов#
Регистрация#
Перед регистрацией тайла, не забудьте зарегистрировать блок. Регистрация тайла почти ничем не отличается от регистрации блока:
Финал#
Теперь, кликнув на блок, мы увидем какое число содержится в TileEntity.
Тримы, тайлы и террейн: как происходит ремастеринг игровых карт
Игровые уровни занимают больше половины пространства экрана, что автоматически повышает требования к их выразительности и соответствию стандартам современной графики. И когда мы работали над ремастером своей игры War Robots, переработка карт по сути оказалась самой длительной и трудоемкой из работ по обновлению арта. Переработать весь пайплайн рендеринга, изменить подход к текстурированию в целом, поменять все модели на карте, по-новой произвести маппинг геометрии — всему этому следовало уделить внимание.
Со стороны геймдизайна от нас требовалось сохранить прежнюю атмосферу и основную концепцию, а также общий макет карты и габариты всех старых коллайдеров, чтобы геймплей оставался максимально близким к ее прежней версии. Игроки привыкли к старым уровням, и новые решения могут восприниматься ими негативно. С другой стороны, нам нужно было существенно переработать геометрию, увеличив полигонаж и сложность моделей. При этом обновленная графика не должна была привести к просадке производительности на старых устройствах, но в то же время на мощных планшетах с огромными экранами должна была соответствовать самым высоким современным стандартам.
Иными словами, нам нужно было:
Поменять все, ничего не меняя, и сделать так, чтобы созданное окружение смотрелось круто на самых последних моделях девайсов, при этом шустро работая и на лоу-энде.
Поэтому, чтобы не рисковать и не допускать излишних доработок, мы решили слегка обновить устаревший дизайн локаций в рамках уже имеющихся коллайдеров.
А чтобы этого достичь, нам нужны были новые концепты разного рода построек.
Концепт-арт: формируем стиль обновленной игры
Одна из основных задач на этапе концептирования — сохранив стилистику игровой локации и узнаваемые силуэты объектов, создать нечто новое и при этом заложить возможность большей детализации в рамках неизменных физических коллайдеров. И поскольку стиль War Robots — фотореализм без какой-либо иной стилизации, основной упор мы сделали на приближении масштаба и пропорций объектов максимально близко к реалистичным, чтобы у игрока создавалось ощущение, что он действительно управляет огромным боевым роботом высотой с четырехэтажный дом.
Для примера возьмем центральное здание карты Valley. Ниже показан оригинальный концепт и его реализация в «ванильной» версии War Robots:
А теперь посмотрим на концепт и 3D-модель той же самой постройки в War Robots Remastered:
В новой реализации появилось множество мелких деталей — это сделано для того, чтобы подчеркнуть масштаб модели. Раньше некоторые игроки упоминали, что из-за недостатка детализации не ощущали себя за штурвалом гигантского робота, поэтому в War Robots Remastered мы задались целью улучшить этот аспект.
Однако не стоит забывать, что любое даже самое современное устройство, на котором можно запустить обновленную War Robots, имеет свои ограничения, и их необходимо учитывать. Так, вместо индивидуальных текстур нам приходилось использовать ограниченные текстурные массивы, что могло привести к нехватке или несбалансированности используемых материалов. Из этого вытекало то, что уже на этапе концептинга нам нужно было создать черновой перечень текстур для геометрии уровня, определиться с их количеством, типом, основным цветом и многими другими аспектами. Поэтому концепт-художники и моделлеры формировали перечень этих текстур совместно, а затем первые использовали его для разработки внешнего вида объектов: сочетания и повторяемости материалов, деталировки, распределения цветов.
Пример доски планирования текстурных массивов. Здесь мы видим сопоставление материалов на зданиях к слоям (0-15) в массиве текстур по цвету вершины.
Быстрее, больше, мощнее: изменения в техническом пайплайне
Для ремастера мало создать красивую картинку — не менее важно не просесть по производительности по сравнению с Legacy-картами. Это стало возможным благодаря работе наших графических программистов и технических художников, обеспечивших нас современными, соответствующими всем стандартам инструментами для создания окружения.
По части кода мы переписали практически весь пайплайн рендеринга (Scriptable Render Pipeline, SRP), и теперь вместо классического рендера материалов (diffuse/normal/specular) у нас используется современный физически корректный рендеринг — PBR (albedo/metallic/normal/smoothness) для более точной передачи физических свойств материалов. Но об этом мы еще расскажем подробнее в будущих статьях.
Кроме того, мы стали применять современный подход тайлов и тримов вместо большого атласа текстур на всю локацию, что позволило нам упростить текстурирование большого массива однотипных объектов, а также сэкономить память на мобильных девайсах.
Что такое тайлы и тримы?
Тайловая текстура (Tile sheet) — однородная текстура, которая может повторяться n-ое количество раз как по горизонтали, так и по вертикали без каких-либо видимых швов;
Трим-текстура (Trim sheet) — тот же тайл, но повторяющийся в одном направлении: либо горизонтально, либо вертикально.
Основные ограничения по текстурам, накладываемые памятью лоу-энд девайсов, — это один, максимум два массива из 16 текстур с одинаковым разрешением 512×512px на основную геометрию уровня, которую можно и нужно текстурировать тайлами и тримами. Для HD-пресета мы можем использовать разрешение и 1024×1024px с более прогрессивными алгоритмами компрессии — и это почти не увеличивает потребление памяти, но по факту визуально разница на экранах мобильных устройств невелика. Обычно мы используем по одному массиву текстур на геометрию уровня и для террейна, использующему каждый свои шейдеры для специфических задач. Например, на индустриальных объектах не нужен такой плавный переход между текстурами, как на террейне. Вообще любые плавные бленды сложнее по просчету для GPU, поэтому шейдер террейна мы используем только на природных объектах.
Атлас для карты Canyon Legacy (2048×2048px) Набор из 16 текстур для карты Canyon Remastered (512×512px)
Что касается полигонажа, здесь мы тоже шагнули далеко вперед: теперь для HD-пресета качества в нашем распоряжении до 500 тысяч треугольников, тогда как раньше было максимум 200-300 тысяч.
Раз уж речь зашла о террейнах…
Прежде, чем идти дальше, давайте разберемся в том, какая разбивка уровня у нас вообще существует.
Итак, типичная игровая карта в War Robots состоит из:
Террейна. Обычно так называют основную поверхность игровой локации, по которой перемещаются динамические игровые объекты, — она же и является базой для расстановки игровой статики. Но в нашем случае это не всегда так. Под террейном мы подразумеваем объекты, использующие материал с особым типом смешивания текстур, предназначенный для реалистичной имитации естественных поверхностей: скал, песчаных дюн, галечника, почвы, — но и не только. Также он может успешно использоваться и для имитации техногенных поверхностей: разбитого асфальта, брусчатки, заросших травой бетонных плит.
Игровой статики. Это участвующие в игровом процессе объекты, с которыми возможно «физическое» взаимодействие. Как правило, сюда относятся различные виды строений.
Крупной неигровой статики. Сюда относятся объекты, влияющие на визуальное восприятие локации, но не участвующие в игровом процессе и не допускающие физического взаимодействия: скалы в окружении локации, осветительные фермы за границей геймплейной зоны и т. д. Эти объекты нельзя отключить для увеличения производительности на слабых устройствах, а значит, они нуждаются в лодировании — процессе подмены объектов разного уровня детализации (LOD) в зависимости удаленности их от игровой камеры.
Средней неигровой статики. Это статические обьекты без физического взаимодействия, размер которых сопоставим со средним размером робота — нашего основного динамического объекта. Их можно отключить для улучшения производительности, но из-за их размеров мы все же стараемся сохранить их в как можно больших пресетах качеств. Соответственно, на них тоже можно применить лодирование.
Мелкая неигровая статика. Сюда относятся объекты без физики, размер которых значительно меньше робота. Эти объекты нужны для создания ощущения масштаба и доступны только в пресетах HD или Ultra-HD. Но и их мы стараемся не отключать на слабых устройствах, если позволяют тесты производительности.
Пример мелкой неигровой статики — различного рода транспорт, контейнеры, столбы, балки и прочее
Тримы, тайлы, пропсы: разбивка геометрии уровня и создание текстур
А теперь, наконец, перейдем к основной артовой части.
Получив концепты в виде 3D-болванок, мы можем приступить к анализу уровня. Условно разбиваем всю геометрию на большие, средние и малые формы и решаем, что будет сделано с использованием тримов и тайлов, а что — в виде уникальных пропсов со своими шейдерами.
На этом этапе также необходимо определить объекты-укрытия и объекты с коллизиями и отделить их от тех, которые никак не влияют на геймплей. Нужно это для того, чтобы их можно было с легкостью отключать на более низких качествах графики (LD/ULD).
Ниже представлен один из концептов для карты Canyon:
Что мы здесь видим?
Площадь, по которой игрок перемещается, — это и есть террейн. Основной шейдер террейна — для объектов природного типа.
Синим отмечена игровая статика, с которой игрок может взаимодействовать — она же текстурируется с помощью тримов и тайлов. Для нее используется отдельный и основной шейдер.
Зеленые объекты — неигровая статика (пропсы), не имеющая коллайдеров, так что игрок может спокойно через нее проходить, никак не влияя на геймплей. Эти объекты используют уже другой шейдер со своими уникальными текстурами. Их можно отключать на слабых девайсах.
Красное — по факту тоже игровая статика (скальные образования как часть террейна), но для нее используется шейдер террейна.
Наконец, фиолетовым отмечена неигровая статика средних размеров, которую текстурируют с помощью тримов и тайлов, используя основной шейдер для крупных строений на карте. Эти объекты не имеют коллайдеров, но мы не можем их скрыть, поскольку они занимают большое пространство на экране, и у игроков с LD- и HD-пресетами разница была бы слишком сильно заметна. Другими словами, это просто часть бэкграунда, поэтому она менее детализирована, чем игровая статика, ведь игрок все равно не сможет подойти к ней вплотную.
Когда у нас формируется понимание о количестве объектов и их масштабе, можно приступать к созданию массива текстур. Желательно сразу заложить все текстуры, которые нам могут понадобиться, при этом помня, что полностью обойтись без их редактирования и добавления новых все равно не получится.
Обычно мы выделяем несколько тайлов (пол, стены и прочее) на большие поверхности и делаем к ним тримы для очертания и окантовки геометрии. Важно помнить, что структура тримов не должна меняться от текстуры к текстуре, иначе придется перемапливать всю геометрию. А для различного рода детализации (окна, решетки, двери, люки и прочее) мы формируем мини-атласы.
Один из примеров атласов для карты Powerplant
Общую плотность текселей — плотность пикселей текстуры на единицу масштаба объекта — мы решили использовать в районе 128 px/m2. Этого вполне достаточно, чтобы прорисовать даже мелкие детали на дверях, окнах, решетках и подобных элементах, которые не поплывут после сжатия до 512×512px ETC2.
Обычно мы создаем текстуры в связке 3ds Max/Maya + Substance Painter или Substance Designer + Substance Painter, но некоторые заимствуем из библиотеки Megascan от Quixel и редактируем под наши нужды. Пайплайн здесь несложный: моделируем геометрию в 3ds Max или Maya, разбиваем ее на ID Color, а потом запекаем все карты в Marmoset или в Substance Painter. После этого вся работа ведется уже в Substance Painter.
Другой вариант: в Substance Designer получаем все необходимые карты (height, normal, AO, curvature) и выгружаем в Substance Painter для текстурирования. А иногда текстурируем и сразу в Substance Designer — кому как удобнее.
Моделирование и маппинг основной геометрии уровня
Как только готов набор текстур, можно приступать к моделированию основной геометрии уровня. Для этого мы берем *.obj-файл концепт-болванки (если есть), делаем из него оптимизированное лоу-поли, стараясь сохранить интересный силуэт. При этом следим за объемом, чтобы здание не выглядело слишком плоским и скучным. Где нужно, добавляем дополнительную геометрию с небольшими скосами. Там, где можно обойтись просто тримом — нарезаем геометрию на полоски и мапим уже на них текстуры. Хотя зачастую при использовании технологии тайл- и трим-текстур это и не нужно: модель создается одновременно с текстурированием.
Уже знакомое нам главное здание с карты Valley
Главное на этом этапе — не переусердствовать и не превысить общий полигонаж сцены. Для HD-пресета качества этот предел достигает 500 тысяч треугольников. При этом наши игровые карты по своему наполнению сильно разнятся: где-то хватит и 300-400 тысяч, а где-то может не хватить и этих самых 500. Но мы помним, что ограничения и оптимизация — превыше всего, поэтому ради них порой приходится жертвовать красотой картинки.
Как правило, для основных зданий на LOD0 у нас выделяется в среднем от одной до пяти тысяч треугольников. Это оригинальный меш детализации, который игрок увидит перед собой вблизи. Последующие LOD оптимизируются в зависимости от дальности расположения камеры.
В целом полигонаж на уровни детализации определяется следующим образом:
Здесь можно увидеть, как отличаются LOD между собой для здания с карты Powerplant, и исчезают мелкие детали
В HD-пресете у нас используются только LOD0 и LOD1, в LD — LOD3 и LOD2 как базовый меш, в ULD — только LOD3 в качестве базового меша.
Также учитываем, что некоторые здания могут превышать высоту робота в два, а то и в пять раз. Если здание слишком крупное, делим его на модульные конструкции.
Наряду с постройками ведутся работы и по террейну. Базовый меш создается в World Machine или World Creator. Там же генерируются маски для SplatMap RGBA — текстуры, показывающей, где какой тайл прорисовывать на геометрии террейна.
SplatMap для террейна Powerplant. В данном случае в канале R используется трава, G — почва, B — скальная порода, A — песок
Меш, как правило, впоследствии дорабатывается и оптимизируется руками, а SplatMap дорисовывается в Substance Painter или уже непосредственно в Unity с помощью плагина Splat Painter.
Так выглядит террейн с примененной SplatMap и массивом из четырех текстур
Перед выгрузкой *.fbx в Unity мы назначаем цвета вершин на полигоны по ID в соответствии с нашей рабочей таблицей:
В Unity шейдер по цвету определяет, какую из 16 текстур массива использовать. Таким образом, в проекте получается по одному ID материалу на каждый меш. А если нужно использовать разные шейдеры, то просто разделяем этот меш на подобъекты.
Одно из зданий на карте Powerplant: слева старое, справа новое
От доработки до релиза — финальные этапы работы
Как только основные меши выгружены и настроены, начинается этап оформления. Сцена наполняется объектами детализации (пропсами), подкрашивается террейн с прилегающей геометрией, настраивается и запекается тестовое освещение и так далее. Кроме того, на этом этапе мы рисуем RGBA-маски для зданий, имитирующие легкие износы природного характера: потеки, грязь, пыль, сажа, ржавчина и прочее. Они нужны для того, чтобы разбивать монотонные тайлы на больших поверхностях, подчеркивать объем и в целом привносить в картинку определенную художественность и живость.
Вот так выглядит карта до запекания света и заполнения объектами детализации:
А так — после финальной полировки:
И снова здесь главное не перенасытить сцену мелкими объектами, которые впоследствии придется убирать при генерации разных качеств (HD, LD, ULD). Следим также за композицией объектов в кадре, чтобы она смотрелась красиво и гармонично с любого ракурса игровой камеры. Если нужно, сводим текстуры по тону и цвету, чтобы они не выбивались из общей картины.
Далее происходит оптимизация — один из первых технических этапов, после которых карта попадает в билд. Здесь идет настройка лодов, материалов, дистанции отрисовки, в случае обнаружения критических багов правится геометрия. После добавления всех объектов на source-сцену она передается левел-дизайнеру на ревью геометрии и читаемости окружения с точки зрения геймплея. После этого считается, что уровень целиком готов, и наступает следующий этап генерации разных качеств (HD, LD, ULD) от исходной source-сцены. В арсенале технических художников есть отличные инструменты для автоматизации этого процесса, но ручной доработки все же не избежать.
Так выглядят HD, LD и ULD пресеты карты:
После генерации карты в разных пресетах качества она отдается обратно левел-дизайнерам. Они снова тестируют теперь уже обновленную геометрию, читаемость уровня и выдают общий фидбек. Кроме того, на финальном этапе создания карты левел-дизайнеры несколько раз отправляют ее на внешнее тестирование. Это отдельный тестовый сервер, куда мы приглашаем игроков оценить новый контент, готовящийся к релизу. Этот метод позволяет нам собрать отзывы по геймплейным аспектам, графике, а также оценить производительность карты, так сказать, в «боевых» условиях. Подробнее об этом мы расскажем в следующем материале.
После всех правок и доработок за карту принимается QA-отдел. Тестировщики тщательно проверяют уровень на различные баги, связанные с графикой и геймплеем. Тестирование выполняется на всевозможных смартфонах и планшетах, чтобы убедиться, что карта будет хорошо выглядеть на устройстве вне зависимости от его технической начинки. Здесь же происходит финальная проверка производительности сцены и сравнение ее показателей с другими картами.
И, наконец, карта попадает в игру. После этого художники и моделлеры могут приступить к переработке следующей карты. Тем временем левел-дизайнеры и техническая команда проекта следят за показателями той, что ушла в релиз, специалисты техподдержки собирают жалобы игроков, связанные с потенциальными багами на уровне, а комьюнити-менеджеры мониторят отзывы в социальных сетях. Этот процесс поддержки карты после релиза не менее важен, чем остальные: здесь мы отлавливаем незамеченные баги и прочие моменты, которые дорабатываем к следующему запланированному релизу. Таким образом, работа над картой становится непрерывным процессом, который не завершается даже тогда, когда уровень доводится до релизной готовности. Что, впрочем, считается нормальным явлением для любой GaaS.
До/после: карта из «ванильной» War Robots и War Robots Remastered:
Подводя итоги: какие приемы и почему показались нам наиболее эффективными
Переход на новый графический пайплайн (SRP) позволил нам использовать PBR текстуры вместо классической модели diffuse/normal/specular. Это упрощает создание текстур, поскольку современные пакеты вроде Substance Painter/Designer, а также Quixel заточены именно под PBR.
Использование текстурных массивов (Texture Array) и тримов/тайлов предоставляет нам возможность более эффективно расходовать память на девайсе при текстурировании большого количества крупных однотипных моделей. Это также сокращает время создания более детализированных объектов.
«Умная» система лодирования позволяет увеличить полигонаж и детализацию на HD-картах не в ущерб общей производительности на низких качествах и слабых девайсах.
Самописные инструментарии вроде Splat Painter упрощают рисование Splat-масок для террейна и не только.
Кастомные шейдеры и материалы под SRP дают возможность воплотить весь творческий потенциал, заложенный на этапе препродакшена карты.
Появление шейдера, поддерживающего прозрачность, дает возможность нам использовать декали на вертикальных и горизонтальных поверхностях. Эти декали могут быть как в виде разметки на дороге, так и надписей и логотипов на зданиях.
Отказ от старых технологий в пользу более гибкого и удобного инструментария дал нам возможность повысить общее визуальное качество уровней и приблизить их к современным стандартам мобильной игровой графики.
Автор материала — старший 3D-художник Pixonic Александр Коляда, также помогал Алексей Пастушков