Как Rubber Duck Games разработала бой с боссом в Evil Wizard

Компания Rubber Duck Games, один из победителей конкурса GDC 2023 Best in Play, рассказывает о том, как они создали захватывающий бой босса. В этом гостевом блоге члены команды Банки и Серджио Ваджсвол проведут вас через весь путь, от дизайна и прототипа до анимации, тестирования, балансировки и окончательной доработки визуальных эффектов и звука для Evil Wizard.
Привет, читатели! Я Банки, гейм-дизайнер и продюсер в Rubber Duck Games. Наша наполненная юмором ролевая игра Evil Wizard уже вышла на Steam и Xbox, и я хотел бы рассказать вам о том, как мы разрабатывали бой с боссом.
Evil Wizard - это игра в жанре Metroidvania, в которой вы окажетесь на месте поверженного "финального босса", жаждущего мести проклятому герою.
В поисках мести игроки пройдут через очаровательные пиксель-арт окружения, заполненные полчищами врагов, которых им предстоит одолеть, чтобы вернуть свое. Когда-то главный герой был могущественным волшебником, но проигранная битва с героем лишила его сил, и ему предстоит вернуть их себе, чтобы проникнуть в замок и свершить разрушительную месть.
Первым героем, которого предстоит победить в игре, станет Хейлга, могущественная ледяная колдунья. Мы будем использовать ее в качестве примера босса для этого глубокого погружения.
Давайте начнем с самого начала. Создать босса (и всех врагов) для Evil Wizard было непросто. Несмотря на то, что игроки сражаются против хороших парней, мы знали, что они должны выглядеть достаточно грозно, чтобы их можно было распознать как врагов.
Для боссов мы искали вдохновение и отсылки среди героев известных игр. При создании Хейлги мы черпали вдохновение в Джайне из Warcraft, поскольку у них много общего.
Первым делом мы подготовили электронную таблицу (которую мы использовали для каждого босса в игре), в которой подробно описали основные черты персонажа, механику и некоторые рекомендации.

Разработав механику, мы могли перейти к следующему шагу: созданию прототипа.
Пришло время представить команде дизайн нового босса, и мы собрались на совещание, где я прошелся по документу и объяснил некоторые из своих решений.
Здесь мы немного подправляем дизайн - в некоторых случаях механика может оказаться слишком сложной для разработки, анимация - слишком сложной для рисования и т. д. Мы стараемся делать все вовремя, поэтому есть некоторые моменты, которые следует учитывать, чтобы сделать лучшего босса за то время, которое у нас есть.
Когда дело дошло до создания прототипа, вот что сказал наш ведущий программист Диего Ордоньес: "Хейлга была нашим первым боссом в Evil Wizard, и я впервые в жизни программировал босса. Я знал, что это сложная задача, поэтому начал с того, что делает каждый программист: разделил атаки на простые задачи и выполнял их по отдельности. У этого босса есть атаки, которые было просто выполнить, повторно используя снаряды".
"Атаки ледяной пули и ледяной ракеты ощущаются игроками совершенно по-разному: от одной просто увернуться, а другая представляет собой вызов во время боя", - продолжил он. "Ключевое различие заключается в том, как компонент атаки порождает ледяные снаряды, и в количестве снарядов, направленных на игрока. С помощью простых настроек мы смогли повторно использовать всю систему, чтобы представить две разные атаки. Все это прекрасно работало, пока мне не пришлось начать делать атаку метели".
Для боя с Хейлгой мы хотели привнести что-то новое и решили, что ветер может быть интересным. Это очень отличалось от всего, что мы делали для Evil Wizard, поэтому мне пришлось начинать с нуля. Основная идея заключалась в том, чтобы породить ветер в случайном направлении, чтобы переместить игрока к стене, усеянной шипами, которые могут нанести урон. Лучший способ противостоять этой атаке - укрыться за укрытием, поэтому на поле боя мы высаживаем несколько сталактитов, чтобы использовать их в качестве блокираторов ветра. Если игроки окажутся за ледяным сталактитом, ветер больше не будет действовать на них.
Мы начали с создания компонента атаки, который будет отвечать за управление различными системами, которые мы используем. В системах использовались сталактиты, излучатель ветра и метель VFX. Я сосредоточусь на первых двух, а о VFX расскажет Серджио Вайсвол далее в этом блоге.
Сталактиты порождаются с помощью кругового коллайдера, чтобы получить случайные точки внутри него, а затем, используя эти позиции, создать сталактит со смещением по оси Y. Применяя coroutine, мы заставляем объекты падать, опуская ось Y - простая вещь. После того как сталактиты были спавнены, мы перешли к контроллеру ветра. Этот компонент работает как большой вентилятор прямоугольной формы, который вращается вокруг края круга.

