https://github.com/tranek/GASDocumentation#concepts-ga
단순히 위의 문서를 보고 제멋대로 해석해서 적은 것입니다. 차라리 위의 문서를 봐주세요. 저 영어 못 합니다.
4.6 Gameplay Abilities
4.6.1 Gameplay Ability Definition
게임플레이 어빌리티는 게임에서 액터가 할 수 있는 행동이나 스킬이다. 한번에 하나 이상의 게임플레이 어빌리티가 실행될 수 있다. 예를 들어 달리면서 총을 쓴다던지. 블루프린트나 C++로 만들 수 있다.
게임플레이 어빌리티의 예:
- 점프
- 달리기
- 총쏘기
- 자동적으로 X초마다 공격을 막기
- 포션을 사용하기
- 문을 열기
- 리소스를 획득하기
- 빌딩을 건설하기
다음의 것들은 게임플레이 어빌리티로 만들면 안된다.:
- 기본적인 움직임 입력
- UI와의 상호작용, 아이템을 상점에서 구매하는 것은 게임플레이 어빌리티를 사용하지 않는다.
위의 것들은 규칙은 아니고 추천이다. 디자인은 아주 다양하게 만들 수 있다.
게임플레이 어빌리티는 기본 기능과 함께 제공되며 어트리뷰트의 변경 내용을 수정하거나 게임플레이 어빌리티의 기능을 변경할 수있다.
게임플레이 어빌리티는 자신의 클라이언트 and/or 서버에서 돌아가며 Net Execution Policy에 달려있다 but not simulated proxies. Net Execution Policy는 게임플레이 어빌리티를 로컬로 predict할지 결정한다. 어빌리티는 기본적으로 코스트와 쿨다운 게임플레이 이펙트 행동을 가진다. 게임플레이 어빌리티는 AbilityTask를 사용한다. 어빌리티 태스크는 이벤트를 기다리거나 어트리뷰트가 변경되는걸 기다리거나 타겟 플레이어를 선택하거나 Root Motion Source로 캐릭터를 움직이는 액션을 위해 사용된다. Simulated 클라이언트는 게임플레이 어빌리티를 실행하지 않는다. 대신에, 서버가 어빌리티를 실행할 때, simulated proxies(like animation montages)처럼 플레이에 시각적으로 필요한 것이 replicated되고 RPC가 소리나 파티클같은 외곽적인 것들을 위해 AbilityTasks나 GameplayCues가 실행된다.
모든 게임플레이 어빌리티는 ActivateAbility()함수를 가지며 게임플레이 로직에 따라 재정의하면 된다. 추가적인 로직은 EndAbility()에 추가될 수 있다. EndAbility()는 어빌리티가 완료되거나 취소되었을 때 실행된다.
게임플레이 어빌리티의 플로우 차트:
더 복잡한 게임플레이 어빌리티의 플로우 차트:
복잡한 어빌리티는 다수의 게임플레이 어빌리티를 사용하여 구현될 수 있다. 그런 어빌리티는 서로서로와 상호작용 한다. 위의 플로우 차트를 보면 Start Ability Task가 3개이다.
4.6.1.1 Replication Policy
이 옵션을 사용하지 마라. 이름에 오해의 소지가 있고 이해하지 못 할 것이다.
GameplayAbilitySpecs은 서버로부터 자신의 클라이언트로 기본적으로 replicated된다. 위에서 말했다 시피, 게임플레이 어빌리티는 simulated proxies에서 실행하지 않는다. 이 것은 replicate 혹은 시각적인 변화를 simulated proxies에 RPC하기 위해서 어빌리티 태스크와 게임플레이 큐를 사용한다. 에픽에선 훗날에 이 옵션을 삭제할 생각이다.
4.6.1.2 Server Respect Remote Ability Cancellation
이 옵션은 여러 문제를 일으킬 수도 있다. 그 것은 다음을 의미한다. 만약 클라이언트의 게임플레이 어빌리티가 취소 혹은 어빌리티의 완료에 의해 종료되었다면, 서버에서는 종료가 되었든 말든 종료를 시킨다. 이 후의 이슈가 중요한 건데, 특히 높은 레이턴시의 플레이어가 사용하는 locally predicted 게임플레이 오브젝트인 경우 문제이다. 일반적으로 이 옵션을 사용하지 않는게 좋을 것이다.
4.6.1.3 Replicate Input Directly
이 옵션은 입력 press와 release 이벤트를 서버에 항상 replicate한다. 에픽은 이 옵션을 사용하는 것보다 Generic Replicated Events를 사용하는 것을 추천한다. Generic Replicated Events는 ASC에 input bound가 있다면 어빌리티 태스크와 연관된 존재하는 인풋에 만들어진다.
에픽 코멘트:
/** Direct Input state replication. These will be called if bReplicateInputDirectly is true on the ability and is generally not a good thing to use. (Instead, prefer to use Generic Replicated Events). */
UAbilitySystemComponent::ServerSetInputPressed()
4.6.2 Binding Input to the ASC
ASC는 우리가 직접적으로 input action을 바인드할 수 있도록 해주며 게임플레이 어빌리티에 해당 인풋을 바인드할 수 있도록 해준다. 인풋 액션은 게임플레이 태그의 요구사항이 맞고, 우리가 눌렀을 때, 등록한 게임플레이 어빌리티를 자동적으로 활성화할 수 있도록 해준다. 등록한 인풋 액션은 인풋에 반응하느 built-in AbilityTasks를 사용하는게 요구된다.
게임플레이 어빌리티에 인풋을 등록하는 것 외에도, ASC는 일반적인 Confirm과 Cancle 인풋도 등록한다. 이 특별한 인풋은 어빌리티 태스크에 의해 타겟 액터와 같은 사항들을 확인하거나 어빌리티 태스크를 취소하기 위해 사용된다.
ASC에 인풋을 바인드하기 위해서, 먼저 enum을 만들어야 하는데 여기선 인풋 액션의 이름이 byte로 변경된다.(enum에선 당연한거) enum의 이름은 반드시 프로젝트 세팅에서 등록한 인풋액션의 이름과 맞아야 한다. DisplayName은 상관없다.
샘플 프로젝트에서:
UENUM(BlueprintType)
enum class EGDAbilityInputID : uint8
{
// 0 None
None UMETA(DisplayName = "None"),
// 1 Confirm
Confirm UMETA(DisplayName = "Confirm"),
// 2 Cancel
Cancel UMETA(DisplayName = "Cancel"),
// 3 LMB
Ability1 UMETA(DisplayName = "Ability1"),
// 4 RMB
Ability2 UMETA(DisplayName = "Ability2"),
// 5 Q
Ability3 UMETA(DisplayName = "Ability3"),
// 6 E
Ability4 UMETA(DisplayName = "Ability4"),
// 7 R
Ability5 UMETA(DisplayName = "Ability5"),
// 8 Sprint
Sprint UMETA(DisplayName = "Sprint"),
// 9 Jump
Jump UMETA(DisplayName = "Jump")
};
만약 ASC가 PlayerState에 있다면,
4.6.3 Granting Abilities
ASC에 게임플레이 어빌리티를 부여하면 ASC의 ActivatableAbilities 목록에 게임플레이 태그 요구사항을 충족할 경우 원하는 대로 게임플레이 어빌리티를 활성화할 수 있다.
서버에 게임플레이 어빌리티를 부여하여 클라이언트에 게임플레이 어빌리티 스펙을 자동으로 replicate한다. 다른 클라이언트 / simulated proxies는 게임플레이 어빌리티 스펙을 받지 않는다.
샘플 프로젝트에서는 TArray<TSubclasOf<UGDGameplayAbility>> 를 캐릭터 클래스에 저장한다. 여기서는 게임이 시작되었을 때, 어빌리티를 읽는다.
void AGDCharacterBase::AddCharacterAbilities()
{
// Grant abilities, but only on the server
if (Role != ROLE_Authority || !AbilitySystemComponent.IsValid() || AbilitySystemComponent->bCharacterAbilitiesGiven)
{
return;
}
for (TSubclassOf<UGDGameplayAbility>& StartupAbility : CharacterAbilities)
{
AbilitySystemComponent->GiveAbility(
FGameplayAbilitySpec(StartupAbility, GetAbilityLevel(StartupAbility.GetDefaultObject()->AbilityID), static_cast<int32>(StartupAbility.GetDefaultObject()->AbilityInputID), this));
}
AbilitySystemComponent->bCharacterAbilitiesGiven = true;
}
이 게임플레이 어빌리티들을 얻었을 때, 게임플레이 어빌리티, 어빌리티 레벨, 인풋 바운드, 소스 오브젝트, ASC에 누가 이 어빌리티를 주었는지에 대한 정보와 게임플레이 어빌리티 스펙을 만든다
4.6.4 Activating Abilities
게임플레이 어빌리티가 인풋 액션을 받았을 때, 인풋이 눌려졌고, 게임플레이 태그가 만족되면 자동적으로 활성화된다. 이것이 항상 게임플레이 어빌리티를 활성화하는 올바른 방법은 아닐 수도 있다. ASC는 4가지 다른 방법을 제공한다: 게임플레이 태그에 의해, 게임플레이 어빌리티 클래스에 의해, 게임플레이 어빌리티 스펙 핸들에 의해, 이벤트에 의해서. 이벤트를 사용하여 게임플레이 어빌리티를 활성화하는 것은 allow you to pass in a payload of data with the event.
UFUNCTION(BlueprintCallable, Category = "Abilities")
bool TryActivateAbilitiesByTag(const FGameplayTagContainer& GameplayTagContainer, bool bAllowRemoteActivation = true);
UFUNCTION(BlueprintCallable, Category = "Abilities")
bool TryActivateAbilityByClass(TSubclassOf<UGameplayAbility> InAbilityToActivate, bool bAllowRemoteActivation = true);
bool TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation = true);
bool TriggerAbilityFromGameplayEvent(FGameplayAbilitySpecHandle AbilityToTrigger, FGameplayAbilityActorInfo* ActorInfo, FGameplayTag Tag, const FGameplayEventData* Payload, UAbilitySystemComponent& Component);
FGameplayAbilitySpecHandle GiveAbilityAndActivateOnce(const FGameplayAbilitySpec& AbilitySpec, const FGameplayEventData* GameplayEventData);
이벤트로 게임플레이 이벤트를 활성화 할려면, 그 게임플레이 어빌리티에는 그것의 트리거가 반드시 있어야 한다. 게임플레이 태그를 부여하고 게임플레이 이벤트를 위한 옵션을 골라야 한다. 이벤트를 보내기 위해 다음의 함수를 사용한다. UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload). 이벤트로 게임플레이 이벤트를 활성화 하려면 payload with data를 보내야 한다.
게임플레이 어빌리티 트리거는 게임플레이 태그가 더해지거나 제거되었을 때, 또한 게임플레이 어빌리티를 황성화 할 수 있도록 해준다.
Note: 어빌리티를 블루프린트 이벤트안에서 활성화 할 때, 반드시 ActivateAbilityFromEvnet 노드를 사용해야 하며 standard ActivateAbility 노드는 그래프에 존재해서는 안된다. 만약 ActivateAbility 노드가 존재한다면, 그 것은 항상 ActivateAbilityFromEvent 노드를 통해서 호출될 것이다.
Note: 패시브 어빌리티와 같이 항상 돌아가는 게임플레이 어빌리티를 사용하지 않을 때는 EndAbility를 호출하는 것을 잊지마라.
locally predicted GameplayAbilities를 활성화 하는 단계:
- Owning client calls TryActivateAbility()
- Calls InternalTryActivateAbility()
- CanActivateAbility()를 호출하고 게임플레이 태그의 요구사항이 만족하는지, ASC가 코스트를 감당할 수 있는지, 어빌리티가 쿨다운 상태가 아닌지, 다른 인스턴스가 활성화 중은 아닌지를 확인한다.
- CallServerTryActivateAbility()를 호출하여 생성되는 Prediction Key를 전달한다.
- CallActivateAbility()를 호출
- Calls PreActivate() Epic refers to this as "boilerplate init stuff"
- 마지막으로 ActivateAbility()를 호출
서버에서 CallServerTryActivateAbility()를 받았을 때:
- ServerTryActivateAbility()를 호출
- InternalServerTryActivateAbility()를 호출
- InternalTryActivateAbility()를 호출
- CanActivateAbility()를 호출하고 게임플레이 태그의 요구사항이 만족하는지, ASC가 코스트를 감당할 수 있는지, 어빌리티가 쿨다운 상태가 아닌지, 다른 인스턴스가 활성화 중은 아닌지를 확인한다.
- ActivateionInfo가 성공적으로 업데이트 되고, 서버에서 OnConfirmDelegate를 브로드캐스팅할 확인이 되었다면ClientActivateAbilitySucceed()를 호출한다. 이 과정은 input confirmation과는 같지 않다.
- CallActivateAbility()를 호출
- Calls PreActivate() Epic refers to this as "boilerplate init stuff"
- Calls ActivateAbility() finally activating the ability
서버가 활성화하는데 실패했다면 ClientActivateAbilityFailed()를 호출하며 즉시 클라이언트의 게임플레이 어빌리티를 제거하며 predicted changes를 그만둔다.
4.6.4.1 Passive Abilities
패시브 게임플레이 어빌리티를 구현하기 위해서는 UGameplayAbility::OnAvartarSet()을 재정의한다. 이 함수는 게임플레이 어빌리티가 granted되었을 때 자동적으로 호출되며, AvartarActor가 셋 되며, TryActivateAbility()를 호출한다.
커스텀 게임플레이 어빌리티 클래스에 bool 값을 추가하는 것을 추천한다. 이것은 액티비티가 granted되었을 때, 값을 만족하였을 때만 실행하도록 해준다. 샘플 프로젝트에서는 패시브 아머 스택 어빌리티에서 사용한다.
패시브 게임플레이 어빌리티는 일반적으로 Net Execution Policy of Server Only를 가진다.
void UGDGameplayAbility::OnAvatarSet(const FGameplayAbilityActorInfo * ActorInfo, const FGameplayAbilitySpec & Spec)
{
Super::OnAvatarSet(ActorInfo, Spec);
if (bActivateAbilityOnGranted)
{
ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle, false);
}
}
에픽에서는 이 함수에서 패시브 어빌리티를 발동하고 BeginPlay와 같은 것들을 넣기에 좋은 위치라고 한다.
4.6.4.2 Activation Failed Tags
어빌리티는 어빌리티의 활성화가 실패한 이유를 보여주는 로직이 기본적으로 있다. 이것을 사용하기 위해서 디폴트 실패 케이스에 맞도록 게임플레이 태그를 셋업해야 한다.
이 태그를 (혹은 자신의 네이밍 컨벤션에 맞는 태그)를 프로젝트에 추가:
+GameplayTagList=(Tag="Activation.Fail.BlockedByTags",DevComment="") +GameplayTagList=(Tag="Activation.Fail.CantAffordCost",DevComment="") +GameplayTagList=(Tag="Activation.Fail.IsDead",DevComment="") +GameplayTagList=(Tag="Activation.Fail.MissingTags",DevComment="") +GameplayTagList=(Tag="Activation.Fail.Networking",DevComment="") +GameplayTagList=(Tag="Activation.Fail.OnCooldown",DevComment="")
그리고 다음을 GASDocumentation\config\DefaultGame.ini에 추가:
[/Script/GameplayAbilities.AbilitySystemGlobals] ActivateFailIsDeadName=Activation.Fail.IsDead ActivateFailCooldownName=Activation.Fail.OnCooldown ActivateFailCostName=Activation.Fail.CantAffordCost ActivateFailTagsBlockedName=Activation.Fail.BlockedByTags ActivateFailTagsMissingName=Activation.Fail.MissingTags ActivateFailNetworkingName=Activation.Fail.Networking
이제 어빌리티 활성화가 실패하면 로그 메시지나 showdebugAbilitySystem hud에 출력될 것이다.
LogAbilitySystem: Display: InternalServerTryActivateAbility. Rejecting ClientActivation of Default__GA_FireGun_C. InternalTryActivateAbility failed: Activation.Fail.BlockedByTags LogAbilitySystem: Display: ClientActivateAbilityFailed_Implementation. PredictionKey :109 Ability: Default__GA_FireGun_C
4.6.5 Canceling Abilities
어빌리티를 취소하기 위해서는 CancleAbility()를 호출하면 된다. 이 것은 EndAbility()를 호출하고 WasCancelled 파라미터를 true로 변경한다.
외부적으로 어빌리티를 취소하고 싶다면 ASC가 제공하는 몇가지 함수가 있다:
/** Cancels the specified ability CDO. */
void CancelAbility(UGameplayAbility* Ability);
/** Cancels the ability indicated by passed in spec handle. If handle is not found among reactivated abilities nothing happens. */
void CancelAbilityHandle(const FGameplayAbilitySpecHandle& AbilityHandle);
/** Cancel all abilities with the specified tags. Will not cancel the Ignore instance */
void CancelAbilities(const FGameplayTagContainer* WithTags=nullptr, const FGameplayTagContainer* WithoutTags=nullptr, UGameplayAbility* Ignore=nullptr);
/** Cancels all abilities regardless of tags. Will not cancel the ignore instance */
void CancelAllAbilities(UGameplayAbility* Ignore=nullptr);
/** Cancels all abilities and kills any remaining instanced abilities */
virtual void DestroyActiveState();
만약 Non-Instanced 게임플레이 어빌리티를 가지고 있다면 CancleAllAbilities가 정상적으로 작동되지 않는다는 것을 발견했다. 진행중 Non-Instanced 어빌리티라면 작동이 되지 않는다. CancleAbilities는 Non-Instanced 어빌리티를 다룰 수 있으며 샘플 프로젝트에서도 사용한다.(Jump 는 non-instanced 어빌리티이다.)
4.6.6 Getting Active Abilities
변수를 설정하거나 어빌리티를 취소할 때 어떻게 활성화된 어빌리티를 얻을 수 있을까? 하나 이상의 어빌리티가 같은 시간에 활성화 중일 수도 있으니 하나의 활성화된 어빌리티는 없다. 대신에 ASC의 ActivatableAbilities(granted GameplayAbilities that the ASC owns)의 리스트를 통해 찾아야 한다. 그리고 Asset과 Granted 게임플레이 태그와 맞는 것을 찾아야 한다.
UAbilitySystemComponent::GetActivatableAbilities()는 TArray<FGameplayAblitySpec>을 반환하며 여기서 찾는다.
또한 ASC는 게임플레이 어빌리티 스펙에서 하나하나 찾는 것보다 더 좋은 게임플레이 태그 컨테이너를 인자로 받아서 찾아주는 함수를 제공한다. bonlyAbilitiesThatSatisfyTagRequirements 파라미터는 게임플레이 어빌리티 스펙들 중에서 게임플레이 태그에 만족하는 것들만 반환하도록 해주며 바로 활성화 할 수 있다. 예를 들어, 2개의 기본 공격 어빌리티가 있다고 했을 때, 하나는 무기와 하나는 기본 주먹이며 무기의 존재 유무 게임플레이 태그에 따라 접근할 수 있다. 에픽의 코멘트를 더 보고싶다면 찾아보자.
UAbilitySystemComponent::GetActivatableGameplayAbilitySpecsByAllMatchingTags(const FGameplayTagContainer& GameplayTagContainer, TArray < struct FGameplayAbilitySpec* >& MatchingGameplayAbilities, bool bOnlyAbilitiesThatSatisfyTagRequirements = true)
찾고자하는 게임플레이 어빌리티 스펙에 접근했다면 IsActive()를 호출하여 확인할 수 있다.
4.6.7 Instancing Policy
게임플레이 어빌리티의 인스턴싱 정책에 따라 게임플레이 어빌리티가 어떻게 인스턴스되고 언제 활성화되는지 결정한다.
Instancing Policy | Description | Example of when to use |
Instanced Per Actor | 각 ASC는 각 게임플레이 어빌리티의 인스턴스를 하나만 가지고 있다. 어빌리티는 활성화마다 재활용된다. | 이게 가장 많이 사용하는 인스턴싱 정책일 것이다. 모든 어빌리티에 사용할 수 있으며 활성화 마다 지속성을 제공한다. 디자이너는 필요한 활성화 사이의 변수를 수동으로 재설정해야 한다. |
Instanced Per Execution | 게임플레이 어빌리티가 활성화될 때마다 새로운 게임플레이 어빌리티 인스턴스가 생성된다. | 이 정책의 장점은 매 활성화마다 변수가 리셋된다는 것이다. 이 것은 Instanced Per Actor와 비교하면 좋지않은 퍼포먼스를 보여주지만 그들은 그들이 호출될 때마다 새로운 게임플레이 어빌리티가 생성된다. 샘플프로젝트에선 이 정책을 쓰지 않는다. |
Non-Instanced | 게임플레이 오브젝트는 그들의 ClassDefaultObject에서 작동한다. 어떠한 인스턴스도 생성되지 않는다. | 이 방법은 세가지 방법 중 가장 좋은 퍼포먼스를 보여준다. 하지만 할 수 있는 것이 제한적이다. Non-Instanced 게임플레이 어빌리티는 상태를 저장할 수 없다. 이것은 동적 변수와 AbilityTask에 델리게이트 바인딩을 사용할 수 없다는 것을 의미한다. 이 정책을 사용하는데 가장 좋은 곳은 미니언의 공격처럼 아주 간단한 어빌리티이면서 자주 사용하는 곳이다. 샘플 프로젝트의 점프에서 이 정책을 사용한다. |
4.6.8 Net Execution Policy
게임플레이 어빌리티의 Net Execution 정책은 어빌리티를 어디서 돌리는지, 어떤 순서로 돌리는지 결정한다.
Net Execution Policy | Description |
Local Only | 이 정책에서 어빌리티는 본인의 클라이언트에서만 돌아간다. local cosmetic 변화에서만 사용하는데 편하다. 싱글 플레이 게임이라면 Server Only 정책을 사용해야 한다. |
Local Predicted | 이 정책에서 어빌리티는 본인의 클라이언트에서 먼저 실행되고 그 다음 서버에서 실행된다. 서버 버전에선 클라이언트가 잘못 predicted한 내용을 수정한다. See Prediction |
Server Only | 이 정책에선 어빌리티가 서버에서만 돌아간다. 패시브 어빌리티가 일반적으로 Server Only를 사용한다. 싱글 플레이 게임이라면 이 정책을 사용한다. |
Server Initiable | 이 정책에서 어빌리티는 서버에서 먼저 실행되고 그 다음 본인의 클라이언트에서 실행된다. 개인적으로 이 걸 사용해 본적 없다. |
4.6.9 Ability Tags
게임플레이 어빌리티는 로직이 내장된 게임플레이 태그 컨테이너와 함께 제공된다. 이러한 게임플레이 태그는 replicate되지 않는다.
GameplayTag Container | Description |
Abilty Tags | 단순히 어빌리티의 설명을 위한 게임플레이 태그이다. |
Cancle Abilities with Tag | 어빌리티 태그 안에 이 게임플레이 태그를 가지고 있는 어빌리티들은 이 어빌리티가 활성화 되었을 경우 취소된다. |
Block Abilities with Tag | 어빌리티 태그 안에 이 게임플레이 태그를 가지고 있는 어빌리티들은 이 어빌리티가 활성화 중일 경우 활성화가 막힌다. |
Activation Owned Tags | 이 어빌리티가 활성화 중일 때, 어빌리티의 오너는 설정한 게임플레이 태그가 주어진다. replicate가 되지않는 다는 것을 기억해라 |
Activation Required Tags | 오너가 이 태그를 모두 가지고 있을 경우에 활성화 할 수 있다. |
Activation Blocked Tags | 오너가 이 태그들 중 하나라도 가지고 있을 경우 활성화를 할 수 없다. |
Source Required Tags | 소스가 이 태그를 모두 가지고 있을 경우에만 활성화 할 수 있다. 소스 게임플레이 태그는 오직 어빌리티가 이벤트에 의해서만 트리거되었을 때 설정할 수 있다. |
Source Blocked Tags | 소스가 이 태그들 중 하나라도 가지고 있을 경우 활성화를 할 수 없다. 소스 게임플레이 태그는 오직 어빌리티가 이벤트에 의해서만 트리거되었을 때 설정할 수 있다. |
Target Required Tags | 타겟이 이 태그를 모두 가지고 있을 경우에만 활성화 할 수 있다. 타겟 게임플레이 태그는 오직 어빌리티가 이벤트에 의해서만 트리거되었을 때 설정할 수 있다. |
Target Blocked Tags | 타겟이 이 태그들 중 하나라도 가지고 있을 경우 활성화를 할 수 없다. 타겟 게임플레이 태그는 오직 어빌리티가 이벤트에 의해서만 트리거되었을 때 설정할 수 있다. |
4.6.10 Gameplay Ability Spec
게임플레이 어빌리티 스펙은 게임플레이 어빌리티가 부여된 후에 ASC에 존재하며 활성화 가능한 게임플레이 어빌리티 -게임플레이 어빌리티 클래스, 레벨, 인풋 바인딩 및 런타임 상태를 정의하며 게임플레이 어빌리티 클래스와는 분리되어 있어야 한다.
서버에 게임플레이 어빌리티 스펙이 부여되면 서버는 소유한 클라이언트가 게임플레이 어빌리티 스펙을 활성화할 수 있도록 replicate한다.
게임플레이 어빌리티 스펙을 활성화하면 어빌리티의 인스턴싱 정책에 따라 인스턴스를 생성한다.
4.6.11 Passing Data to Abilities
게임플레이 어빌리티의 일반적인 패러다임은 Activate -> Generate Data -> Apply -> End이다. 때때로 기존의 데이터를 처리한는 것이 필요하다. GAS는 우리의 게임플레이 어빌리티 안에 외부의 데이터를 집어넣는 몇 가지 옵션을 제공한다.
Method | Description |
Activate GameplayAbility by Event | 게임플레이 어빌리티를 데이터의 적재량을 포함하여 이벤트를 통해 활성화한다. 이벤트의 적재량은 local predicted 게임플레이 어빌리티를 위해 클라이언트에서 서버로 replicate된다. 기존 변수에 맞지 않는 임의의 데이터에 두 개의 Optional Object 또는 TargetData를 사용한다. 이 방법의 단점은 입력 바인딩으로 기능을 활성화하지 못하게 한다는 것이다. (이벤트를 이용해야 하기 때문) 게임플레이 어빌리티를 이벤트로 활성화 하려면, 게임플레이 어빌리티는 게임플레이 어빌리티안에 트리거를 세팅해야만 한다. 게임플레이 태그를 설정하고 게임플레이 이벤트 옵션을 선택한다. 이벤트를 보내기 위해 다음의 함수를 사용한다. UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload) |
Use WaitGameplayEvent AbilityTask | 게임플레이 어빌리티가 활성화된 후에 데이터 페이로드와 함께 이벤트를 듣도록 WaitGameplayEvent AbilityTask를 사용한다. 이벤트 페이로드 및 전송 프로세스는 어빌리티를 이벤트로 활성화 하는 것과 같다. 이 방법의 단점은 어빌리티 태스크로 이벤트는 replicate되지 않는다는 것이고 Local Only나 Server Only인 어빌리티만 사용해야한다는 것이다. 잠재적으로 이벤트 페이로드를 replicate하는 본인만의 어빌리티 태스크를 만들어야 할 수 있다. |
Use TargetData | 커스텀 TargetData 구조체는 클라이언트와 서버사이에 임의의 데이터를 보내는데 좋은 방법이다. |
Store Data on the OwnerActor or AvartarActor | OwnerActor나 AvartarActor 혹은 참조가능한 다른 오브젝트에 replicate된 변수를 사용하는 방법이다. 이 방법은 인풋 바인드를 통해 활성화된 어빌리티와 함께 돌릴 수 있는 가장 유연한 방법이다. 그러나 사용할 때, replication에서 데이터의 동기화를 보장하지 않는다. 그러니 명심해야한다. replicated 변수를 설정할 때 그리고 즉시 게임플레이 어빌리티를 활성화 할 때마다 받는 쪽에서 잠재적인 패킷 손실이 일어날 수 있고 순서를 보장하지 않는다는 것을 미리 알아야 한다. |
4.6.12 Ability Cost and Cooldown
어빌리티는 코스트와 쿨다운이라는 기능적인 옵션이 있다. 코스트는 어빌리티를 활성화하기위한 미리 설정한 어트리뷰트의 양으로 Instant 게임플레이 이펙트로 구현해야 한다. 쿨다운은 어빌리티를 지정한 시간에 다시 사용하는 것을 막는 타이머로 Duration 게임플레이 이펙트로 구현해야 한다.
어빌리티가 UGameplayAbility::Activate()를 호출하기 전에, UGameplayAbility::CanActivateAbility()를 호출한다. 이 함수는 본인의 ASC가 코스트와 쿨다운 중인지를 확인하는 함수이다.
어빌리티가 Activate()를 호출한 후에, UGameplayAbility::CommitAbility()를 통해 코스트와 쿨다운을 임의의 시간에 커밋할 수 있다. CommitAbility는 UGmaeplayAbility::CommitCost()와 UGameplayAbility::CommitCooldown()을 호출한다. 디자이너는 코스트와 쿨다운을 같은 시간에 커밋하는 것을 원하지 않을 때에는 분리해서 호출하면 된다. 코스트와 쿨 다운을 커밋하는 것은 CheckCost()와 CheckCooldown()을 호출한다. 이 때가 코스트와 쿨다운과 연관되어 어빌리티가 실패할 수 있는 마지막 기회이다. owning's ASC의 어트리뷰트는 잠재적으로 어빌리티가 활성화되고 난 후에 변할 수 있기에 커밋할 때 코스트를 만족하지 못할 수 있다. 코스트와 쿨다운을 커밋하는 것은 커밋할 때 prediction key가 유효한 경우 locally predicte될 수 있다.
4.6.13 Leveling Up Abilities
어빌리티를 레벨업시키는 데는 2가지의 흔한 방법이 있다.
Level Up Method | Description |
Ungrant and Regrant at the New Level | 어빌리티를 ASC에서 제거하고 서버에서 다음 레벨의 어빌리티를 다시 얻는 것이다. 그 때 어빌리티가 실행중이었다면 삭제한다. |
Increase the GameplayAbilitySpec's Level | 서버에서 게임플레이 어빌리티 스펙을 찾고 레벨을 올리고 소유 클라이언트에서 replicate할 수 있도록 dirty 표시를 한다. 이 방법에서는 어빌리티가 실행중이었어도 삭제하지 않는다. |
2가지의 주된 차이점은 레벨업 시 활성화된 어빌리티를 취소하고 싶은지의 차이이다. 어빌리티에 따라 2가지를 다르게 쓰면 된다. 2가지중 어떤 방법을 쓸지는 어빌리티에 bool값을 추가하는 것으로 표시하는 걸 추천한다.
3.6.14 Ability sets
GameplayAbilitySets는 UDataAsset클래스이다. 이것은 캐릭터의 로직인 스타트업 게임플레이 어빌리티 리스트의 인풋 바인딩을 가지고 있다. 하위 클래스는 추가 로직이나 프로퍼티를 포함할 수 있다. 파라곤에서는 한 영웅마다 그들의 주어진 어빌리티 모두를 포함하는 어빌리티 셋을 가지고 있다.
지금까지 한 것을 보면 별로 필요없는 기능이였다. 샘플프로젝트는 모든 게임플레이 어빌리티 셋의 기능을 GDCharacterBase와 Base의 서브 클래스에서 다룬다.
4.6.15 Ability Batching
전통적인 게임플레이 어빌리티 라이프사이클은 클라이언트에서부터 서버까지 최소 2개 혹은 3개의 RPC를 포함한다.
- CallServerTryActivateAbility()
- ServerSetReplicatedTargetData() (Optional)
- ServerEndAbility()
만약 게임플레이 어빌리티가 한 프레임에 원자적으로 그룹되어 모든 행동을 수행한다면 우리는 하나의 RPC에 2개 혹은 3개의 RPC를 통합하여 워크플로우를 최적화할 수 있다. GAS는 이 RPC 최적화를 Ability Batching이라고 부른다. 이러한 경우의 흔한 예시는 히트스캔 총을 사용할 때 Ability Batching을 사용하는 것이다. 히트스캔 총을 활성화하고, 라인 트레이스를 실시하고, TargetData를 서버에 보내고, 어빌리티를 종료하는 모든 과정을 한 프레임으로 하나의 원자적 그룹으로 설정한다. GASShooter의 샘플 프로젝트에서 히트스캔 총이 이러한 테크닉을 보여준다.
반자동 소총이 CallServerTryActivateAbility(), ServerSetReplicatedTargetData()(the buillet hit result)와 ServerEndAbility() 3개의 RPC를 하나의 RPC로 처리하는 최고의 시나리오 케이스이다.
자동, Burst 소총은 첫번째 탄환에서 CallServerTryActivateAbility()와 ServerSetReplicatedTargetData() 2개의 RPC를 하나의 RPC로 처리한다. 이후의 각 총알은 자체 ServerSetReplicatedTargetData()를 사용한다. 최종적으로, ServerEndAbility()는 사격을 중지했을 때, 분리된 RPC로 보내진다. 이것은 첫번째 총알에 두 개 대신 한 개의 RPC만 저장하는 최악의 시나리오이다. 이 시나리오는 클라이언트로부터 서버에 EventPayload를 포함하여 총알의 TargetData를 보내는 Gameplay Event를 지나서 어빌리티를 활성화하는 것으로 구현될 수 있다. 후자의 접근 방식의 단점은 TargetData가 어빌리티의 외부에서 생성되어야 하는 반면 어빌리티 내부에서 TargetData를 batch가 일어난다는 점이다.
Ability Batching은 기본적으로 ASC에서 비활성화 되어 있다. 활성화하기 위해선, ShouldDoServerAbilityRPCBatch()가 true를 반환하도록 재정의해야 한다.
virtual bool ShouldDoServerAbilityRPCBatch() const override { return true; }
이제 Ability Batching이 가능하다. 어빌리티를 실행하기 전에 batch를 원한다면 먼저 FScopedServerAbilityRPCBatcher 구조체를 반드시 만들어야 한다. 이것은 특별한 구조체로 범위내에서 이에 따르는 어빌리티를 batch하려고 시도한다. FScopedServerAbilityRPCBatcher가 한번 범위를 벗어나면 어떠한 활성화된 어빌리티도 batch를 시도할 수 없다. FScopedServerAbilityRPCBatcher는 RPC를 전송하는 호출을 가로채고 대신 메세지를 batch 구조로 패킹하여 batch할 수 있는 각 함수의 특수 코드를 포함하여 작동한다. FScopedServerAbilityRPCBatcher가 범위를 벗어날 때, 자동적으로 이 batch구조를 UAbilitySystemComponent::EndServerAbilityRPCBatcher()에서 RPC한다. 서버에서 UAbilitySystemComponent::ServerAbilityRPCBatch_Internal(FServerAbilityRPCBatch& BatchInfo)에서 batch RPC를 받는다. BatchInfo 파라미터는 플래그를 포함하는데 어빌리티가 끝나야 하거나 인풋이 활성화 시간에 눌려졌거나 TargetData가 포함되어 있는지의 데이터이다. 이 함수는 batch가 올바르게 작동하고 있는지 확인하는데 브레이크 포인트를 두는데 좋은 함수이다. 또는 특별한 어빌리티의 batch 로그를 보기위해서 AbilitySystem.ServerRPCBatching.Log 1을 사용한다.
이 메커니즘은 C++에서만 수행할 수 있으며 FGameplayAbilitySpecHandle을 통해서만 어빌리티를 활성화할 수 있다.
bool UGSAbilitySystemComponent::BatchRPCTryActivateAbility(FGameplayAbilitySpecHandle InAbilityHandle, bool EndAbilityImmediately)
{
bool AbilityActivated = false;
if (InAbilityHandle.IsValid())
{
FScopedServerAbilityRPCBatcher GSAbilityRPCBatcher(this, InAbilityHandle);
AbilityActivated = TryActivateAbility(InAbilityHandle, true);
if (EndAbilityImmediately)
{
FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(InAbilityHandle);
if (AbilitySpec)
{
UGSGameplayAbility* GSAbility = Cast<UGSGameplayAbility>(AbilitySpec->GetPrimaryInstance());
GSAbility->ExternalEndAbility();
}
}
return AbilityActivated;
}
return AbilityActivated;
}
GASShooter에서는 반자동, 자동 소총에 같은 batched GameplayAbility를 재사용하며 EndAbility()를 절대로 직접적으로 호출하지 않는다(EndAbility()는 현재 firemode를 기반으로 플레이어 입력과 batch된 어빌리티에 대한 호출을 관리하는 local-only 어빌리티에 의해 어빌리티 밖에서 처리된다). 모든 RPC는 FScopedServerAbilityRPCBatcher의 범위내에서 이루어져야하기 때문에 EndAbilityImmediately 파라미터를 추가했다. 그렇게 하여 controlling/managing local-only can specify whether this ability should batch the EndAbility() call (semi-automatic), or not batch the EndAbility() call (full-automatic) and the EndAbility() call will happen sometime later in its own RPC.
GASShooter에서 앞서 언급한 local-only 어빌리티가 batched 어빌리티를 트리거하는데 사용하는 batching 어빌리티를 제공하는 불루프린트가 있다.
4.6.16 Net Security Policy
게임플레이 어빌리티의 NetSecurityPolicy는 네트워크의 어디에서 어빌리티가 실행되어야 하는지 결정한다. 클라이언트로부터 제한된 기능을 막는데 사용된다.
NetSecurityPolicy | Description |
ClientOrServer | 보안성이 필요없을 경우. 클라이언트나 서버나 실행시킬 수 있으며 어빌리티를 자유롭게 제거할 수 있다. |
ServerOnlyExecution | 이 어빌리티의 실행을 요청하는 클라이언트는 서버에 의해 무시된다. 클라이언트는 여전히 이 어빌리티의 취소나 end를 요청할 수 있다. |
ServerOnlyTermination | 이 어빌리티의 제거를 요청하는 클라이언트는 서버에 의해 무시된다. 클라이언트는 여전히 실행을 요청할 수 있다. |
ServerOnly | 서버가 이 어빌리티의 실행과 제거를 제어한다. 클라이언트의 어떠한 요청도 무시된다. |
'Unreal Engine > Gameplay Ability System' 카테고리의 다른 글
[UE] Gameplay Ability System -7- Gameplay Cue (0) | 2023.06.02 |
---|---|
[UE] Gameplay Ability System -6- Ability Task (0) | 2023.05.30 |
[UE] Gameplay Ability System -4- Gameplay Effects (0) | 2023.05.28 |
[UE] Gameplay Ability System -3- Attribute Set (0) | 2023.05.26 |
[UE] Gameplay Ability System -2- Attributes (0) | 2023.05.24 |