В январе этого года наш игровой департамент выпустил мобильную игру «Эволюция: Битва за Утопию». Игру хорошо приняли, людям нравится играть в нее. Я часто вижу людей с «Эволюцией» в метро. Даже начал желания загадывать, когда оказываюсь между игроками. И в этом посте, подготовленным по моему докладу на КРИ 2014, я хотел бы подробнее рассказать о процессе разработки и особенностях «Эволюции».
Недавно у нас вышло большое контентное обновление «Черный легион». Оно, естественно, привлекло много новых игроков, но самое главное, мы вернули большую часть своих игроков, которые уже прошли основную сюжетную ветку. В этой статье я расскажу о том, как развивалась наша игровая механика в процессе разработки. Вкратце упомяну о сторонних инструментах, которые мы использовали. И побольше внимания уделю графическим «фишкам», расскажу о том, как на слабых девайсах получить настоящие динамические тени и динамическое освещение.
Игровая механика
В начале разработки, в первых концептах «Эволюция» выглядела совсем иначе, нежели сейчас. У нас было две камеры, split screen, и они отображали две армии, которые располагались довольно далеко друг от друга. Это расстояние между ними никак не чувствовалось. Например, одна армия бросает гранаты. Они долетают до центра экрана, пропадают на некоторое время и потом появляются на второй части экрана. Игрокам это было неудобно. Мы провели некоторые изыскания, нарисовали различные схемы, провели мозговой штурм, придумали новую механику и геймплей. Затем потихоньку стали все это реализовывать.
В ранних версиях у нас не было даже авто-огня. Мы заставляли игрока постоянно нажимать на соответствующую кнопку, чтобы стрелять в выбранного противника. Плюс у нас был прицел, имитирующий разброс пуль при отдаче: вы стреляете, прицел раздвигается и начинает постепенно сходиться обратно. Пока он не свёлся, вы наносите чуть меньший урон. Все это было слишком сложно, даже для нашей хардкорной аудитории. В это время вышла игра «Dead Trigger 2». Это обычный First Person Shooter. Но, на наше удивление, в этой игре ввели авто-огонь. Мы посмотрели, попробовали внедрить подобную схему управления в Эволюцию и пришли к такой механике, которая сегодня и представлена в игре.
Не бойтесь развивать свой проект. Это та цена оригинального геймплея, которую вам придется заплатить как разработчикам. Если у вас оригинальная игровая механика, то ее нужно проверять на реальных людях, возможно, от чего-то придется отказаться, что-то придется дописать.
Сторонние инструменты
В нашей игре практически 80% всей игровой механики построено вокруг GUI. В «Джаггернауте» мы использовали систему GUI собственной разработки, она в каком-то состоянии до сих пор там работает. Но когда мы проектировали «Эволюцию», то посмотрели, что предлагается на рынке. Так мы нашли NGUI и сравнили его функционал с тем, что было у нас. После чего пришли к выводу, что либо нам придется несколько месяцев дописывать свою систему, либо мы можем взять готовое решение.
Мы используем NGUI версии 2.2.3, которая имела несколько багов. Мы много чего в ней пропатчили. Например, написали автоматическое масштабирование под разные разрешения и специальный атлас-менеджер, который выгружает большие текстуры при смене экранов. За счет этого исчезли лаги при переключении экранов и прекратились падения по памяти.
Посмотрите на эти здания, в игре они анимированы. Это обычная флеш-анимация. Чтобы ее импортировать, мы использовали еще один инструмент, который называется uniSWF. Они неплохо взаимодействуют вместе с NGUI.
Еще один большой фреймворк, который мы использовали в «Эволюции» — это PlayMaker. Когда мы начинали проектировать «Эволюцию», нам нужна была какая-то технология для анимационных, да и вообще, для игровых машин состояний (Finite state machines или конечные автоматы). Технология Mecanim в то время была в зачаточном состоянии, и не позволяла делать очень нужную для нас вещь, а именно – переключать наборы анимаций. Игрок может взять в бой два вида оружия — пистолет и что-то потяжелее: пулемет, дробовик и т.п. Mecanim в том состоянии своего развития этого не позволял делать. Поэтому мы взяли за основу технологию PlayMaker, который не только позволяет описать неплохую стейт-машину для анимации, но также позволяет добавить некоторую логику из скриптов.
Система освещения
Для «Эволюции» мы написали собственную систему освещения. Вообще, для чего писать что-то свое, если в движке уже все готово, просто бери и используй? Все дело в том, что системы, встроенные в Unity, довольно универсальны. За обслуживание этих систем приходится платить производительностью. Особенно если целевое устройство слабопроизводительное. По сути, нужно платить за универсальную систему освещения своим FPS. Поэтому мы отказались от этого подхода в пользу так называемых Dynamic Light Probe. Также мы полностью отказались от обычных динамических источников света, которые есть в Unity. Всю статическую геометрию мы освещаем с помощью Lightmap. Для персонажей мы используем специальные Light Probe, и собственный шейдер.
Light Probe – это сферическая гармоника, содержащая информацию об освещении в указанной точке. Таких Light Probe у нас на сцене несколько десятков. Движок выбирает 4 ближайших к персонажу Light Probe и интерполирует их в одну. Затем мы добавляем в нее свет от наших специальных динамических источников света. Таким образом, получается Dynamic Light Probe – т.е. сферическая гармоника с добавлением динамического света. После этого начинается рендеринг персонажа с шейдером, который умеет работать со сферическими гармониками.
Если представить сферическую гармонику упрощенно, то ее функционал будет похож на кубическую текстуру (Cube map). В шейдере мы делаем выборку из сферической гармоники по вектору (аналогично Cube map). Для диффузного (diffuse) освещения мы используем вектор нормали к поверхности, для отраженного (specular) – отраженный вектор взгляда.
Диффузное освещение считается в вертексном шейдере, отраженное – в пиксельном. Таким образом, мы за один Draw Call рендерим модель персонажа с динамическим освещением от нескольких источников света.
Тени
До прихода в Mail.Ru Group я несколько лет разрабатывал свой движок, и успел проработать практически все алгоритмы рендеринга теней и освещения, и понимаю, как работает рендер Unity и тени в частности. Там используется shadow mapping, а точнее PSSM — parallel split shadow mapping. Невозможно на слабом устройстве получить высокий FPS при использовании такой техники.
Поэтому я разработал три системы, чтобы попробовать какая из них будет более производительной. Остановился на алгоритме planar-shadows, или тенях, которые рисуются на плоскости. Это самый простой алгоритм, но и вместе с тем самый производительный.
Что такое планарные тени? Это рендеринг такой же модели, только сплющенной по какой-то из осей до нуля. Выбор оси масштабирования зависит от угла источника света.
В интернете вы можете найти кучу примеров, как посчитать такую матрицу масштабирования. Я хотел бы остановиться на других аспектах. Во-первых – это блендинг тени. Если ее блендить ее напрямую, то получаются такие артефакты как на следующем рисунке.
Там, где пересекаются несколько полигонов, они и блендятся несколько раз. И тень получается в некоторых местах темней. Как с этим бороться? Мы все эти тени рисуем сначала в stencil buffer, отключая запись в back buffer. Затем, когда все тени занесены в stencil buffer, мы делаем финальный проход рендеринга, на котором заливаем весь экран нужным цветом, с учетом stencil-маски. Получаем корректные динамические тени, которые никак (!) не влияют на FPS. Во-вторых, планарные тени можно рендерить только на плоские поверхности (недаром они называются планарными).
С этим бороться не имеет смысла, на наш взгляд. Мы просто переработали контент таким образом, чтобы тени всегда ложились на непрерывную плоскость.
Пост-эффекты
Стандартное решение для пост-эффектов, которое используется во всех примерах Unity — это использование метода OnRenderImage. Суть его в следующем: c Unity работают не только программисты, но и гейм-дизайнеры, художники, аниматоры и другие специалисты. У них всех разная квалификация, и разработчики Unity вынуждены делать универсальные решения, чтобы пользователи просто брали движок из коробки и получали какой-то более-менее работающий пост-процесс. Из-за подобной универсальности в Unity прибегли к следующей технике — они за вас копируют back buffer в отдельную текстуру и отдают вам ее на обработку. В чем здесь проблема? Копирование back buffer’а в текстуру на iPad 2 убьёт больше половины FPS. Даже простой вызов метода OnRenderImage без дальнейшей обработки снижает производительность больше, чем в 2 раза. Как с этим бороться? Мы указываем камере отдельную рендер-текстуру и обрабатываем ее в методе OnPostRender. Единственное, что придется сделать дополнительно — отрисовать результат в back buffer.
Также, по возможности, если вы сможете придумать, как заменить вот такой мощный пиксельный пост-процесс на какие-то геометрические эффекты, то это стоит сделать, потому что геометрические эффекты работают заведомо быстрее.
Еще пара слов о геометрическом пост-процессе: так как мы все равно рендерим всю сцену в свою рендер-текстуру, то впоследствии можем использовать ее как захотим. В данном случае, рисуя снайперский прицел, мы накладываем текстуру на специально подготовленный mesh, у которого растянуты текстурные координаты, таким способом, что создается впечатление преломления по краям. В динамике это выглядит, как будто вы смотрите через линзу. Этот пост-процесс вообще никак не влияет на FPS.
Вот такими методами и уловками мы получили наши 30 FPS (и выше) на iPad 2 и iPhone 4S. Приложив некоторые усилия, мы получили, практически, бесплатное в плане производительности динамическое освещение и тени. Мы прошли долгий путь разработки, сменили множество механик и подходов, чтобы получить ту игру, которую вы видите сейчас. Если у вас есть какие-то вопросы, то буду рад ответить на них в комментариях.
Презентация с КРИ:
Александр Черняков
Программист IT Territory, студии Mail.Ru Group
Автор: GamesMRG