paint-brush
Более сложная система способностей, но более плавный рабочий процесс разработки игр – как?к@pastaman
1,150 чтения
1,150 чтения

Более сложная система способностей, но более плавный рабочий процесс разработки игр – как?

к Vladimir Popov9m2024/02/08
Read on Terminal Reader

Слишком долго; Читать

Система способностей War Robots претерпела значительную эволюцию, перейдя от упрощенной структуры к сложной модели. Новая система расширяет возможности гейм-дизайнеров и снижает зависимость от программистов, что приводит к более разнообразным возможностям механизмов и обогащению игрового опыта.
featured image - Более сложная система способностей, но более плавный рабочий процесс разработки игр – как?
Vladimir Popov HackerNoon profile picture


Привет! Я Владимир Попов, клиентский разработчик проекта War Robots. На момент написания War Robots существует уже несколько лет, и за это время в игре появились десятки новых мехов. Естественно, важны разнообразные способности роботов, ведь без них роботы теряют свою уникальность, что делает игру более интересной.


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


Как раньше реализовывались способности

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


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


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




Давайте рассмотрим на примере способности «Гаммер», которой ранее обладал робот «Сталкер»; это работало следующим образом:


  1. Если способность активна, отображается анимация и робот переходит в состояние «Джаммер»; в этом состоянии другие не могут целиться в робота, активирующего эту способность.
  2. Если способность неактивна, ничего не происходит.
  3. При попытке активировать способность мы проверяли, прошло ли с момента последней активации более n секунд.
  4. Деактивация автоматически произошла через m секунд.


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


Введите новую систему способностей

Итак, мы поняли, что нужно что-то менять. Поэтому мы разработали новую систему, где каждая способность была представлена как набор нескольких связанных объектов. Функциональность была разделена на возможности состояния и компоненты состояния.


Как это работает? У каждой способности есть главная цель. Этот центральный объект соединяет другие объекты способностей с внешним миром, и наоборот; он также принимает все основные решения.


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


Главным нововведением по сравнению со старой системой стали компоненты: компонент описывает какое-то действие, а каждое состояние может иметь любое количество компонентов.


Как работают новые способности? Способность может находиться только в одном из состояний; основной объект отвечает за их переключение. Компоненты, привязывающиеся к состоянию, реагируют на активацию/деактивацию состояния и в зависимости от этого могут либо начать выполнять какое-то действие, либо прекратить его выполнять.


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


Поток стал таким:

  1. Главный объект активирует первое состояние
  2. Государство активизирует все свои компоненты
  3. Состояние определяет момент переключения способности в другое состояние.
  4. Основной объект деактивирует предыдущее состояние
  5. Предыдущее состояние деактивирует его компоненты
  6. Главный объект активирует новое состояние
  7. Новое состояние активирует свои компоненты


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




Основной объект, состояние и компоненты остались на своих местах, но добавились и новые элементы.


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


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


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


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


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


Подведем итоги составляющих наших способностей:


  • Основной объект выполняет функции конечного автомата. Он предоставляет состояниям и компонентам информацию о мире и предоставляет миру информацию о способностях. Основной объект служит связующим звеном между состояниями, компонентами и служебными частями способности: зарядами и внешними таймерами.


  • Состояние слушает команды активации и деактивации от основного объекта и соответственно активирует и деактивирует компоненты, а также запрашивает у основного объекта переход в другое состояние. Состояние определяет, когда нужно переключиться на следующий; для этого он использует свое внутреннее состояние: нажал ли игрок на кнопку способности, прошло ли определенное время с момента активации состояния и так далее, а также внешние условия, привязанные к состоянию.


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


  • Условие проверяет, в каком состоянии находится нужный элемент, и сообщает об этом состоянию или компоненту. Условия могут быть сложными. Состояние не запрашивает переход в другое состояние, если условие не выполнено. Компонент также не выполняет действия, если условие не выполнено. Условия являются необязательным объектом; не у каждой способности они есть.


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


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


Теперь вернемся к диаграмме способностей. Как изменился его функционал?


  1. В начале игры главный объект выбирает первое состояние и активирует его.
  2. Государство активизирует все свои компоненты
  3. Компонент проверяет, выполнено ли условие, и только после этого выполняет действие.
  4. Состояние начинает проверять условие перехода в другое состояние
  5. Если условие выполнено и связанное с ним дополнительное условие удовлетворено, состояние запрашивает у основного объекта переход в другое состояние.
  6. Главный объект деактивирует это состояние и активирует другое.
  7. Вся процедура повторяется


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


Интерфейсы способностей

Мы не стали полностью изобретать велосипед для новых интерфейсов способностей.


Главный объект имеет собственный пользовательский интерфейс. Он определяет некоторые элементы, которые всегда должны быть в пользовательском интерфейсе и не зависят от текущего активного состояния.


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


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





Примеры способностей

Мы рассмотрим, как работают способности, на конкретных примерах; сначала давайте посмотрим на робота по имени «Инквизитор». У нас есть четыре состояния, которые следуют друг за другом — над состояниями вы можете увидеть их отображение в пользовательском интерфейсе. Для двух из них мы также видим принадлежащие им компоненты; два других состояния просто не имеют компонентов.


Вот поток способности:

  1. Все начинается с состояния «WaitForClick». В это время способность ничего не делает; он просто ждет команд.


  2. Как только такая команда получена, основной объект переключает состояние. Следующее активное состояние — «WaitForGrounded».


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


  4. Когда робот приземляется, его способность переходит в следующее состояние.


  5. Это состояние имеет три компонента: уже знакомые Sound и Jammer, а также Shake, вызывающий тряску камеры у всех игроков в радиусе n .


  6. Поскольку у этого состояния есть Duration , оно работает n секунд, затем способность переходит в следующее состояние.


  7. Последнее состояние также имеет продолжительность, но у него нет каких-либо компонентов: оно имеет обычное время восстановления.


  8. По завершении способность возвращается в первое состояние.





Другой пример – «Фантом». Очень похоже на Инквизитора, но есть некоторые нюансы:


  1. Начнем с WaitForClick.


  2. Затем Длительность, в которой устанавливается телепорт, изменяются характеристики меха, воспроизводится звук и анимация.


  3. После этого: DurationOrClick, в котором изменяются характеристики меха, воспроизводится анимация и эффекты.


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


  5. После этого состояния (или по истечении времени для DurationOrClick) мы переходим в Duration.


Основное отличие здесь в том, что мы видим состояния с ветвлением: DurationOrClick переходит в состояние A, если заданное время прошло, или в состояние B, если игрок ранее нажимал кнопку способности.



Выводы

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