2 of October - MS Stage Free Online conference: .NET, MS SQL, MS Azure, Cosmos DB. REGISTER
×Закрыть

Что такое Gameplay Ability System и как написать свою абилку

Привет, меня зовут Степан, я работаю в Wargaming. Вместе с командой я тружусь над проектом на Unreal Engine и активно интересуюсь развитием этого движка, его возможностями и инструментами.

На эту статью меня вдохновило выступление по Ability System Мэтта Эдмондса на Unreal Fest Europe в Праге. Мы тогда только начинали с этой системой работать, читали документацию, смотрели на пометку «Experimental» в списке плагинов. Это вызывало вопросы: насколько эта система мощная, подходит ли она для быстрого прототипирования, и достаточно ли она стабильна и легка в поддержке для продакшн-решения? Спойлер: уже больше года как мы с командой пользуемся абилити-системой в нашем проекте, и планируем продолжать пользоваться. В этом материале я поделюсь своими знаниями по теме, расскажу то, что мне удалось выяснить и, надеюсь, смогу ответить на те вопросы, которые наверняка возникают у вас.

Что же такое Gameplay Ability System

В первую очередь, это фреймворк для разработки игровой логики программистами и гейм-дизайнерами. Тут я намерено не употребил слово «абилка», потому что, когда мы говорим про абилки, мы сразу представляем себе игры жанров MOBA или MMORPG — сложные «ульты» с хитрыми механиками, с кучей зависимости от разных параметров. Что-то, что можно увидеть в играх вроде DOTA или Diablo. С помощью абилити-системы все эти вещи сделать, безусловно, можно — и это, с технической точки зрения, лишь часть функционала систему. Gameplay Ability System понимает абилку гораздо шире — для нее это в принципе любое геймплейное действие, включая, например, перезарядку или стрельбу. В частности, абилити-система была сделана Epic Games для Paragon, а позже на ней же был сделан и Fortnite: стрельба, перезарядки, строительство — все сделано на абилити-системе.

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

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

Как работать с Ability System: C++ или blueprints

Это распространенная дилемма для всех, кто работает с Unreal Engine — где стоит помещать логику, на уровне кода, или уже в редакторе, в блюпринтах. В сообществе принято писать основу в C++, выделять отдельные функции как blueprint-ноды, которые доступны в скриптах, и использовать редактор для настроек и примитивных скриптов.

Ability System очень хорошо ложится как раз на такой подход. Настроить Ability System Component и атрибуты и добавить их к персонажу можно только на уровне C++. Даже если есть план писать всю логику абилок в блюпринтах, основу придётся заложить в коде.

При этом Ability System обладает на уровне скриптов достаточно обширным, пусть и не полным функционалом. После базовой инициализации компонентов в C++ и описания атрибутов, разработку можно полностью продолжать в редакторе. Такой подход больше подойдёт прототипу, и вряд ли подойдёт production-решению — сложной архитектуры поверх того, что система сама предлагает и прокидывает в блюпринты, написать не получится.

Какие решения предлагает Ability System

