Архитектура оружия на примере Erra: Exordium

Здравствуйте жители DTF

На связи Максим, программист из Erra: Exordium. В этой статье я расскажу вам про архитектуру оружия в нашей игре. Разберу из чего состоят механизмы управления холодного и огнестрельного оружия. Как устроено взаимодействие главного героя с оружием и прочая дичь… Данная статья имеет скорее обучающий контекст, поэтому решили разместить её в разделе gamedev. Надеюсь, что информация будет полезна не только программистам.

Главный герой и анимации

Прежде чем погружаться в механизмы оружия, хочу немного рассказать из чего состоит герой. В одной из статей, я как-то уже рассказывал, что однажды мы решили отказаться от стандартного аниматора и написать свой.

Вся анимация, что есть в игре, тем более главный герой - это набор отрисованных кадров. У героя есть десятки состояний, каждое из которых может содержать несколько анимаций. Есть промежуточные анимации. И всё это “ручная прорисовка пикселей”.

Поэтому у нас своя система управления анимациями, с которыми взаимодействуют состояния героя.

Когда герой входит в состояния атаки холодным оружием, то запускаются разные анимации атаки. Где в каждом кадре, герой отрисован в определенной позиции с оружием.

С огнестрельным оружием немного сложнее. Герой может находится в определенном состоянии, а огнестрельное оружие в своем. Например, герой находится в движении или стоит, а оружие может перезаряжаться или стрелять. Именно из-за этого в аниматоре пришлось создать обработку слоев.

Для тех состояний, где герой может использовать огнестрельное оружие, анимация героя представлена без рук. Огнестрельное оружие отрисовано вместе с руками героя в разных позициях прицеливания.

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

Сотни параметров

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

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

Кроме очевидных параметров, мы храним еще те, которые связаны с анимацией или существуют по причине подхода к покадровой анимации и отсутствие аниматора из коробки.

Приведу пример для огнестрельного оружия выстрел и перезарядка. Герой стреляет находясь в состоянии движения. Проигрывается анимация ходьбы. Оружие считывает настройки для анимации выстрела (спрайты кадров и межкадровый интервал) и запускается анимация. Во время проигрывания анимации выстрела, в момент переключения кадров, происходит проверка номера текущего кадра анимации с параметром в настройках оружия, который хранит кадр выстрела. Если кадр анимации подходящий, то оружие должно выстрелить.

Аналогично с перезарядкой. Разница лишь в том, что во время выстрела, игрок должен прицеливаться. А для перезарядки, этого делать не обязательно. Но о самом прицеливании поговорим чуть позже.

Во время выстрела, оружие должно знать точку выхода снаряда. А во время прицеливания - длину ствола, чтобы герой упирался оружием в препятствия или сдвигался в противоположную сторону, если уже находится впритык к объекту. Для этого мы храним для каждого кадра прицеливания набор данных. Т.е. к сотни параметрам, можно еще смело добавить сотню параметров, связанных с анимацией.

Сам снаряд или Projectile может быть описан десятками параметров, это одна из важных частей архитектуры, вершина горы, поэтому ниже рассмотрим более подробно.

В зависимости от огнестрельного оружия, после выстрела или перед, из оружия вылетает гильза. Для этого мы храним позицию, задержку или номер кадра анимации, номер эффекта и звуковую информацию.

У холодного оружия есть тоже персональный список параметров, который не используется в огнестрельном. Он появился не сразу и менялся не раз. Во время отработки удара для каждого кадра могут проверяться с десяток параметров. Во время удара герой должен двигаться вперед или назад, создавать “повреждающие зоны”, реагировать на попадания и тоже двигаться…

Архитектура оружия на примере Erra: Exordium
Архитектура оружия на примере Erra: Exordium

Параметры снаряда (Projectile)

И холодное и огнестрельное оружие разделяют одну структуру настроек. Удар холодным оружием, создает вызовы, которые хранят часть настроек Projectile.

Вначале было два типа Projectile: милиха и пуля. На сегодня количество типов дошло до 20. Например: ракета, самонаводящаяся ракета, лазер, электрическое поле, кислотный дым, граната…

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

Префаб может отсутствовать. Сами же оружия могут иметь настройки одного типа Projectile, но с разными префабами. Например враги ракетчики могут стрелять визуально разными ракетами.

Архитектура оружия на примере Erra: Exordium
Архитектура оружия на примере Erra: Exordium

Менеджер снарядов (Projectile Manager)

Задача Projectile Manager управлять пулом снарядов.

В одной из статей, мы рассказывали как у нас устроена архитектура интерактивных объектов.

В нашем проекте мы используем правило - чем меньше объектов MonoBehaviour с методами Start / Update на сцене, тем лучше. Да, на сцене полно разных “заскриптованых” объектов, но MonoBehaviour-методы Start и/или Update (и прочая ересь) есть только у одного короля!

