Gameplay Ability란 게임 내에서 액터가 수행할 수 있는 모든 행동들을 의미한다.
게임 내에서 동적으로 키를 입력할 때마다 특정 어빌리티를 사용할 수 있도록 해보자.
먼저 두 가지 헷갈리는 개념인 추가(Add), 활성화(Activate)에 대해서 짚고 넘어가야 할 필요가 있다.
'추가' 는 어빌리티를 액터에 할당하거나 등록하는 과정을 의미한다. 이 과정은 주로 Ability System Component를 통해 수행된다.
어빌리티를 캐릭터에 추가하면, 어빌리티 목록에 해당 어빌리티가 등록된다. 어빌리티가 등록되었다고 해서 바로 '활성화' 시킬 수 있는 것은 아니다. 태그를 통해 어빌리티의 동작을 제어하고, 활성화 조건을 명시할 수 있다.
Ability System Component에서 GiveAbility 함수를 통해서 어빌리티를 추가할 수 있다.

태그 프로퍼티는 Gameplay Ability 블루프린트에서 확인할 수 있으며, 각 프로퍼티마다 태그를 바인딩할 수 있다.
어빌리티가 활성화되기 위해서 '반드시 필요로 하는' 태그이다.
즉 캐릭터에게 이 태그가 없으면 어빌리티가 활성화되지 않는다. 사용하기 위해 어떤 상태 또는 능력 등이 요구되는 어빌리티에 유용하다.
어빌리티의 활성화를 '막는' 태그이다.
즉 캐릭터에게 이 태그가 있으면 어빌리티가 활성화되지 않는다. 실행을 제어한다는 측면에서는 required와 비슷하지만, 반대 방향으로의 제어를 수행한다고 보면 될 것 같다.
예를 들어서 특정한 상태이상이 걸린 상태에서는 스킬을 사용할 수 없는 등의 제약조건을 구현할 때 유용하다.
'활성화' 는 '추가' 와 아예 다른 개념이다. 추가되었다고 해서 모두 활성화된 상태는 아니다. 어빌리티가 실제로 사용할 준비가 되었을 때 (사용 조건을 만족하고 있는 상태일 때) 어빌리티를 실행 및 발동시키는 과정을 의미한다.
TryActivateAbility 함수를 통해 활성화할 수 있으며, 이 과정에서 cost, requirement 등의 조건을 확인한다. 어빌리티가 활성화될 때는 GameplayAbility::ActivateAbility 함수가 호출되며, 여기에서 어빌리티가 활성화됐을 때의 로직을 추가하면 된다.
우리는 특정 키 입력에 대해서 어빌리티를 활성화하기 위해 인풋 컴포넌트에 게임플레이 태그를 연결해야 할 필요가 있다. Enhanced Input Component는 입력을 처리하는 데 강력한 기능을 제공하지만, 기본적으로는 입력에 대한 태그나 Ability와 직접 연결하는 기능은 제공하지 않는다.
따라서 특정한 InputAction과 특정한 태그를 서로 연결시켜 하나의 구조체로 만들고, 이 구조체를 data asset으로 만들어서 관리함으로써 코드의 모듈화, 관리의 편의 등의 이점을 누릴 수 있다.
EnhancedInputComponent를 상속하는 커스텀 인풋 컴포넌트 클래스를 만들고, TriggerEvent에 바인딩하는 작업이 필요하다.
먼저 커스텀 Data Asset 클래스를 하나 만들고, 아래와 같이 UInputAction과 UGameplayTag를 서로 묶을 수 있는 구조체를 만든다.
USTRUCT(BlueprintType)
struct FAuraInputAction
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly)
const UInputAction* InputAction = nullptr;
UPROPERTY(EditDefaultsOnly)
FGameplayTag InputTag = FGameplayTag();
};
이 클래스는 커스텀 구조체를 담고 있는 TArray와, 이 TArray에서 특정한 태그에 대응하는 구조체를 반환하는 lookup 함수를 포함할 것이다.