Чтобы подробнее разобраться с тем, что из себя представляет абилити-система, разберем ее основные составляющие:

  • Ability System Component — это компонент, который прописывается какому-то actor, и управляет всеми характеристиками внутри его. То есть все абилки, которые у вас активируются, все эффекты, которые на вас накладываются, атрибуты, которые у вас есть, — все регулируется именно этим компонентом. Но если вам знакома архитектура ECS — пусть слово «System» в названии не сбивает вас с толку. Это обычный Actor Component, и руководит он всем только внутри своего актора-владельца. У каждой сущности в игре, которая пользуется абилками, Ability System Component должен быть свой.
  • Gameplay Ability — это сама абилка: набор скриптов игровой логики, описание геймплейно-значимых действий. Основа для нее создается в C++ — там мы можем создать наследника от этого базового класса, сделать какие-то методы для себя, какие-то ноды для блюпринтов, какие-то building-блоки, из которых потом финально и составим абилку. Или — ничего не мешает нам полностью реализовать логику на уровне C++ и вынести параметры наружу. Но создать в итоге blueprint-ассет вам всё равно понадобится — именно там удобнее всего настроить теги абилки и указать ссылки на используемые эффекты (о том и о другом будет рассказано далее). Как уже упоминалось ранее, абилкой может быть любое геймплейное действие — включая, например, стрельбу, прыжки или перезарядку.
  • С помощью Gameplay Effect осуществляется взаимодействие между различными элементами, в том числе между разными акторами. Gameplay Effect — это дата-ассет, набор данных, которые определяют, что же на самом деле произойдет: могут поменяться атрибуты или может измениться набор тегов — Gameplay Tags. В терминах Ability System эффектами являются как банальные бафы/дебафы, так и нанесение урона, кулдауны абилок, или цены использования абилок — например, если абилка тратит энергию или патроны. По времени действия эффекты можно разделить на моментальные (выполняются одномоментно и оставляют после себя все внесённые изменения), временные (активны некоторое время, а затем пропадают вместе со всеми внесёнными изменениями) и постоянные (работают так же, как временные, но не пропадают самостоятельно). Так же из коробки присутствует механика стеков (stack) — суммирования одинаковых наложенных эфектов, с возможности настройки максимального количества стеков и условий, при которых они «складываются». И наконец, кроме всех перечисленных возможностей, типичных для игр такого жанра как RPG или MOBA, есть ещё одна интересная механика — эффекты могут давать персонажу «абилки». Это позволяет реализовывать довольно хитрую логику, поскольку с помощью эффектов можно наделить актора поведением, которым он раньше не обладал.
  • В любой RPG есть огромное количество параметров, которые друг от друга зависят и на что-то влияют — на урон, здоровье и так далее. Attribute Set — это все параметры актора, которые как-то связаны с игровой логикой и с абилити-системой. Attribute Set пишется только в C++, и актор может владеть множеством разных Attribute Set’ов. Таким образом, атрибуты можно разбивать на категории. К примеру, один Attribute Set описывает параметры, необходимые для «живучести» персонажа (такие как здоровье, броня, и т.д.), другой — параметры передвижения, и любое количество других наборов атрибутов, необходимых вашей конкретной игре.

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

Важный момент: Если в вашей игре есть сторонние сущности, отвечающие за те или иные аспекты поведения персонажа, и вы хотите, чтобы абилки и эффекты могли влиять на их работу — вам придётся завести атрибуты для тех их параметров, которые вы захотите эффектами менять. Наиболее распространённым примером являются Movement Component’ы, в частности, Character Movement Component. В нём реализована вся логика передвижения, указаны скорости, гравитация, параметры управления в воздухе. И если вам потребуется эффект, замедляющий персонажа — придётся сделать атрибут, к примеру, MaxWalkSpeed — и при изменении этого атрибута выставлять Character Movement Component’у значение соответствующего поля. Чисто технически, мы можем написать абилку, которая будет делать то же самое напрямую, но это лишает нас гибкости и создаёт множество спорных ситуаций, когда персонаж может быть одновременно чем-то замедлен и чем-то ускорен — а это именно то, для чего лучше всего подходят эффекты и атрибуты.

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

Меняются атрибуты с помощью эффектов, и только с помощью них. Есть техническая возможность изменять их напрямую из C++ кода, но делать это не рекомендуется. Делать это можно только в рамках уже наложенных эффектов — если нужно провести дополнительные вычисления.

Хорошим примером будет следующая ситуация — допустим, в вашей игре несколько полосок «здоровья» — например, само здоровье, броня и энергетический щит — и игровая логика подразумевает распределение урона между ними в каком-то процентном соотношении. Для подобных вычислений эффекту, накладывающему эффект урона, можно назначить калькулятор — класс-наследник от UGameplayEffectExecutionCalculation. В момент наложения эффекта этот калькулятор будет использован для того, чтобы на основе входных данных, заданных в самом эффекте, сформировать набор изменений атрибутов. Кроме того, в самом Attribute Set’е есть методы PostGameplayEffectExecute и PreAttributeChange, в которых можно обрезать значения до максимальных/минимальных или как-то иначе обработать входящие изменения (срабатывает это уже после того, как калькулятор сформировал список изменений).

Gameplay Tags нужны для того, чтобы отмечать эффекты и абилки тэгами и настраивать между ними взаимодействия. Они являются идентификаторами для ваших эффектов — если у вас есть cooldown-эффект с каким-то тэгом, вы по этому тэгу можете получить оставшуюся длительность этого эффекта, чтобы показать в UI, когда можно будет применить эту абилку снова. В эффектах тэгами можно настраивать иммунитет к эффектам с другим тэгом и взаимодействие с другими абилками.