На сцене кроме героя, могут присутствовать различные объекты, у которых может быть оружие (противники, турели и другое). Когда происходит инициализация такого объект, то он инициализирует оружие, которое в свою очередь дает команду Projectile Manager создать пул снарядов определенного типа.

Во время выстрела, оружие достает из пула Projectile Manager снаряд. Затем задает снаряду разные параметры (позиция, направление, слои и т.п.) и активирует его. После этого, снаряд может жить самостоятельно, обновляясь с помощью Projectile Manager.

Архитектура оружия на примере Erra: Exordium

Контроллер управления огнестрельным оружием героя (Firearm Controller)

Как было сказано выше, герой во время использования огнестрельного оружия, показывает две анимации: тело без рук и руки с оружием.

У оружия есть такие состояния как: pullout, aiming, attack, reload, load и hide. На эти состояния существуют соответствующие анимации.

Когда герой находит оружие, то контроллер подготавливает для состояний оружия - анимации. Кадры этих анимаций хранятся в настройках оружия.

Контроллер, при наличии огнестрельного оружия, всегда следит за двумя вещами: прицеливание и перезарядка.

Игрок может запустить перезарядку без прицеливания. И здесь должны сработать последовательно состояния pullout и reload. Во время перезарядки герой может получить повреждение или прервать другим состоянием (прыжок, подкат, блок). Поэтому на стороне оружия мы храним параметр кадра, после которого добавляется (защитывается) обойма или пуля.

Стрельба может начаться в трех состояниях героя: idle, run и walk. Когда контроллер оружия ловит команду прицела, а герой находится в состоянии бега (run), то состояние героя переключается на ходьбу. Контроллер оружия включает слой анимации героя, на котором отображено оружие с руками, а состояние героя (idle или walk) переходит на анимацию тела без рук.

Во время стрельбы, контроллер оружия запускает алгоритм отдачи (Recoil). На стороне оружия хранятся параметры для отдачи: длительность, задержка, количество кадров и завершающая задержка (при необходимости). По сути Recoil управляет или назначает кадр анимации прицеливания контроллеру оружия.

Архитектура оружия на примере Erra: Exordium

Прицеливание

Параметры прицела хранятся в каждом оружии и их тоже не мало.

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

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

У прицела так же есть состояния: обычное, рождение, перезарядка и завершение. Задача состояний прицела - влиять на расстояние элементов прицела и альфу цвета.

Когда Firearm Controller получает данные от устройства ввода, он вызывает метод Start у Aim Controller. Aim Controller подготавливает данные и объекты прицела, ну и запускает состояние рождения. Состояние рождения прицела сводит элементы прицела к центру…

Параллельно этому, могут меняться данные от устройства ввода, поэтому Aim Controller обновляет направление и позицию прицела.

Во время обновления, Aim Controller рассчитывает в том числе “кадр прицела”. Этот кадр зависит от направления прицела, нижней и верхней границы одного сектора. Границы сектор вычисляются один раз на основе высоты сектора. А Firearm Controller считывает эти данные для оружия и анимации.

Архитектура оружия на примере Erra: Exordium

Выстрел, Bullet и эффекты

Рассмотрим всю цепь процесса “выстрел” на примере пистолета (Pistol Weapon) и пули (Bullet).

Pistol Weapon унаследован от Firearm, Firearm от Weapon. Hero создает и обновляет Firearm Controller. Firearm Controller создает нужные анимации, следит за Firearm, Aim Controller и Recoil.

Pistol Weapon в момент создания, обращается к Projectile Manager, чтобы тот создал пул пуль (Bullet).

Архитектура оружия на примере Erra: Exordium

Firearm Controller отслеживает данные устройства ввода и прицела. Когда есть прицеливание и появляется команда выстрела, то активируется Firearm, в данном случае Pistol Weapon.

Firearm обращается к Projectile Manager, получает ссылку на свободный и нужный Projectile. Получив снаряд, Firearm настраивает его и запускает (включает). В зависимости от вида оружия, могут запускаться и другие обработки, такие как выброс гильзы, генерация импульса тряски камеры и прочее.

Bullet Projectile включается!

Заполняется данными объект Hit. Он хранит различные свойства на этапе включения и попадания снаряда. Затем ссылка или клон Hit может передаваться другим сущностям.

Вначале Bullet проверяет через Physics2D.OverlapCircleNonAlloc рождён ли снаряд в месте, где уже присутствуют объекты на дистанции в 1 пиксель. Если есть куда лететь, то запускается проверка Physics2D.RaycastNonAlloc.

Архитектура оружия на примере Erra: Exordium