Поскольку ветер должен дуть со случайных направлений, мы должны были направить излучатель в центр, чтобы все внутри области было затронуто. Контроллер ветра оснащен излучателем ветра, который запоминает вращение, направление и положение ветра. Когда мы вращаем компонент, эмиттер вычисляет эти значения и применяет их к ветру (который представляет собой просто направление и силу). По сути, это Vector3 и float.
Ветер также можно блокировать, как уже говорилось, с помощью ветрозащитного устройства. В этом компоненте есть BoxCollider2D, который проверяет столкновения с игроком. Если игрок окажется рядом с блокиратором ветра, OnTriggerEnter2D включит его, а когда игрок покинет коллайдер, OnTriggerExit2D отключит его. Это иллюстрирует голубая линия между игроком и эмиттером на изображении ниже. Пока линия зеленая, игрок находится под защитой.

Наконец, нам нужно было сделать так, чтобы игрок воспринимал силу ветра и двигался в его направлении. Для этого у нас есть компонент WindReceiver. Он отвечает за проверку того, насколько сильный ветер воздействует на игрока и с какого направления. Эта информация собирается путем выполнения Raycast от игрока в сторону приемника ветра. Эта информация используется для определения того, воздействует ли излучатель ветра на приемник, с какой силой и с какого направления. Получив всю информацию, мы применяем силу ветра с помощью собственного контроллера перемещения и перемещаем игрока в нужном нам направлении.
Когда Диего начал совершать первые атаки, я начал давать Хейлге повадки.
Для создания ИИ в Evil Wizard мы использовали очень полезный актив под названием Behavior Designer. Я не могу рекомендовать его. Он идеально подходит для дизайнеров игр, которые не занимаются кодом: программист может работать над механикой, а дизайнер накладывает ее на дерево поведения персонажа, заставляя его работать так, как ему нужно, без кодирования. Вы можете ознакомиться с основами здесь.
Вот дерево поведения Хейлги:

Это большое дерево, но не пугайтесь - это проще, чем кажется.
По сути, мы использовали несколько заданий в начале дерева, чтобы подготовить битву. Например, чтобы поставить босса на первую фазу, воспроизвести вступительную анимацию и сбросить некоторые переменные.

Эти задачи выполняются только в начале боя, поэтому мы добавили ретранслятор, который будет выполнять поведение босса, как вы можете видеть на следующем изображении.

Приоритетом поведения босса всегда является здоровье. С его помощью мы контролируем, должен ли босс перейти в другую фазу или даже умереть. Итак, сначала мы спросим, есть ли у босса более 75 % здоровья. Если да, то мы выполняем задания первой фазы - ледяные шипы, ледяные ракеты и призыв снежных слизней (миньонов босса). Когда здоровье босса опускается ниже 75 %, дерево переходит к следующему селектору в нижней части изображения и выполняет задания, соответствующие второй фазе, и так далее, пока босс не достигнет своей финальной фазы и персонаж не умрет при 0 здоровья.
Прежде чем перейти к следующему шагу, я хочу упомянуть о функции внешнего дерева поведения, которая представляет собой отличный способ организации больших деревьев поведения, как это было в данном случае.
Вы уже видели их на предыдущем изображении - иконки с тремя квадратиками - это деревья внешнего поведения.