Например, у вас есть какая-то крутая способность, ульта — персонаж крутится и наносит всем вокруг урон. И гейм дизайнеры приходят к вам с просьбой: нужно сделать контр-способность, которая могла бы ее блокировать. Для этого в первой способности вы прописываете тэг, по которому она будет блокироваться. А в эффекте, которым вы ее блокируете, вы пишете, что этот эффект присваивает этот тег.. По сути, вам не нужно будет даже писать скрипт и реализовывать эту логику, вы просто накладываете этот эффект на персонажа, и он не может активировать абилку. Если потребуется ещё и прервать активную абилку — можно воспользоваться свойством эффекта «давать» абилки. Скрипт прерывания можно оформить отдельной абилкой, и заставить блокирующий эффект её «выдать» и активировать. Главное — в коде самой абилки прописать, как она прерывается.

Gameplay Cue отвечает за визуализацию эффектов. Это могут быть particle-эффекты, звуки, которые воспроизводятся, взаимодействие с UI или с камерой. Например, у вас трясется камера, когда вы стреляете. К слову, в Unreal есть стандартный Camera Shake Modifier, который вы можете вызвать внутри своей Cue.
Как правило, Cue связаны с конкретным эффектом и вызываются в тот момент, когда он накладывается. Но есть так же Gameplay Cue Manager, с помощью которого можно вызывать Cue самостоятельно. Это может понадобится, например, чтобы снаряд при попадании высекал искры из стен или оставлял на них следы — на объекты, не обладающие Ability System Component, эффекты, а как следствие, и связанные с ними Gameplay Cue, накладываться не будут.

Cue могут быть двух типов — статические (вызывают какую-то логику или спавнят другие объекты, не являясь при этом объектом сцены) или акторы (являются актором и могут содержать в себе любые компоненты). Cue второго типа «цепляются» к владельцу эффекта, и могут реализовывать долгосрочные эффекты, которые должны перемещаться вместе с ним — например, визуализацию ауры.

Gameplay Event — это сообщение, которое вы отправляете актору. Оно обладает тэгом и контейнером для параметров, которые вы можете задать самостоятельно. На Gameplay Event можно подвязывать различную логику, по ним же можно активировать способности. Внутри абилки можно вызвать специальную ноду Wait For Gameplay Event. Например, вы кидаете шарик и вам нужно подождать, пока он куда-нибудь врежется. С помощью этой ноды можно дождаться, когда произойдет этот Gameplay Event, чтобы создать необходимую логику.

Gameplay Tasks — это асинхронное действие, которое происходит внутри абилки. Если в скрипте абилки вам понадобится какая-то асинхронная логика, вы ее оборачиваете в gameplay task в С++ и вызываете в блюпринте. В предыдущем пункте я говорил про Wait For Gameplay Event — это как раз одна из стандартных Gameplay Task. Ещё один пример стандартной Gameplay Task — Play Montage And Wait — запускает анимационный монтаж и ждёт его окончания

Как написать самую простую абилку

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

В первую очередь, необходимо задать персонажу Ability System и Attribute set в С++ классе персонажа. В этом примере мы сохраняем ссылку на Attribute Set, чтобы иметь возможность подписаться на события в нём, если мы сами их определим. Если есть необходимость инкапсулировать Attribute Set, в Ability System Component есть метод для добавления нового Attribute Set по классу, а также методы для получения делегатов, срабатывающих при обновлении значений атрибутов. Стоит отметить, однако, что делегаты эти срабатывают только на сервере, поэтому для обновления параметров в UI они не подойдут.

MyCharacter.h

class MYGAME_API AMyCharacter
: public ACharacter
 , public IAbilitySystemInterface
{
      //<Other code>
      virtual UAbilitySystemComponent* GetAbilitySystemComponent() const;

      UPROPERTY(Transient)
      class UMyAttributeSet* AttributeSet;
      UPROPERTY(VisibleAnywhere, Category = "Gameplay")
      class UAbilitySystemComponent* AbilitySystem;
}

MyCharacter.cpp

AMyCharacter::AMyCharacter()
    : Super()
{      
    //<Other code>
    AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystem"));
    AttributeSet = CreateDefaultSubobject<UMyAttributeSet>(TEXT("AttributeSet"));
}

MyAttributeSet наполняется атрибутами следующим образом:

MyAttributeSet.h

