# Protos, дневник #5: «а какой смысл гасить свет, если без света не видно?»
В прошлом дневнике я показал первую способность: гасить свет в направлении взгляда, даже сквозь стены. Реакция была тёплая, но человек, c большой студии, что смотрит на игры профессионально, задал мне ровно один вопрос:
> Работа збс, но мало что понятно про свет и зачем он. Без света же не видно. Какой смысл?
И я не нашёл, что ответить. Потому что он прав: способность, которая делает мир хуже видимым и ничего не даёт взамен, это не способность, а самострел.
Эти сутки ушли на ответ.
Резонанс-скан
Нажимаешь кнопку, и от игрока расходится импульс. Все живые цели в пределах его зоны и в радиусе сорока метров подсвечиваются сквозь стены на десять секунд. Кулдаун тридцать секунд. Способность открывается не сразу, а когда накопил достаточно связи с зоной.
И вот теперь темнота работает на тебя. Свет ты гасишь, чтобы тебя не видели. Сам видишь через скан. Две способности сцепились в пару, и первая наконец получила смысл.
Это, кстати, хороший урок про фидбек. Я неделю смотрел на погасшие лампы и радовался, как красиво. Понадобился один вопрос со стороны, чтобы стало видно дыру в дизайне размером с механику.
Заливка или обводка: подсветка знает, видишь ты цель или нет
Первая версия просто заливала силуэт целиком. Работало, но мешало: цель стоит прямо перед тобой, а ты смотришь на светящееся пятно вместо персонажа.
Сейчас подсветка ведёт себя по-разному:
- **цель за стеной** - силуэт залит целиком, светится ровным цветом; - **цель на прямой видимости** - заливка уходит, остаётся только тонкая обводка.
Технически это сравнение двух буферов глубины прямо в пост-процессе. Движок умеет рисовать помеченные объекты в отдельный буфер **Custom Depth**, независимо от того, загорожены они или нет. Рядом лежит обычная глубина сцены. Если глубина цели больше глубины того, что реально видно в этом пикселе, значит перед целью что-то стоит, и она за стеной.
Причём сравнение не бинарное, а с мягким переходом: разница до десяти сантиметров плавно гасит заливку. Без этого на кромке силуэта дрожал бы шум из-за точности глубины.
Как метится цель
В игре это делается так же, как «вх» в шутерах, только легально и по нашим правилам.
У каждого меша есть флаг «рисуйся дополнительно в Custom Depth» и номер-метка. Код скана обходит все цели, отсеивает тех, кто вне зоны или дальше сорока метров, и оставшимся ставит флаг и метку, а таймер снимает флаг через десять секунд. Пост-процесс материал читает эту метку и рисует поверх картинки то, что нужно.
Разделение получилось приятное: **логика вообще ничего не знает про рендер**, она просто помечает актёров номерами. А материал ничего не знает про резонанс, зоны и кулдауны, он просто раскрашивает то, что помечено. Захочу завтра подсвечивать врагов красным, а союзников цветом зоны - это разные номера метки, и код скана менять не придётся.
История про 45 нод и девять строк
Обводку я собирал руками в редакторе материалов. Идея простая: пиксель на кромке силуэта это тот, у кого сосед помечен, а он сам нет. Значит надо прочитать метку у четырёх соседних пикселей и сравнить.
Каждое такое чтение в нодах это: взять экранную координату, посчитать размер пикселя, умножить на толщину, задать направление, сложить, прочитать текстуру, вытащить канал. Семь нод. На четыре стороны - двадцать восемь. Плюс сборка, плюс сравнение, плюс проверка перекрытия, плюс цвет.
Я построил половину, посмотрел на этот макаронный завод и вспомнил, что в UE есть нода **Custom**, где можно писать шейдерный код напрямую. И оттуда доступна та же функция чтения буферов, которой пользуются сами ноды.
Весь блок ужался до девяти строк:
```hlsl float2 Texel = InvSize * Thickness; float C = saturate(Stencil.r);
float N = 0; N = max(N, SceneTextureLookup(UV + float2(Texel.x, 0), PPI_CustomStencil, false).r); N = max(N, SceneTextureLookup(UV - float2(Texel.x, 0), PPI_CustomStencil, false).r); N = max(N, SceneTextureLookup(UV + float2(0, Texel.y), PPI_CustomStencil, false).r); N = max(N, SceneTextureLookup(UV - float2(0, Texel.y), PPI_CustomStencil, false).r);
float Outline = saturate(saturate(N) - C); ```
Граф материала при этом ужался с примерно сорока пяти нод до десяти. И это стало не только короче, но и **читаемее**: девять строк я понимаю целиком, а сорок пять нод я понимал ровно до того момента, пока не отходил от монитора.
Вывод для себя записал такой: нодовый редактор хорош, пока ты соединяешь вещи. Как только начинаешь в нём **считать** - лучше писать текстом.
Бег по стенам
Параллельно доделал давнюю хотелку.
Разбег вдоль стены, персонаж прилипает и бежит. Первую секунду он **разгоняется** и гравитация выключена полностью. Дальше скорость начинает падать, а его понемногу тянет вниз. То есть окно, в котором можно что-то полезное сделать, ограничено не таймером в углу экрана, а тем, что ты физически чувствуешь: пока летишь быстро - высота держится, замедлился - поехал вниз.
И одно правило, без которого всё разваливается: **сорвавшись со стены, к ней же прилипнуть повторно нельзя**. Пока не коснёшься земли или не потратишь рывок. Без этого правила можно бесконечно чиркать по одной стене и лезть по ней вверх лесенкой, а вся геометрия уровня перестаёт что-либо значить.
Анимации взял ретаргетом и они мне не нравятся: конечности не прилипают к стене, персонаж как будто бежит рядом с ней. Знаю, что лечится IK, но пока сойдёт - лучше кривая анимация на работающей механике, чем наоборот.
Рубрика «компилируется и врёт»
Код я по-прежнему набиваю руками, коллекция молчаливых багов пополнилась.
**Забытая строчка кулдауна.** Функция скана проверяла время последнего использования, но нигде это время не записывала. Компилируется идеально, работает идеально, кулдауна просто нет: можно спамить кнопку без остановки. Самый противный вид бага - тот, где отсутствует то, чего в коде и не видно.
**Опечатка в поле, одинаковая в двух местах.** Назвал переменную `ScanStenicValue` вместо `Stencil`, и в заголовке, и в реализации. Собралось молча, потому что компилятору всё равно, как называется твоё «ничто».
**Пропавшее объявление.** Из заголовка исчезло поле, а геттер, который его возвращает, остался. Тут повезло: билд упал сразу. Но если бы упал после того, как редактор загрузил уровень, у всех расставленных зон стёрлись бы имена.
И отдельная рубрика, новая: **редактор материалов против человека**. Дважды подряд поиск подсовывал мне соседнюю ноду с похожим именем. Вместо `Lerp` - `InvLerp`, обратная операция. Вместо смешивания - функция послойных материалов.
Что дальше
След за персонажем переделывается: не хвост, а плазменный шлейф, как у аппарата, входящего в атмосферу. И следом большая тема - пространство вокруг игрока начинает распадаться на кубики по мере роста связи, а разрезы между ними светятся.
Мультиплеер закладываю с самого начала: резонанс у каждого игрока свой в каждой зоне, скан ищет других игроков. Пока гоняю на двух окнах в редакторе, один бегает, второй сканирует.
Ищу людей
Проект я тяну один и упёрся в потолок там, где своими руками уже не вывезу.
**Аниматор.** Сейчас всё живёт на Game Animation Sample и ретаргете со стороны. Механика работает, но видно, что движения чужие: при беге по стенам конечности не прилипают, а под способности резонанса своих анимаций нет вообще. Нужен человек, который сделает движения под механику, а не наоборот.
**Художник по персонажам.** Персонаж пока стандартный манекен Epic. Нужен свой. Игра от первого лица, так что отдельно важны руки в кадре, а тело будут разглядывать другие игроки в мультиплеере.
**Художник по окружению, он же левел-арт.** Тут три задачи по убыванию важности:
- собирать пространства **под паркур**: чтобы карнизы читались, маршрут угадывался, вертикаль была осмысленной. Сейчас я лазаю по лесу из Electric Dreams вопреки, а не благодаря - его никто не проектировал под лазанье; - давать **зонам визуальную идентичность**. Ядро игры построено на зонах, а сейчас зона от зоны отличается только цветом воздуха. Хочу, чтобы они отличались архитектурой и настроением; - держать сцену в бюджете. Именно тюнить: плотность фолиажа и PCG, дальности отсечения, виртуальные тени, овердро на масках листвы. Полигонаж не предлагать - проект на Nanite, там это давно не главная статья расходов.
Что за проект: паркурный шутер от первого лица на UE 5.8, ядро - механика связи игрока с пространством, мультиплеер закладывается с самого начала. Код, системы и геймдизайн на мне, дневники видите сами - показываю всё, включая то, что сломал.
Контакт Discord: tot_samiy
Спасибо, что читаете. И отдельное спасибо тем, кто задаёт вопросы.