Итак, мы реализовали обновление карты видимости и применение тумана войны через .cginc, вставленный во все шейдеры проекта. Но пока что в самом простом виде, что дает некрасивые пикселированные границы видимых областей. В этой части мы устраним пиксели а также реализуем сокрытие вражеских юнитов туманом войны.
Сглаживаем пиксели
Напомню, что на данный момент мы имеем ортогональную камеру с видом сверху, которая рендерит белые круги вокруг юнитов в текстуру видимости. Собственно, сгладить эту текстуру – элементарное действие. Просто добавим стандартный Blur. Все, готово:
Поскольку вся эта конструкция с отдельной камерой была вынесена в префаб, то добавление и настройка размывки делается полностью в Unity. То есть, эту фичу можно реализовать, не написав ни строчки кода! Обожаю такие моменты :-) Ну а сам префаб выглядит примерно так:
И может показаться, что по туману войны уже все готово, но это не так. Дело в том, что туман войны затеняет все объекты, в том числе и врагов. А должен прятать их полностью. Правильный способ сделать это – отключать рендеринг этих объектов, а не просто модифицировать цвет или clip’ать в шейдере. А это значит, что нам нужен доступ к информации о видимости на CPU.
Revealers and Revealables
Нет, мы не будем пытаться читать обновляемую каждый кадр текстуру на CPU. Вместо этого мы просто заново выполним нужные расчеты на CPU. На GPU мы пытаемся сделать красивую картинку, на CPU же мы хотим просто сделать так, чтобы враги скрывались и появлялись зависимо от того, видит ли их кто-то из “наших” или нет.
В самом тупом виде можно сделать простой перебор. Получим квадратичную сложность, точнее, N * M, где N – количество наших юнитов и M – количество врагов. На самом деле вместо “наших юнитов” будут выступать Revealer’ы, а вместо врагов – Revealable’ы. Ну потому что негоже туману войны вообще знать про каких-то там юнитов. Да и Revealer’ами могут быть не только юниты, но и служебные объекты в ключевых местах карты.
Но такая реализация меня не устраивала. Поэтому я решил применить простенькую оптимизацию. Каждый Revealable будет иметь 2 списка Revealer’ов: ближайших и всех остальных. Ближайших мы будем проверять каждый кадр, всех остальных – время от времени. Если один из ближних Revealer’ов оказывается далеко, переносим его в дальний список. Если один из далеких оказывается достаточно близко – переносим его в ближний список. При этом расстояние перехода должно быть больше, чем радиус видимости. Если точно, то оно должно составлять радиус видимости плюс сумму максимальных скоростей этих объектов, деленную на интервал обновления. Но, честно говоря, меня такая корректность не интересовала, поэтому я просто подобрал достаточно разумное значение.
Пара интересных мест помечена стрелочками:
- Распределяем полные обновления разных юнитов по кадрам.
- Чтобы определить, что Revealable достаточно близок к Revealer’у, спрашиваем последнего о том, раскрывает ли он Revealable’е большего радиуса, чем мы. Такой ход мне поначалу казался почти что костылем, позволившим не трогать интерфейс Revealer, но в дальнейшем он пришелся очень кстати ;-)
А теперь зигзагом
Еще в разрабатываемой игре были такие типы миссий: уничтожение конвоя и защита конвоя. В уничтожении конвоя нужно, собственно, уничтожить вражеские грузовики, которые едут по предустановленным маршрутам. Почти что Tower Defense, но с танками вместо башен. Ну а в защите конвоя нужно оборонять собственный конвой от врагов.
И вот в какой-то момент Данила, наш гейм-дизайнер, спросил: а есть ли какой-то способ “просветить” маршрут через туман войны? Или просто понатыкать круговых Revealer’ов?
Понатыкать круговых Revealer’ов это, конечно, вариант. Но это менее удобно, менее сопровождаемо, да и менее производительно. Так что я решил быстренько реализовать новый тип Revealer’а: RouteRevealer.
Логика работы предполагается довольно простая: ставим SphereRevealer в каждую точку пути и ставим по ориентированному RectangleRevealer на каждый пролет. Да, я решил использовать наследование для таксомии Revealer’ов: это кажется вполне подходящим и простым решением в данном случае. Собственно, нужно реализовать RectangleRevealer.
Реализовать Revealer означает сделать 2 вещи: добавить отрисовку правильной картинки в карту видимости и добавить расчет на CPU. В случае с прямоугольником обе задачи тривиальны. Для отрисовки мы просто добавляем MeshRenderer со стандартным квадом. А для расчета перенесем проверяемую точку в локальное пространство и посмотрим, попадает ли она в axis-aligned rectangle:
Да-да, это одна из реализаций той самой Reveals(), что вызывается из Revealable.
После этого остается лишь добавить RouteRevealer, который насоздает дочерних Revealer’ов для маршрута. Конфигурация маршрута не может меняться во время игры, что упрощает задачу. Вот как это выглядит при включенном слое Fog of War:
Готово?
В общем-то да. Ну, разве что добавить еще пару мелочей, вроде автоматического раскрытия стреляющих по нам врагов. Но как я спалил в названии, это еще не конец истории с туманом войны. Что же еще? Скоро узнаете :-)