class MYGAME_API UMyAttributeSet
: public UAttributeSet

{
      //<Generated Body and other stuff>
      UPROPERTY(BlueprintReadOnly, Category = "Health")
      FGameplayAttributeData Health;
       GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health) 
       GAMEPLAYATTRIBUTE_VALUE_GETTER(Health) 
       GAMEPLAYATTRIBUTE_VALUE_SETTER(Health) 
       GAMEPLAYATTRIBUTE_VALUE_INITTER(Health)
}

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

Теперь, когда персонаж обладает Ability-системой и атрибутами, можно назначить ему абилку. Вариантов сделать это можно придумать много, обойдёмся самым простым — добавим персонажу поле, в которое в редакторе сможем указать ассет абилки, и зарегистрируем её в Ability-системе при запуске игры

MyCharacter.h

      UPROPERTY(EditAnywhere, Category = "Gameplay")
      TSubclassOf<UGameplayAbility> StartupAbility;
MyCharacter.cpp
void AMyCharacter::BeginPlay()
{      
    //<Other code>
    AbilitySystem->GiveAbility(FGameplayAbilitySpec(StartupAbility, 1, INDEX_NONE, this));
}

После этого абилка будет зарегистрирована, и вызвать её можно будет либо по её тегу, либо по указателю на класс.

Рассмотрим пример скрипта самой простой абилки

После активации способности, происходит Commit ability. Это базовая проверка того, что на нее может наложиться эффект cooldown (cooldown — это эффект или таймер, который мешает нам использовать абилку несколько раз подряд). Также здесь проверяется цена абилки, необходимая для того, чтобы ее наложить. После успешной проверки в CommitAbility на персонажа накладывается cooldown и цена. В этом случае мы можем двигаться дальше по нашей логике и делать все необходимые действия. В примере мы накладываем на персонажа, активирующего абилку, эффект GE_Berserk.

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

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

Вот так выглядят параметры этой абилки:

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

Далее можно настроить, как создаётся инстанс абилки и как она ведёт себя при сетевом взаимодействии — пытается ли «предсказать» поведение на клиенте, или вызывается строго на сервере.

В секции Triggers можно настроить условия, по которым абилка активируется автоматически — при наложении эффекта с определённым тегом или по Gameplay Event.

Наконец, можно выбрать Cooldown и Cost эффекты. Остановимся на них подробнее.

Для cooldown-эффекта наибольший смысл имеют следующие параметры: длительность и его тег:

По тегу определяется, активен сейчас cooldown или нет. Если у персонажа будет другая абилка, cooldown которой будет иметь такой же тег, то на «перезарядку» обе будут уходить одновременно, когда активируется одна из них.

Длительность, как можно увидеть из примера, берётся из таблицы BerserkStats, из строчки с именем CooldownDuration. Здесь же можно выбрать множитель для этого значения. Вместо Scalable Float, однако, можно указать другой механизм определения длительности — например, он может быть основан на атрибуте персонажа или может быть вычисляем специально написанным для этого классом.

«Цена» же эффекта является «моментальным» эффектом. Для неё наибольший смысл имеет само «значение» цены. Будем считать, что ценой получения временной неуязвимости будет 10 единиц здоровья.

Здесь мы можем выбрать, какой атрибут мы хотим изменить, и как. Выбрать можно из списка всех атрибутов, определённыйх в C++ — даже если ваши атрибуты разбиты на несколько наборов, здесь они будут присутствовать единым списком. Точно такая же логика используется для нанесения урона и для временных усилений. Как и в прошлом примере, значение берётся из таблицы.

В самой таблице нет ничего сверхъестественного:

CurveTable может импортироваться в UE4 из .csv или .json, или, при определённом усилии — из любого другого формата. Таким образом удобно сводить все настройки конкретной абилки или даже все настройки персонажа в одну общую таблицу. Этому так же помогает добавленный недавно в UE4 функционал Composite-таблиц.

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

Уже было упомянуто, что у каждой абилки есть отдельный ассет cooldown, отдельный cost, сам скрипт абилки, отдельным файлом будет gameplay cue, и это только самые базовые вещи. Если мы захотим летящий снаряд — он будет создан как отдельный ассет, как и любые другие акторы, которые могут нам понадобиться. И всё это многообразие может обладать своими параметрами, которые, с помощью FScalableFloat, можно запросто свести в одну таблицу — единую точку входа для редактирования всех числовых (и не только) параметров абилки.