Думайте о внешних деревьях поведения как о методе в вашем коде: вы вызываете метод в нескольких местах по всей логике игры, и в каждом месте он выполняет один и тот же код, но у вас он находится только в одном месте. Если вам нужно что-то изменить в этой логике, вы меняете код этого метода, и он будет меняться во всех местах, где вызывается этот метод. Здесь все работает так же. У вас есть внешнее дерево поведения, которое содержит поведение для выполнения определенного действия, например "вызвать снежных слизней".
Если я войду в наше дерево внешнего поведения, то найду следующее:

Это как мини-дерево поведения, которое проверяет, не повторяет ли босс одну и ту же атаку слишком много раз и не слишком ли много миньонов уже на поле боя, затем призывает миньонов, воспроизводит голосовые реплики Хейлги или завершает, переводя босса в режим ожидания.
Если я хочу изменить количество миньонов, которые могут находиться на поле боя одновременно, мне достаточно изменить его в задании "Check Enemy Amount of Type" внутри этого внешнего дерева поведения, и это будет работать для каждой части дерева, которая используется для вызова миньонов.
При создании Хайльги наш ведущий художник Рубен Гомес начал с отсылки к существующим персонажам в надежде, что игроки их узнают.
В данном случае мы использовали Джайну из Warcraft (как уже говорилось) и Эльзу из Frozen в качестве референсов для персонажей.
Вот что говорит Рубен:
"Принимая во внимание все предпосылки, я начал с дизайна Hailga, - говорит Рубен. "Мы взяли некоторые характеристики из одежды и причесок каждого эталона, элементы, которые могут быть легко узнаваемы в очень маленьком пиксельном спрайте, и заполнили остальное воображением (никакого ИИ, только человеческое воображение, то самое, которое я использовал в детстве)".

Во время анимации нам нужно было, чтобы движения происходили быстро, не теряя плавности между кадрами, поэтому мы старались выиграть время за счет более быстрых итераций для каждого действия персонажа. Мы всегда начинаем с финальной версии, обрезая, переставляя, масштабируя и поворачивая каждую часть персонажа для каждого кадра анимации с помощью ближайшей интерполяции с целью повторного использования некоторых элементов и избежания сглаживания.
После этого мы провели быстрый тест движений и настроили их таким же образом.

Когда анимационный проект был готов, мы доработали каждый элемент, заполнили пустые места и добавили детали.
Окончательный вариант каждого кадра вы можете увидеть на следующем изображении.

Пока анимация была в процессе, я занялся тестированием и балансировкой боя босса. Для этого мы создали сцену под названием "Боевая зона", в которой тестировали боссов, врагов, заклинания и многое другое. По сути, это небольшая область, в которой есть все инструменты и функции, необходимые нам для тестирования, не касаясь реальной игры.
Так выглядел ранний босс Хейлга, когда мы тестировали персонажа в сцене Combat Zone:


Наконец, когда мы были довольны ее поведением, функциональностью и сложностью, мы интегрировали Хейлгу на ее место в реальной игре.

Когда мы закончили работу над поведением и анимацией Хайльги, мы начали интегрировать анимацию босса, используя AnimatorImporterкоторый является отличным инструментом для интеграции пиксель-арт анимации, сделанной в Aseprite. С ним всего за несколько шагов вы сделаете все, что нужно.
Теперь настало время придать боссу изюминку, и тут в дело вступил наш VFX-художник Теки. Забирай, Теки:
Эй! Серджио Вайсвол (он же Теки), программист и VFX-художник в Evil Wizard.
VFX Хейлга был действительно сложным, и не только потому, что это был первый босс команды, но и потому, что это была одна из моих первых задач в игре. Во время боя с боссом используется несколько VFX-последовательностей - по крайней мере, по одной на каждую атаку, но я остановлюсь лишь на нескольких.
Один из пиковых моментов битвы - последний фазовый переход, когда Хейлга, разозлившись, бросает ледяной луч в сторону злого волшебника, в результате чего поле боя застывает.

