# 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

Спасибо, что читаете. И отдельное спасибо тем, кто задаёт вопросы.