Кроме перечисленных выше параметров, в эффекте есть также весьма гибкие настройки тегов:

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

Риски и распространённые опасения

Я уже упоминал, что у нас были опасения к Ability System, когда мы ездили на Unreal Fest в 2019 году. Опасения были следующие, и, в принципе, они могут и вполне закономерно возникают у тех, кто только начинает пользоваться системой, или задумывается о том, использовать её или нет.

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

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

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

Риски и реальность

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

  • Нужно потратить некоторое время на обучение. Как уже было сказано, в системе нужно как следует разобраться и научиться мыслить её терминами — только тогда можно использовать её максимально эффективно.
  • Сложно следить за всеми взаимодействиями по тэгам. Сам движок не отслеживает все взаимодействия, которые вы записали. То есть если гейм-дизайнеры придумали хитрое взаимодействие с тэгами — желательно, чтобы это было записано где-то в документации. Иначе будет сложно поддерживать это существующее решение, и незначительное изменение в списке тегов может привести к крупным багам в игровой логике.
  • Куча разных файлов. Это решается введением Data Tables и само по себе решает ряд других проблем, но факт остаётся фактом — файлов будет много.
  • Одностороннее взаимодействие Ability System и других систем. Уже было упомянуто то, как стоит строить взаимодействие между Ability System и Character Movement Component. Cо всеми остальными сущностями система работает так же. Если вам нужно заставить Gameplay Effect’ы влиять на какой-то ваш компонент, вы специально для него заводите атрибуты и пишете логику, связывающую атрибуты в Ability-системе и параметры вашего компонента, в одностороннем порядке.
  • Завязана под ивенты, но проверка текущего статуса может быть затруднительной. Ability System предоставляет достаточно большое количество ивентов и колбеков, сообщающих о том, что внутри неё происходит — но этими ивентами её прозрачность, как правило, ограничивается. Получение информации о том, какая абилка в данный момент активна, может оказаться нетривиальной задачей.

Почему опасаться не стоит

В первую очередь, разберёмся с заявлением самих разработчиков. В этой части доклада я собирался повторить слова Мэтта Эдмондса о том, что Epic Games, хоть и сделали с использованием Ability-системы уже две игры, понимают сложность плагина в поддержке и с опасением выкладывают его в публичный доступ. О том, что наше с вами внимание к плагину, активность на форумах, Answerhub и UDN даёт Epic Games лишний повод задуматься о поддержке этого плагина. И в данный момент, с версии 4.22, плагин вместо стеснительной пометки unsupported гордо называет себя бета-версией. Всё больше разработчиков сейчас обращают на Ability System своё внимание, и это даёт нам всем шанс на его дальнейшее развитие и совершенствование.

Стабильность и лёгкость в поддержке Ability-системы можно достаточно просто оценить невооружённым глазом, посмотрев на Fortnite и их скорость разработки/замены контента. И несмотря на то, что Paragon закрылся, многие разработчики, включая самих разработчиков из Epic Games, продолжать использовать данный инструмент.

Почему бы не написать свое решение?

Я уже несколько раз упоминал мультиплеер и то, что к нему мы ещё вернёмся. И это как раз та часть статьи, когда мы о нём более подробно поговорим. Абилити-система предоставляет огромное количество инструментов для решения тех проблем, которые начинают возникать при разработке динамичной сетевой игры. Со стороны может показаться, что писать там нечего — отправляем сообщения между клиентоми сервером, настраиваем репликацию. На деле ситуация более сложная. Такие вещи, как prediction, lag compensation могут стать камнем преткновения, и они отличают стабильную мультиплеерную игру от тестовой наработки с дёргающимися объектами, несвоевременным фидбеком игроку и мегабайтами трафика. Чтобы решить эти проблемы в своём проекте самостоятельно, вам в лучшем случае понадобится уйма времени, а в худшем — более опытные специалисты, уже знакомые с проблемой (а они вам, скорее всего, скажут, что проще взять готовое решение). К тому же, время программистам нужно для того, чтобы реализовывать саму игру, а именно — интересные геймлейные механики, делающие игру уникальной.

Что предлагает Ability System для мультиплеера

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

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

Что касается озвученных выше сложных задач — prediction, lag compenstaion, validation — рассмотрим кратко их суть, чтобы лучше понять важность предлагаемых Ability-системой решений.