Я считаю, что это очень хороший эффект, который можно разложить на части и показать, из каких кусочков состоит волшебство. Для этого мы разделили переход на два VFX-фрагмента: ледяной луч и замораживание пола.
Для первой части моя работа начиналась, как только Диего заканчивал программировать атаку, и обычно сопровождалась красивой заготовкой (в данном случае - вытянутым прямоугольником) - плюс он желал мне удачи. И тут я с головой окунулся в работу, используя те инструменты, которые, как я знал, уже были в моем арсенале. Но поскольку это было начало разработки, у меня их было мало. В прошлом я использовал компонент Line Renderer из Unity для рендеринга линии между любыми двумя точками, управляемой с помощью скрипта, поэтому я начал с него, а затем объединил его с базовым шейдером, чтобы добавить цвет к краям и центру линии.

Не совсем, как вы можете видеть выше. В VFX я быстро понял, что (вопреки классической философии) "целое - это сумма частей". Я имею в виду, что для создания желаемого эффекта может потребоваться несколько систем, а не один шейдер или одна частица.
Далее мне нужно было, чтобы эффект больше походил на луч (и был менее массивным), поэтому я поиграл с Shader Graph, чтобы добиться этого. Я постараюсь объяснить это вкратце.
Чтобы добиться желаемого, шейдер должен состоять из трех основных частей. Во-первых, я использовал ранее нарисованную текстуру, которая представляла собой просто горизонтальный зеркальный градиент (вы можете увидеть ее на следующем изображении под именем MainTex). Используя узел "Pow", я могу легко управлять шириной луча (текстура умножает себя и, поскольку это градиент, уменьшает луч).

Затем я использовал анимацию Simple Noise с узлом Tiling And Offset для двух целей:
Растворить луч в определенных частях (всегда контролируется публичной переменной)
Для изменения ультрафиолетового цвета луча, чтобы он выглядел искаженным

Продолжая работу, я умножил полученный луч на цвет HDR, чтобы контролировать излучаемое свечение. Наконец, я взял края луча с помощью узла step и умножил их, чтобы получить другой цвет, оставив центр луча выделяться.
Когда я закончил, у меня было что-то вроде этого, чтобы поиграть с разными цветами и переменными.

В этот момент мне стало казаться, что у меня что-то получается, но я заметил, что мне чего-то не хватает: луч был брошен неизвестно откуда.
Поэтому с помощью частиц я создал ледяную сферу, чтобы она могла стать точкой литья для луча:

Наконец, нам нужны частицы вдоль луча, чтобы интегрировать его с остальной частью области.

Вторая часть короче, но сложнее. Чтобы завершить основной эффект, нам нужно было иметь возможность переливаться между двумя текстурами - незамерзшим и замерзшим полом - но под радиальным углом, следуя траектории и скорости луча.
Для согласования скорости мы использовали регулярный скриптинг для управления скоростью движения балки по переменной от 0 до 1, т.е. насколько сильно должен быть заморожен пол в зависимости от текущего вращения балки. Для лессировки текстур нам понадобился короткий шейдер. Посмотрите, как мы разделили шейдер на две части, чтобы получить радиальный градиент и фактический лерпинг.

Ниже показана формула, применяемая для расчета радиального градиента, учитывающая положение UV и идущая точно от 0 до 1. Это синхронизирует движение луча с замороженным значением.

Вот как это выглядит на месте преступления:

Чтобы завершить сцену, мы сложили все вместе и получили крутой эффект, служащий как переходом к финальной фазе, так и одной из атак этого босса.
После того как все было готово, композитор игры Хаакон Давидсен добавил последний штрих - умопомрачительную музыку, которая заставляет игроков почувствовать "накал" битвы. Вы можете послушать его здесь. И, конечно, актриса озвучивания - Бреанна Макдауолл - проделала потрясающую работу, подарив голос Хейлге.
Мы надеемся, что вам понравился этот блог!
Испытайте на себе бой босса Хейлга, заглянув в игру Evil Wizard на Steam или Xbox, и следите за выходом игры на других платформах. Читайте другие истории Made with Unity прямо от разработчиков здесь.