이후 위 사진과 같이 Data Asset을 만들고, Input Action과 태그를 만든 뒤 서로 연결한다.
그리고 Project Settings -> Input -> Default Classes 에서 Default Input Component Class를 꼭 직접 만든 클래스로 재지정해 줘야 한다.

AuraInputComponent 에서는 기존의 EnhancedInputComponent에서처럼 특정한 TriggerEvent에 함수를 바인딩하는 역할을 추가한다.
여기서는 여러 타입의 함수를 여러 타입의 액터에 바인딩할 수 있어야 하므로 템플릿 함수를 만든다.
UCLASS()
class AURA_API UAuraInputComponent : public UEnhancedInputComponent
{
GENERATED_BODY()
public:
template <class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
void BindAbilityActions(const UAuraInputConfig* InputConfig, UserClass* Object, PressedFuncType PressedFunc,
ReleasedFuncType ReleasedFunc, HeldFuncType HeldFunc);
};
// Data Asset에 존재하는 모든 InputAction에 대해 액션 바인딩을 한꺼번에 수행
template <class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
void UAuraInputComponent::BindAbilityActions(const UAuraInputConfig* InputConfig, UserClass* Object,
PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, HeldFuncType HeldFunc)
{
check(InputConfig);
for (const FAuraInputAction& Action : InputConfig->AbilityInputActions)
{
if (Action.InputAction && Action.InputTag.IsValid())
{
if (PressedFunc)
BindAction(Action.InputAction, ETriggerEvent::Started, Object, PressedFunc, Action.InputTag);
if (ReleasedFunc)
BindAction(Action.InputAction, ETriggerEvent::Completed, Object, ReleasedFunc, Action.InputTag);
if (HeldFunc)
BindAction(Action.InputAction, ETriggerEvent::Triggered, Object, HeldFunc, Action.InputTag);
}
}
}
세 가지 주요 이벤트인 Hold, Pressed, Released에 대응하는 함수를 바인딩할 수 있도록 했다.
PlayerController 클래스도 일부 수정해야 한다.
먼저 BindAbilityActions 함수를 사용할 수 있게 하기 위해 AuraInputComponent로 캐스팅해야 하며, InputConfig와 함게 BindAbilityActions 함수를 호출한다.
먼저 바인딩할 함수 3가지를 정의한다.
void AbilityInputTagPressed(FGameplayTag InputTag);
void AbilityInputTagReleased(FGameplayTag InputTag);
void AbilityInputTagHeld(FGameplayTag InputTag);
그리고 BindAbilityActions 함수를 호출.
void AAuraPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
UAuraInputComponent* AuraInputComponent = CastChecked<UAuraInputComponent>(InputComponent);
AuraInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AAuraPlayerController::Move);
AuraInputComponent->BindAbilityActions(InputConfig, this, &AAuraPlayerController::AbilityInputTagPressed, &AAuraPlayerController::AbilityInputTagReleased, &AAuraPlayerController::AbilityInputTagHeld);
}
어빌리티의 활성화는 ASC에서 수행하는 것이 일반적이다. Ability System Component에도 이 인풋 함수에 대응하는 함수를 만들어야 한다.
어빌리티를 활성화하기 위한 코드만 살펴보면 GetActivatableAbilities() 함수를 통해서 활성화 가능한 어빌리티 스펙들을 얻은 후, 이 중에서 인풋에 바인딩 된 태그와 일치하는 것을 찾는다. 찾은 어빌리티 스펙에 TryActivateAbility 함수와 함께 AbilitySpecInputPressed 함수를 호출해 준다.
void UAuraAbilitySystemComponent::AbilityInputTagHeld(const FGameplayTag& InputTag)
{
if (!InputTag.IsValid()) return;
for (FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
{
if (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
{
AbilitySpecInputPressed(AbilitySpec);
if (!AbilitySpec.IsActive())
{
TryActivateAbility(AbilitySpec.Handle);
}
}
}
}
AbilitySpecInputPressed 함수는 입력이 눌렸을 때 해당 Ability를 활성화하는 함수이다. 이 함수에 어빌리티 스펙을 파라미터로 전달하면 된다.