Protos, дневник #6: два дня скучной работы
Некоторое время я ничего не показывал, потому что занимался тем, что обычно откладывают на потом: загрузка, меню, прогрев шейдеров. Скучные вещи, без которых игру нельзя отдать людям.
По дороге выяснилось, что скучные вещи в Unreal преподают самые неожиданные уроки.
Зеркальная комната
Раньше игра запускалась сразу в лес, без всякого меню. Теперь есть лобби: комната, персонаж по центру, вокруг отражения.
Первым делом я полез в честные зеркала. В UE 5.8 они есть: Lumen с аппаратной трассировкой умеет рекурсивные отражения, до восьми штук подряд, зеркало в зеркале в зеркале. Красиво и физически правдиво.
И дорого. Плюс требует видеокарту с трассировкой, а мне надо, чтобы билд открылся у тестеров на слабых машинах.
Дальше был план со Scene Capture и обратной связью по кадрам: захват пишет картинку в текстуру, зеркало эту текстуру показывает, захват снимает зеркало с текстурой прошлого кадра, и глубина набирается сама. Так делают бесконечные зеркала в дешёвых шейдерах. Работает, но возни много: два захвата, две текстуры, ручной расчёт положения зеркального двойника камеры.
А сработало то, что я пробовал в последнюю очередь. Обычные Screen Space Reflections, маленькая коробка, один источник света. Всё.
SSR отражает только то, что уже нарисовано на экране, и в большой открытой сцене это выглядит криво. Но в маленькой закрытой комнате с неподвижной камерой на экране и так почти всё - ограничение просто не проявляется. Дёшево, красиво, работает без трассировки.
Урок записал такой: выбирай технику под свои условия, а не по тому, что звучит солиднее. Я потратил вечер на два продвинутых способа, а победил самый простой, потому что подошёл к задаче.
Экран загрузки, который научил меня, как устроен движок
Хотелось стандартного: картинка на фон, полоска снизу. Сделал виджет, повесил, запустил. Картинка есть, текста и полоски нет.
Полчаса я двигал элементы, менял цвета, проверял порядок отрисовки. Потом дошло.
Во время загрузки уровня игровой поток заблокирован. Именно поэтому в движке есть отдельная система для экранов загрузки - она рисует поверх, на другом потоке, пока основной занят. И отсюда два следствия, которые бьют по рукам:
Первое: Tick и анимации UI не работают. Полоска, которую я анимировал по тику, была мертва по определению. Живым может быть только виджет, который анимируется силами самой системы отрисовки - готовый спиннер. Заменил, крутится.
Второе, более коварное: не выполняется проход раскладки. Элементы, которые считают свой размер по содержимому (текст, полоса), остаются нулевого размера. Формально они есть, фактически невидимы. Картинка работала только потому, что была растянута по якорям и размер имела изначально.
Лечится заданием явных размеров. Но найти это самому, без понимания, что поток стоит, практически невозможно - симптом выглядит как «виджет сломался».
Заодно узнал, что настоящего процента загрузки в Unreal не существует. Открытие уровня блокирует поток, спросить «сколько загружено» не у кого. Все полоски загрузки, которые вы видели в играх на этом движке, либо бесконечные крутилки, либо честная ложь.
Прогрев шейдеров: оптимизация, о которой я не знал
Вот это самое полезное, что я вынес за два дня.
Знакомая всем вещь: заходишь в игру, бежишь, и на секунду всё замирает. Потом ещё раз в другом месте. Потом перестаёт. Это не «плохая оптимизация» в общем смысле - это компиляция шейдерных состояний в момент, когда объект впервые появляется в кадре.
Лечится не понижением качества, а прогревом заранее. В UE5 есть механизм, который готовит эти состояния по мере загрузки объектов, до того как они понадобятся.
Я вывел счётчик оставшихся в интерфейс и заблокировал кнопку «Начать игру», пока он не обнулится. Логика простая: пусти игрока в мир раньше времени - он получит фризы и решит, что игра тормозит. Пусть лучше десять секунд посмотрит на меню.
Занятно, что пока я это делал, счётчик упорно показывал ноль. Оказалось, в движке два разных механизма прогрева, и я считал не тот: один работает с заранее запечённым файлом (которого у меня нет), другой прогревает в рантайме. Добавил второй счётчик, включил рантайм-прогрев - ожило.
Ловушки редактора интерфейсов
Отдельная коллекция, чисто чтобы вы не наступали.
Якорь-точка и якорь-рамка - это разные режимы одних и тех же полей. У точки поля называются «позиция» и «размер». У рамки те же самые поля превращаются в отступы, и элемент растягивается. Я вписал 400 в поле, которое стало отступом, и получил элемент во весь экран. Выглядело как баг движка, оказалось непонимание.
Элемент не становится переменной автоматически, если в настройках проекта это отключено. В графе его просто нет, и ты ищешь, куда он делся.
У виджета нет сеттера. Переменную виджета можно только прочитать, а менять состояние надо функциями. Логично, когда понял, неочевидно, когда ищешь.
Меню, которое работает в редакторе, может не работать в собранной игре. У меня интерфейс создавался при старте уровня, а в отдельном запуске контроллер игрока появляется позже - виджет молча не создавался. В редакторе тайминги другие, поэтому баг был не виден. Урок: всё, что связано с игроком, создавать в контроллере, а проверять в настоящей сборке.
Что дальше
Настройки качества, чтобы тестеры на слабых машинах могли снизить нагрузку. Потом обратно к резонансу: разлом на стыке зон и распад пространства на светящиеся кубики.
И я всё ещё ищу людей: аниматора и двух художников, по персонажам и по окружению. Денег у проекта сейчас нет, зато вся работа остаётся у вас, а если игра выйдет и окупится - участники получат свою часть. Подробности и контакт в отдельной теме, или пишите в дискорд: tot_samiy
Спасибо, что читаете.