Оба случая ищут у объектов наличие интерфейса повреждения (IDamage), а у объектов с тегами препятствий - компонент Surface.

Опять же все эти проверки могут вернуть свойство Through, что означает - проходит сквозь этот объект.

Любой из снарядов (Projectile) может обратится к Surface Hit. Это статический класс, задача которого обрабатывать попадания снарядов в различные поверхности, вызывая звуки, эффекты, а иногда даже воздействовать на объект, если в его родителе реализован интерфейс повреждения (IDamage).

Для каждого типа Projectile, в словарях эффектов и звуков, может быть создана связь взаимодействия этого типа снаряда с поверхностью.

Когда пуля обратиться к Surface Hit и передаст данные своего Hit, то Surface Hit найдет нужный эффект в словарях и передаст менеджеру эффектов (Effects Manager), чтобы тот запустил нужный эффект из пула.

Архитектура оружия на примере Erra: Exordium

Также пуля может обратиться к менеджеру эффектов (Effects Manager) за включением таких эффектов как вспышка выстрела, трассировочный след и прочее.

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

Герой и холодное оружие (Melee Controller)

Реализация холодного оружия на стороне героя отличается от огнестрельного. Огнестрельное оружие работает параллельно с состояниями героя idle и walk. А холодное - связано конкретно с состоянием героя Melee State.

Герой создает контролер холодного оружия (Melee Controller), как и в случае с огнестрельным оружием. Но задач у Melee Controller намного меньше.

Архитектура оружия на примере Erra: Exordium

Контроллер подготавливает анимации оружия, связывает с вызовами состояния Melee State, настраивает обработку звуков и хранит ссылку на конкретный экземпляр оружия (Weapon). И в случае необходимости, Melee Controller запускает состояние героя Melee State.

Melee State или сама “милиха” варьирует состояниями удара: подготовка, обычный удар, подготовка к сильному удару и сильный удар. На каждое такое состояние оружие хранит кадры для анимаций. А Melee Controller собирает эти анимации.

На переключение состояния удара влияют множество условий: изменения команд устройства ввода, кадр анимации, стамина…

Например, удар начинается с состояния “подготовка”. Включается соответствующая анимация. Когда анимация заканчивается, вызывается проверка события устройства ввода. И тут может быть переход в состояние “обычный удар” или “подготовка к сильному”.

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

Архитектура оружия на примере Erra: Exordium

Я специально привожу пример части настроек “милихи”, чтобы вы понимали и почувствовали всю боль человека, который потом с этим работал и настраивал.

На этом пожалуй я закончу. Итак получилась, как по мне, слишком большая паста. Надеюсь, что статья была хоть немного полезной и интересной для вас.

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

Следите за новостями, поддерживайте инди, слушайтесь родителей и будьте здоровы.

~ Команда Fair Pixel

97
15 комментариев

Комментарий недоступен

3
Ответить

Хотел спросить, а используете ли вы scriptable objects или библиотеки типа Unity Atoms в своем проекте?

1
Ответить

Используем scriptable objects. Его нам хватает вполне

3
Ответить

что то мне кажется очень много настроек, которые можно было бы как то распределить по категориям, возможно если оружия планируется не так много, то еще норм, но вот когда будет очень много разного оружия, а потом и разные модификации к нему или разные типы патронов(разрывные/скоростные/бронебойные), то будет много одинаковых конфигов с отличием в пару параметров, но это я в плане расширяемости игры, возможно вам это и не нужно совсем

1
Ответить

Да, распределять настройки, дробить их по группам, и прочее, с этим можно согласится. Об этом я писал вначале, что мы используем одну структуру для милихи и огнестрела. Когда вы планируете поведение оружия заранее, то можно (нужно) потратить время и на организацию настроек. У нас функционал оружия расширялся на протяжении всего проекта. И вот кстати, самое последнее, что появилось в структуре настроек по снарядам - это Extra Projectile, на картинке параметров он как раз открыт...

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

2
Ответить

Проект выглядит интересно! Статья тоже! Статей по архитектуре в играх мало. Пишите еще!)

Из демки, правда, не понятно, можно ли поднять оружие выше, например, вертикально вверх? И так ли свободный поворот оружия нужен, если враги только впереди. Планируется ли bullethell и харкорный мультиплеер?

1
Ответить

Erra: Exordium однопользовательская игра. Мудьтиплеер мы не рассматривали в принципе.
Оружие имеет лимиты вращения. На 90 градусов не поднимается. Мы решили ввести эти правила в игру очень давно. Но для самой архитектуры - это не имеет значение. Враги могут быть с разных сторон, поэтому свободный поворот оружия присутствует.
Что касается каких-либо режимов интенсивного отстрела врагов, то несколько будет, например костюмы

https://twitter.com/ErraTheGame/status/1377269873441705986

2
Ответить