게임플레이 어빌리티 문서를 기반으로 합니다.
Gameplay Ability (게임플레이 어빌리티)는 UGameplayAbility 클래스에서 파생된 것으로, 게임 내 어빌리티가 하는 일, 시전하는 데 드는 비용, 언제 어떤 상황에서 사용할 수 있는지 등을 정의합니다. 게임플레이 어빌리티는 비동기 실행되는 오브젝트 인스턴스로 존재할 수 있기에 캐릭터 애니메이션, 파티클과 사운드 이펙트, 플레이어 입력이나 실행 도중 벌어지는 캐릭터 상호작용에 따른 분기처럼 전문화된 다단계 작업을 실행할 수 있습니다. 네트워크를 통한 자체 리플리케이트나, (클라이언트 측 예측 지원을 포함해서) 클라이언트 또는 서버 머신에서의 실행이나, 심지어 변수 동기화 및 원격 프로시저 콜(RPC) 호출도 가능합니다. 또한 게임 세션 도중 엔진에서 게임플레이 어빌리티를 구현하는 방식도 유연합니다. 예로 쿨타임(재사용 대기시간) 및 시전 비용, 플레이어 입력, 애님 몽타주가 있는 애니메이션, 어빌리티 자체를 액터에 부여할 때의 반응도 구현할 수 있습니다.
액터가 어빌리티를 사용하려면, Ability System Component에 그 어빌리티를 부여(grant)해야 합니다.
다음 Ability System Component 함수는 어빌리티에 액세스를 부여합니다.
FGameplayAbilitySpecHandle GiveAbility(const FGameplayAbilitySpec& AbilitySpec);
FGameplayAbilitySpecHandle GiveAbilityAndActivateOnce(FGameplayAbilitySpec& AbilitySpec, const FGameplayEventData* GameplayEventData = nullptr);
다음은 Ability System Component에서 어빌리티에 대한 액세스를 회수(revoke)하는 함수로, 어빌리티를 부여받았을 때 반환된 FGameplayAbilitySpecHandle 을 사용합니다.
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Gameplay Abilities")
void ClearAbility(const FGameplayAbilitySpecHandle& Handle);
void SetRemoveAbilityOnEnd(FGameplayAbilitySpecHandle AbilitySpecHandle);
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="Gameplay Abilities")
void ClearAllAbilities();
Game Ability에 대한 정보를 담고 있는 구조체(FGameplayAbilitySpec) 이며, ASC는 Ability에 대한 정보를 직접 참조하고 있지 않고 이 Spec이라 불리는 데이터를 통해 관리합니다. Spec으로 GA의 현재 정보를 알 수 있으며, ASC로 부터 GA를 사용하려면 Spec에 있는 Handle 값을 사용해야 합니다. Handle은 전역적인 값이며 Spec 생성 시 자동으로 1씩 증가합니다. SpecHandle은 Ability 인스턴스에 대한 레퍼런스 값입니다.
게임플레이 어빌리티가 액터의 Ability System Component에 부여된 이후의 기본 실행 주기는 다음과 같습니다.
CanActivateAbility (어빌리티 가동 여부 확인)는 호출자가 어빌리티를 실행 시도하지 않아도 그 사용가능 여부를 알려줍니다.
예를 들어, 유저 인터페이스를 회색으로 만들거나 아이콘을 비활성화시켜 플레이어가 사용하지 못하도록 하거나, 캐릭터에 사운드 또는 파티클 이펙트를 재생하여 특정 어빌리티의 사용가능 여부를 알립니다.
CallActivateAbility (어빌리티 가동 호출)은 어빌리티에 관련된 게임 코드를 실행하지만, 어빌리티가 사용 가능한지는 검사하지 않습니다. 이 함수는 CanActivateAbility 검사와 어빌리티 실행 사이 약간의 로직이 필요한 경우에 주로 호출됩니다.
void UGameplayAbility::CallActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
{
PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData);
ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
}
게임플레이 어빌리티는 액터나 컴포넌트와는 달리 주 작업을 "Tick" 함수에서 하지 않습니다. 대신 시전 도중 대부분의 작업을 비동기 처리하는 Ability Task (어빌리티 태스크)를 실행합니다. 그런 다음 그 태스크의 출력을 (C++ 에서) Delegate (델리게이트)에 후킹하거나 (블루프린트에서) 출력 실행 핀에 노드를 연결하여 처리합니다.
CommitAbility (어빌리티 확인)은 Activate 내에서 호출한 경우 어빌리티 시전 비용을 적용, 즉 Gameplay Attribute (게임플레이 어트리뷰트)에서 ("마나"든 "에너지"든 게임 시스템에 맞는) 자원을 빼고 쿨다운을 적용합니다.
CancelAbility (어빌리티 취소)는 어빌리티 취소 메커니즘을 제공합니다. 어빌리티의 CanBeCanceled (취소 가능) 함수가 요청을 거부할 수 있습니다. CommitAbility 와 달리, 이 함수는 어빌리티 자체 외부 호출자에서 사용할 수 있습니다. 취소가 성공하면 OnGameplayAbilityCancelled (게임플레이 어빌리티 취소 시)로 BroadCast한 뒤 그 어빌리티를 마치기 위한 표준 코드 패스로 들어가, 어빌리티에 특수한 클린업 코드를 실핼할 기회를 주거나, 정상적으로 마쳤을 때와 다른 작동을 하도록 할 수 있습니다.
bool UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation)
{
... 생략
if (NetMode != ROLE_Authority && (Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::ServerOnly || Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::ServerInitiated))
{
if (bAllowRemoteActivation)
{
FScopedCanActivateAbilityLogEnabler LogEnabler;
if (Ability->CanActivateAbility(AbilityToActivate, ActorInfo, nullptr, nullptr, &FailureTags))
{
// No prediction key, server will assign a server-generated key
CallServerTryActivateAbility(AbilityToActivate, Spec->InputPressed, FPredictionKey());
return true;
}
else
{
NotifyAbilityFailed(AbilityToActivate, Ability, FailureTags);
return false;
}
}
ABILITY_LOG(Log, TEXT("Can't activate ServerOnly or ServerInitiated ability %s when not the server."), *Ability->GetName());
return false;
}
return InternalTryActivateAbility(AbilityToActivate);
}
Gameplay Tag (게임플레이 태그)는 여러 게임플레이 어빌리티의 상호작용 방식을 결정하는 데 도움이 됩니다. 각 어빌리티의 작동방식에 따라 일정한 태그 세트를 통해 식별하고 분류해 두면 Gameplay Tag Container (게임플레이 태그 컨테이너) 및 Gameplay Tag Query (게임플레이 태그 쿼리)를 통해 다른 어빌리티의 상호작용을 지원할 수 있습니다.
| 게임플레이 태그 변수 | 목적 |
|---|---|
| Cancel Abilities With Tag | 어빌리티 취소 - 이 어빌리티 실행 도중 이미 실행 중인 어빌리티의 태그가 제공된 목록에 일치하면 취소합니다. |
| Block Abilities With Tag | 어빌리티 차단 - 이 어빌리티 실행 도중 다른 어빌리티의 태그가 일치하면 실행을 차단합니다. |
| Activation Owned Tags | 시전 오너 태그 - 이 어빌리티 실행 도중, 그 어빌리티의 오너에 이 태그 세트를 붙입니다 |
| Activation Required Tags | 시전 필수 태그 - 이 어빌리티는 시전하는 액터 또는 컴포넌트에 이 태그가 전부 있을 때만 시전할 수 있습니다. |
| Activation Blocked Tags | 시전 차단 태그 - 이 어빌리티는 시전하는 액터 또는 컴포넌트에 이 태그가 하나도 없을 때만 시전할 수 있습니다. |
| Target Required Tags | 대상 필수 태그 - 이 어빌리티는 대상 액터 또는 컴포넌트에 이 태그가 전부 있을 때만 시전할 수 있습니다. |
| Target Blocked Tags | 대상 차단 태그 - 이 어빌리티는 대상 액터 또는 컴포넌트에 이 태그가 하나도 없을 때만 시전할 수 있습니다. |
또한 태그를 이용하여 GA를 발동하거나 취소할 수 있습니다.
FGameplayTagContainer TargetTag(ABTAG_ACTOR_ROTATE);
if (ASC->HasMatchingGameplayTag(ABTAG_ACTOR_ISROTATING))
{
ASC->CancelAbilities(&TargetTag);
}
else
{
ASC->TryActivateAbilitiesByTag(TargetTag);
}
게임플레이 어빌리티는 내부 상태와 게임플레이 이벤트 리플리케이션을 지원하며, 리플리케이션을 끄면 네트워크 대역폭과 CPU 사이클을 절약할 수도 있습니다. 어빌리티의 Gameplay Ability Replication Policy (게임플레이 어빌리티 리플리케이션 정책)을 "예" 또는 "아니오" 로 설정하여, 어빌리티가 네트워크를 통해 자체 인스턴스를 리플리케이트할지, 상태를 업데이트할지, 게임플레이 이벤트를 전송할지 제어할 수 있습니다. 리플리케이트하는 어빌리티가 있는 멀티플레이어 게임의 경우, 리플리케이션 처리를 위한 옵션이 몇 가지 있는데, Gameplay Net Execution Policy (게임플레이 넷 실행 정책)이라 합니다.
Local Predicted: 로컬 예측 - 이 옵션은 반응 속도와 정확성 사이 균형이 좋습니다. 클라이언트가 명령을 내리면 로컬 클라이언트에서 어빌리티를 즉시 실행하지만, 어빌리티의 실제 영향이 어땠는지 최종 결정은 서버에서 내립니다. 실제로 클라이언트는 서버에 어빌리티 실행 권한을 요청하지만, 클라이언트 관점의 결과를 서버가 동의할 것으로 예상하고 로컬에서 진행합니다. 클라이언트는 로컬에서 어빌리티 작동방식을 예측하므로, 클라이언트의 예측이 서버와 모순되지 않는다면 랙 없이 완전 부드럽게 느껴질 것입니다.
Local Only: 로컬만 - 단순히 클라이언트가 로컬에서 어빌리티를 실행합니다. 서버에 리플리케이트하지 않습니다. 클라이언트와 서버가 같거나 (즉 물리적으로 같은 서버 머신에서 플레이하거나) 싱글 플레이어 게임이라면 서버에서도 실행되긴 합니다. 서버 머신에서 플레이하는 클라이언트가 없는 데디케이티드 서버 게임에는 적용되지 않습니다. 이 어빌리티로 클라이언트가 영향을 주는 모든 것은 일반 리플리케이션 규칙의 대상이 되며, 서버에서 보정을 받을 수도 있습니다.
Server Initiated: 서버 시작 - 어빌리티를 서버에서 시작하여 클라이언트에 전파합니다. 종종 클라이언트 관점에서 서버의 실제 상황과 더욱 정확히 맞지만, 어빌리티를 사용하는 클라이언트에서는 로컬 예측이 없어 지연이 관측됩니다. 지연이 매우 짧긴 하겠지만, 어떤 어빌리티는 압박이 심한 상황에서 신속히 시전해야 하는 동작의 경우라면 특히 로컬 예측 모드만큼 부드러운 느낌이 들지 않을 것입니다.
Server Only: 서버만 - 서버에서만 실행하고 클라이언트에는 리플리케이트하지 않습니다. 이 어빌리티에 영향받는 변수는 평소처럼 리플리케이트됩니다. 이 어빌리티 자체는 서버에서만 실행되기는 해도, 서버에 전권이 있는 데이터에 영향을 줄 수 있고 클라이언트에 전파된다는 뜻입니다.
게임플레이 어빌리티를 실행하면, 보통 (그 어빌리티 유형의) 새 오브젝트를 스폰하여 어빌리티 진행 상황을 추적합니다. 배틀 로얄, MOBA, MMO, RTS 게임에서 플레이어나 AI 캐릭터 수가 백 단위를 넘는 경우 어빌리티 실행 빈도가 매우 높아지면 어빌리티 오브젝트 생성도 너무 빨라 퍼포먼스에 부정적 영향을 미칠 수 있습니다. 이를 해결하기 위해 어빌리티에 원하는 효율과 기능에 따라 인스턴스 정책을 셋 중에서 선택할 수 있습니다. 지원하는 세 가지 인스턴스 유형은 다음과 같습니다.
Instanced per Execution: 실행별 인스턴스 - 어빌리티를 실행할 때마다 어빌리티의 오브젝트 사본을 스폰합니다. 장점은 블루프린트 그래프와 멤버 변수를 자유롭게 사용할 수 있고, 실행 시작 시 모든 것이 기본값으로 초기화됩니다. 구현하기 가장 간단한 인스턴스 정책이지만, 큰 오버헤드로 인해 드물게 실행되는 어빌리티에만 사용해야 합니다. 예를 들어 MOBA 의 "궁극기"같은 것에 좋습니다. 실행 쿨다운이 (보통 60-90 초로) 긴 경향이 있고, 이 어빌리티를 사용할 수 있는 캐릭터가 (보통 10 명 정도로) 얼마 안되기 때문입니다. AI "미니언"의 기본 공격 어빌리티에는 좋지 않습니다. 한 번에 수백 개는 되는 데다 기본 공격은 꽤 자주 일어나므로 새 오브젝트의 생성( 및 리플리케이션)이 잦을 것입니다.
Instanced per Actor: 액터별 인스턴스 - 이 어빌리티를 처음 실행하면 각 액터마다 어빌리티 인스턴스를 하나 스폰한 뒤 앞으로 실행할 때 재사용합니다. 그래서 어빌리티를 실행할 때마다 멤버 변수를 지워야 하지만, 여러 번 실행에 저장하는 정보량을 절약할 수 있습니다. 어빌리티에 리플리케이트된 오브젝트를 통해 변수 변화와 RPC 를 처리할 수 있지만 실행할 때마다 새 오브젝트를 스폰하느라 네트워크 대역폭과 CPU 시간이 소모되지 않기 때문에 리플리케이션에 이상적입니다. 규모가 큰 상황에서 잘 통하는 정책으로, (예를 들어 대규모 전투처럼) 많은 액터가 어빌리티를 사용해도 처음 사용할 때만 오브젝트를 스폰하기 때문입니다. [가장 일반적으로 사용]
Non-Instanced: 인스턴스 없음 - 가장 효율적인 인스턴스 정책입니다. 어빌리티를 실행해도 오브젝트를 스폰하는 대신 클래스 디폴트 오브젝트 를 사용합니다. 하지만 그 효율이 여러 제약을 낳습니다. 우선, 이 정책은 C++ 로만 작성한 어빌리티 전용입니다. 블루프린트 그래프는 오브젝트 인스턴스 없이 생성할 수 없기 때문입니다. 인스턴스가 없는 어빌리티의 블루프린트 클래스를 생성할 수는 있지만, 노출된 프로퍼티의 기본값을 변경할 때만 사용할 수 있습니다. 추가로, 어빌리티 실행 도중 네이티브 C++ 코드에서조차 멤버 변수를 변경할 수 없습니다. 델리게이트 바인딩도 안되고, 어빌리티의 변수 리플리케이션이나 RPC 처리도 안됩니다. 내부적인 변수 저장 (즉 어빌리티 사용자에 어트리뷰트 설정은 가능) 또는 데이터 리플리케이션이 필요 없는 어빌리티에만 사용해야 합니다. 자주 실행하고 여러 캐릭터에 사용되는 어빌리티, 예를 들어 대규모 RTS 또는 MOBA 에서 유닛의 기본 공격에 특히 잘 맞습니다.
Gameplay Event (게임플레이 이벤트)는 일반 채널을 통하지 않고도 어떤 컨텍스트의 데이터 페이로드를 전송하여 게임플레이 어빌리티를 직접 트리거할 수 있는 데이터 구조체입니다. 그 일반적인 방법은 Send Gameplay Event To Actor (액터에 게임플레이 이벤트 전송)을 호출하고 IAbilitySystemInterface 인터페이스와 그 게임플레이 이벤트에 필요한 컨텍스트 정보를 구현하는 액터를 제공하면 되지만, Ability System 컴포넌트에서 Handle Gameplay Event (게임플레이 이벤트 처리)를 바로 호출해도 됩니다. 게임플레이 어빌리티를 호출하는 정상적인 방법은 아니므로, 어빌리티 시스템에 필요할 수 있는 컨텍스트 정보는 FGameplayEventData 데이터 구조체로 전달합니다. 이 구조체는 범용이라 특정 게임플레이 이벤트 또는 어빌리티용으로 확장되지 않지만, 일반적으로 사용하기에 충분할 것입니다. 다용도 ContextHandle 필드에 필요한 부가 정보를 제공하면 됩니다.