Чтобы исключить возможность читов в игре, нужно принимать все основные решения на стороне сервера. Тогда в момент получения от игрока команды на выстрел необходимо эту команду отправить серверу, чтобы тот в свою очередь это событие проработал и создал снаряд, который отправится лететь туда, куда смотрит игрок. Однако на передачу сообщения уходит время, и сервер реагирует на команду позже, чем игрок её дал. За это время объекты успели переместиться, и состояние персонажа игрока так же могло поменяться. В динамичных сетевых шутерах это может привести (и даже однозначно приводит) к дискомфорту игрока и непониманию текущей игровой ситуации. Поэтому мы зачастую «предсказываем» фидбек, который игрок получит, выполнив то или иное действие, на клиенте (prediction), и учитываем ping между клиентом и сервером при рассчёте попаданий (lag compensation), а также продумываем поведение клиента, если prediction был сделан неверно (validation).

Рассмотрим работу этих механизмов на конкретном примере.

Я уже упоминал, что даже такое действие, как перезарядка оружия, может быть абилкой. Его и рассмотрим. Допустим, нужно перезарядить дробовик.
В первую очередь, нужно проверить, если ли возможность запустить перезарядку — не оглушён ли персонаж, есть ли нужное количество патронов. После этого нужно запретить персонажу стрелять, если в обойме ещё остались патроны. Затем — запустить анимацию того, как персонаж засовывает в обойму патроны, один за другим. Как только следующий патрон заряжен, нужно увеличить счётчик «готовых» патронов. Эта процедура повторяется несколько раз. Затем, когда всё завершается, снова разрешаем персонажу стрелять.
Добавим в эту схему клиент-серверное взаимодействие.

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

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

И всё это те проблемы, которые придётся решать своими руками всем желающим написать подобную систему с нуля.

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

Ability System Multiplayer: Prediction Keys?

Prediction Key создается в рамках предиктивного окна (отмечено синим на схеме выше). В момент открытия этого окна он создаётся и связывается со своей копией на сервере, как только она будет создана. В рамках этого ключа можно выполнять предиктивные действия, связанные непосредственно с Ability-системой: из коробки он поддерживает наложение эффектов, запуск анимационных монтажей, вызов Gameplay Events, активацию абилок и Gameplay Cue. Все эти вещи будут корректно очищены, если сервер отзовёт у клиента Prediction Key — вернёт отказ на первоначальное действие, которое привело ко всем этим изменениям. Если, однако, мы выходим за рамки системы — например, создаём снаряд или какую-то другую сущность — нам нужно будет самим подписаться на «отмену» Prediction Key и провести необходимую чистку. Есть также ряд ограничений, связанный с предсказанием

Почему стоит использовать Ability System?

Несмотря на сложность, у Ability System есть 2 главных преимущества. Первое — она предлагает вам огромное количество опций, а также безопасность и стабильность протестированного в продакшне решения. Второе — система берет на себя самые сложные моменты клиент-серверного взаимодействия, которые руками писать просто не хочется. Лучше потратить время на то, чтобы сделать игру, чем писать инструментарий.

Gameplay Ability System достаточно гибкая, чтобы позволить построить на своей основе огромное количество механик, главное (как и с любой частью UE4) — разобраться в том, как она работает, не пытаться с ней бороться и её обмануть. Многие вещи, которые могут вам понадобиться, в ней есть из коробки — важно лишь их найти и осознать.

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

В начале статьи я упомянул Мэта Эдмондса и его выступление на Unreal Fest. Он писал свою абилити-систему на другом движке, он писал свою абилити-систему на Unreal Engine, и он пользовался Gameplay Ability System. Его выводы здесь приводить не буду, ниже есть ссылка на его доклад. В самом его конце он приводит несколько таблиц и подробно о них говорит. Всем, у кого ещё остались сомнения, стоит на это взглянуть.

Полезные ссылки

Выступление Мэтта Эдмондса на Unreal Fest Europe 2019

Документация пользователя tranek на GitHub

Gameplay Abilities and You

Доклад Степана Садчикова про Ability System на Unreal Meetup Minsk

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

У меня вот один вопрос, почему все С++ геймдевелоперы так любят предельно мутные фреймвочные абстракции с запутывающим и сбивающим с толку именованием, это такая традиция? Кто её основатель?

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

Спасибо за старания

Подписаться на комментарии