본문 바로가기
둥지/Unreal

Unreal GAS(GameplayAbilitySystem) Documentation 번역글 3부

by 까닭 2023. 6. 6.

4.6 GameplayAbility

4.6.1 GameplayAbility 정의

GameplayAbility(GA)는 엑터가 게임에서 할 수 있는 모든 액션 또는 스킬입니다. 예를 들어 전력 질주나 총을 쏘는 등 한 번에 두 개 이상의 GameplayAbility를 활성화할 수 있습니다. Blueprint 또는 C++로 만들 수 있습니다.


GameplayAbility의 예시입니다:

  • 점프
  • 질주
  • 총 쏘기
  • 특정 초마다 수동적으로 공격 차단하기
  • 물약 사용
  • 문 열기
  • 자원 수집
  • 건물 건설

 

GameplayAbility로 구현해서는 안 되는 것들:

  • 기본적인 움직임 입력
  • UI와의 상호작용 - GameplayAbility를 사용하여 상점 아이템 구매

 

이는 규칙이 아니라 권장 사항일 뿐입니다. 설계와 구현은 다를 수 있습니다. GameplayAbility에는 Attribute 변경량을 수정하거나 GameplayAbility의 기능을 변경할 수 있는 레벨이 기본 기능으로 제공됩니다.

GameplayAbility는 Net Execution Policy에 따라 소유 클라이언트 또는 서버에서 실행되지만 시뮬레이션된 프록시에서는 실행되지 않습니다. Net Execution Policy은 GameplayAbility가 로컬에서 예측될지 여부를 결정합니다. 여기에는 선택적 비용 및 쿨타임 GameplayEffect에 대한 기본 동작이 포함됩니다. GameplayAbility는 이벤트 대기, 속성 변경 대기, 플레이어가 타겟을 선택할 때까지 기다리거나 루트 모션 소스로 캐릭터를 이동하는 등 시간이 지남에 따라 발생하는 동작에 AbilityTask를 사용합니다. 시뮬레이션된 클라이언트는 GameplayAbility를 실행하지 않습니다. 대신 서버가 어빌리티를 실행하면 시뮬레이션된 프록시에서 시각적으로 재생해야 하는 것(애니메이션 몽타주 등)은 전부 리플리케이트되거나 사운드나 파티클 같은 외형적인 것들은 AbilityTask 또는 GameplayCue를 통해 RPC 처리됩니다.

모든 GameplayAbility는 게임플레이 로직으로 ActivateAbility() 함수를 오버라이드합니다. GameplayAbility가 완료되거나 취소될 때 실행되는 EndAbility()에 추가 로직을 추가할 수 있습니다.

간단한 GameplayAbility의 순서도입니다:

좀 더 복잡한 GameplayAbility의 순서도입니다:

서로 상호작용(활성화, 취소 등)하는 여러 GameplayAbility를 사용하여 복잡한 어빌리티를 구현할 수 있습니다.

 

4.6.1.1 Replication Policy

이 옵션은 사용하지 마세요. 이름에 오해의 소지가 있으며 필요하지 않습니다. GameplayAbilitySpec는 기본적으로 서버에서 소유 클라이언트로 리플리케이트됩니다. 위에서 언급했듯이 GameplayAbility는 시뮬레이션된 프록시에서 실행되지 않습니다. AbilityTask와 GameplayCue를 사용하여 시뮬레이션된 프록시에 시각적 변경 사항을 리플리케이트하거나 RPC합니다. 에픽 게임즈의 데이브 라티가 향후 해당 옵션을 제거하고 싶다는 의사를 밝혔습니다.

 

4.6.1.2 Server Respects Remote Ability Cancellation

이 옵션은 종종 문제를 일으킵니다. 즉, 클라이언트의 GameplayAbility가 취소 또는 자연 완료로 인해 종료되면 서버의 버전이 완료되었는지 여부에 관계없이 강제로 종료됩니다. 후자의 문제는 특히 지연 시간이 긴 플레이어가 사용하는 로컬 예측 GameplayAbility의 경우 중요한 문제입니다. 일반적으로 이 옵션을 비활성화하는 것이 좋습니다.

 

4.6.1.3 Replicate Input Directly

이 옵션을 설정하면 입력 누르기 및 놓기 이벤트를 항상 서버에 리플리케이트합니다. 에픽은 이 옵션을 사용하지 않고 대신 기존 입력 관련 AbilityTask에 내장된 일반 리플리케이트 이벤트를 사용하는 것을 권장합니다(입력이 ASC에 바인딩된 경우).

 

에픽 게임즈 주석:

/** 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 ASC에 입력 바인딩

ASC를 사용하면 입력 액션을 직접 바인딩하고 해당 입력을 부여할 때 해당 입력을 GameplayAbilities에 할당할 수 있습니다. GameplayAbility에 할당된 입력 액션은 GameplayTag 요구 사항이 충족되면 해당 GameplayAbility를 눌렀을 때 자동으로 활성화됩니다. 할당된 입력 액션은 입력에 반응하는 기본 제공 AbilityTask를 사용하는 데 필요합니다.

GameplayAbility를 활성화하기 위해 할당된 입력 동작 외에도 ASC는 일반 확인 및 취소 입력도 허용합니다. 이러한 특수 입력은 AbilityTask가 타겟 액터와 같은 것을 확인하거나 취소하는 데 사용됩니다.

입력을 ASC에 바인딩하려면 먼저 입력 액션 이름을 바이트 단위로 변환하는 열거형을 만들어야 합니다. 이 열거형 이름은 프로젝트 설정에서 입력 액션에 사용된 이름과 정확히 일치해야 합니다. 표시 이름은 중요하지 않습니다.

샘플 프로젝트에서:

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가 캐릭터에 있는 경우, SetupPlayerInputComponent()에 ASC에 바인딩하는 함수를 포함하세요:

// Bind to AbilitySystemComponent
FTopLevelAssetPath AbilityEnumAssetPath = FTopLevelAssetPath(FName("/Script/GASDocumentation"), FName("EGDAbilityInputID"));
AbilitySystemComponent->BindAbilityActivationToInputComponent(PlayerInputComponent, FGameplayAbilityInputBinds(FString("ConfirmTarget"),
    FString("CancelTarget"), AbilityEnumAssetPath, static_cast<int32>(EGDAbilityInputID::Confirm), static_cast<int32>(EGDAbilityInputID::Cancel)));

ASC가 PlayerState에 있는 경우, PlayerState가 아직 클라이언트에 리플리케이트되지 않았을 수 있는 잠재적 경합 조건이 SetupPlayerInputComponent() 내부에 있습니다. 따라서 SetupPlayerInputComponent() 및 OnRep_PlayerState()에서 입력에 바인딩을 시도하는 것이 좋습니다. 플레이어 컨트롤러가 클라이언트에게 InputComponent를 생성하는 ClientRestart() 호출을 지시하기 전에 PlayerState가 리플리케이트될 때 액터의 InputComponent가 널이 되는 경우가 있을 수 있으므로 OnRep_PlayerState() 자체만으로는 충분하지 않습니다. 샘플 프로젝트는 프로세스를 게이팅하는  bool을 사용하여 두 위치 모두에서 바인딩을 시도하여 실제로는 입력을 한 번만 바인딩하는 것을 보여줍니다.

💡 NOTE: 샘플 프로젝트에서 열거형의 Confirm 및 Cancel은 프로젝트 설정의 입력 액션 이름(ConfirmTarget 및 CancelTarget)과 일치하지 않지만, BindAbilityActivationToInputComponent()에서 이들 사이의 매핑을 제공했습니다. 매핑을 제공하기 때문에 특별하며 일치할 필요는 없지만 일치할 수 있습니다. 열거형의 다른 모든 입력은 프로젝트 설정의 입력 액션 이름과 일치해야 합니다. 하나의 입력으로만 활성화되는 GameplayAbility(MOBA처럼 항상 같은 '슬롯'에 존재)의 경우, 저는 해당 입력을 정의할 수 있는 변수를 UGameplayAbility 서브클래스에 추가하는 것을 선호합니다. 그러면 어빌리티를 부여할 때 ClassDefaultObject에서 이를 읽을 수 있습니다.

 

4.6.2.1 GameplayAbility를 활성화하지 않고 입력에 바인딩

GameplayAbility가 입력을 눌렀을 때 자동으로 활성화되지 않도록 하되 여전히 입력에 바인딩하여 AbilityTasks와 함께 사용하려면, 기본값이 true인 새로운 부울 변수를 UGameplayAbility 서브클래스에 추가하고 UAbilitySystemComponent::AbilityLocalInputPressed()를 오버라이드할 수 있습니다.

void UGSAbilitySystemComponent::AbilityLocalInputPressed(int32 InputID)
{
    // Consume the input if this InputID is overloaded with GenericConfirm/Cancel and the GenericConfim/Cancel callback is bound
    if (IsGenericConfirmInputBound(InputID))
    {
        LocalInputConfirm();
        return;
    }

    if (IsGenericCancelInputBound(InputID))
    {
        LocalInputCancel();
        return;
    }

    // ---------------------------------------------------------

    ABILITYLIST_SCOPE_LOCK();
    for (FGameplayAbilitySpec& Spec : ActivatableAbilities.Items)
    {
        if (Spec.InputID == InputID)
        {
            if (Spec.Ability)
            {
                Spec.InputPressed = true;
                if (Spec.IsActive())
                {
                    if (Spec.Ability->bReplicateInputDirectly && IsOwnerActorAuthoritative() == false)
                    {
                        ServerSetInputPressed(Spec.Handle);
                    }

                    AbilitySpecInputPressed(Spec);

                    // Invoke the InputPressed event. This is not replicated here. If someone is listening, they may replicate the InputPressed event to the server.
                    InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, Spec.Handle, Spec.ActivationInfo.GetActivationPredictionKey());
                }
                else
                {
                    UGSGameplayAbility* GA = Cast<UGSGameplayAbility>(Spec.Ability);
                    if (GA && GA->bActivateOnInput)
                    {
                        // Ability is not active, so try to activate it
                        TryActivateAbility(Spec.Handle);
                    }
                }
            }
        }
    }
}

 

4.6.3 어빌리티 부여

ASC에 GameplayAbility를 부여하면 해당 GameplayAbility가 활성화 가능한 어빌리티 목록에 추가되어 GameplayTag 요구 사항을 충족하는 경우 마음대로 활성화할 수 있습니다. 서버에서 GameplayAbility를 부여하면 소유 클라이언트에 GameplayAbilitySpec을 자동으로 리플리케이트합니다. 다른 클라이언트/시뮬레이션된 프록시는 GameplayAbilitySpec을 받지 않습니다.

샘플 프로젝트는 게임 시작 시 읽어들여 부여하는 Character 클래스에 TArray<TSubclassOf<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;
}

이러한 GameplayAbility를 부여할 때는 UGameplayAbility 클래스, 어빌리티 레벨, 바인딩된 입력, 소스 오브젝트 또는 누가 이 ASC에 이 GameplayAbility를 부여했는지가 포함된 GameplayAbilitySpec를 생성합니다.

 

4.6.4 어빌리티 활성화

GameplayAbility에 입력 액션이 할당된 경우, 입력이 눌려지고 해당 GameplayTag 요건을 충족하면 자동으로 활성화됩니다. 이것이 항상 GameplayAbility를 활성화하는 바람직한 방법은 아닐 수 있습니다. ASC는 GameplayTag, GameplayAbility 클래스, GameplayAbilitySpecHandle, 이벤트에 의해 GameplayAbility를 활성화하는 네 가지 다른 메서드를 제공합니다. 이벤트로 GameplayAbility를 활성화하면 이벤트와 함께 데이터 페이로드를 전달할 수 있습니다.

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);

이벤트별로 GameplayAbility를 활성화하려면 GameplayAbility에 트리거가 설정되어 있어야 합니다.GameplayTag를 할당하고 GameplayEvent에 대한 옵션을 선택합니다. 이벤트를 전송하려면 UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload) 함수를 사용합니다. 이벤트로 GameplayAbility를 활성화하면 데이터가 포함된 페이로드를 전달할 수 있습니다. GameplayAbilityTrigger를 사용하면 GameplayTag가 추가되거나 제거될 때 GameplayAbility를 활성화할 수도 있습니다.

💡Note: 블루프린트에서 이벤트에서 GameplayAbility를 활성화할 때는 ActivateAbilityFromEvent 노드를 사용해야 하며, 표준 ActivateAbility 노드는 그래프에 존재할 수 없습니다. ActivateAbility 노드가 존재하는 경우, 항상 ActivateAbilityFromEvent 노드를 통해 호출됩니다.
💡Note: 항상 패시브 어빌리티처럼 실행되는 GameplayAbility가 없는 한, GameplayAbility가 종료되어야 할 때 EndAbility()를 호출하는 것을 잊지 마세요.

 

로컬로 예측된 GameplayAbility의 활성화 시퀀스입니다:

  1. 소유 클라이언트가 TryActivateAbility()를 호출합니다.
  2. InternalTryActivateAbility()를 호출합니다.
  3. CanActivateAbility()를 호출하여 GameplayTag 요건 충족 여부, ASC가 비용을 감당할 수 있는지, GameplayTag쿨타임 중이 아닌지, 현재 활성화된 다른 인스턴스가 없는지 반환합니다.
  4. CallServerTryActivateAbility()를 호출하고 생성한 예측 키를 전달합니다.
  5. CallActivateAbility()를 호출합니다.
  6. 에픽 게임즈는 이를 "boilerplate init stuff"이라고 부릅니다.
  7. ActivateAbility()를 호출하여 최종적으로 어빌리티를 활성화합니다.

서버가 CallServerTryActivateAbility()를 수신합니다.

  1. ServerTryActivateAbility()를 호출합니다.
  2. InternalServerTryActivateAbility()을 호출합니다.
  3. InternalTryActivateAbility()를 호출합니다.
  4. CanActivateAbility()를 호출하여 GameplayTag 요건 충족 여부, ASC가 비용을 감당할 수 있는지, GameplayTag쿨타임 중 아닌지, 현재 활성화된 다른 인스턴스가 없는지 반환합니다.
  5. 서버에 의해 활성화가 확인되었다는 활성화 정보를 업데이트하고 OnConfirmDelegate 델리게이트를 브로드캐스트하도록 알리는 데 성공하면 ClientActivateAbilitySucceed()를 호출합니다. 이는 입력 확인과 동일하지 않습니다.
  6. CallActivateAbility()를 호출합니다.
  7. 에픽 게임즈는 이를 "boilerplate init stuff"이라고 부릅니다.
  8. ActivateAbility()를 호출하여 최종적으로 어빌리티를 활성화합니다.

서버가 언제든지 활성화에 실패하면 ClientActivateAbilityFailed()를 호출하여 클라이언트의 GameplayAbility를 즉시 종료하고 예측된 변경 사항을 취소합니다.

 

4.6.4.1 패시브 어빌리티

자동으로 활성화되고 지속적으로 실행되는 패시브 게임플레이 어빌리티를 구현하려면, 게임플레이 어빌리티가 부여되고 아바타 액터가 설정될 때 자동으로 호출되는 UGameplayAbility::OnAvatarSet()을 오버라이드하고 TryActivateAbility()를 호출하면 됩니다.

GameplayAbility 가 부여될 때 활성화할지 여부를 지정하는 bool을 커스텀 UGameplayAbility 클래스에 추가하는 것이 좋습니다. 샘플 프로젝트에서는 패시브 방어구 스태킹 어빌리티에 대해 이렇게 합니다.

패시브 게임플레이 어빌리티는 일반적으로 Net Execution Policy이 서버 전용입니다.

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 활성화 실패 태그

어빌리티에는 어빌리티 활성화가 실패한 이유를 알려주는 기본 로직이 있습니다. 이 기능을 활성화하려면 기본 실패 사례에 해당하는 GameplayTag를 설정해야 합니다.


프로젝트에 이러한 태그(또는 고유한 명명 규칙)를 추가하세요:

+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

이제 어빌리티 활성화가 실패할 때마다 해당 GameplayTag가 출력 로그 메시지에 포함되거나 showdebug AbilitySystem 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 어빌리티 취소

내부에서 GameplayAbility를 취소하려면 CancelAbility()를 호출합니다. 그러면 EndAbility()가 호출되고 해당 WasCancelled 파라미터가 true로 설정됩니다.
외부에서 GameplayAbility를 취소하려면 ASC에서 몇 가지 함수를 제공합니다:

/** 지정한 Ability CDO를 취소합니다. */
void CancelAbility(UGameplayAbility* Ability);	

/** SpecHandle로 전달된 어빌리티를 취소합니다. 재활성화된 어빌리티 중 핸들을 찾지 못하면 아무 일도 일어나지 않습니다. */
void CancelAbilityHandle(const FGameplayAbilitySpecHandle& AbilityHandle);

/** 지정된 태그가 있는 모든 어빌리티를 취소합니다. 무시 인스턴스를 취소하지 않습니다. */
void CancelAbilities(const FGameplayTagContainer* WithTags=nullptr, const FGameplayTagContainer* WithoutTags=nullptr, UGameplayAbility* Ignore=nullptr);

/** 태그에 관계없이 모든 어빌리티를 취소합니다. 무시 인스턴스를 취소하지 않습니다. */
void CancelAllAbilities(UGameplayAbility* Ignore=nullptr);

/** 모든 어빌리트를 취소하고 남은 인스턴스 능력을 모두 처치합니다. */
virtual void DestroyActiveState();
💡Note: 인스턴스화되지 않은 GameplayAbility가 있는 경우 CancelAllAbilities가 제대로 작동하지 않는 것 같습니다. 비인스턴스 GameplayAbility에 부딪혀서 포기하는 것 같습니다. 취소 어빌리티는 인스턴스화되지 않은 GameplayAbility를 더 잘 처리할 수 있으며 샘플 프로젝트에서는 이를 사용합니다(점프는 인스턴스화되지 않은 GameplayAbility입니다). 마일리지는 다를 수 있습니다.

 

4.6.6 활성화된 어빌리티 얻기

초보자 분들이 종종 "활성화된 어빌리티를 어떻게 얻을 수 있나요?"라고 묻습니다. 아마도 변수를 설정하거나 취소하기 위해서일 것입니다. 한 번에 하나 이상의 GameplayAbility를 활성화할 수 있으므로 하나의 "활성 어빌리티"는 없습니다. 대신 ASC의 ActivatableAbilities(ASC가 소유한 부여된 GameplayAbility) 목록을 검색하여 찾고자 하는 에셋 또는 부여된 GameplayTag와 일치하는 것을 찾아야 합니다.


UAbilitySystemComponent::GetActivatableAbilities()는 반복처리할 수 있는 TArray<FGameplayAbilitySpec>을 반환합니다. ASC에는 GameplayAbilitySpec 목록을 수동으로 반복처리하는 대신 검색을 지원하기 위해 GameplayTagContainer를 파라미터로 받는 또 다른 헬퍼 함수도 있습니다. bOnlyAbilitiesThatSatisfyTagRequirements 파라미터는 GameplayTag 요구사항을 충족하고 지금 활성화할 수 있는 GameplayAbilitySpec만 반환합니다. 예를 들어, 무기를 사용하는 기본 공격 GameplayAbility와 맨주먹을 사용하는 기본 공격 GameplayAbility가 두 개 있을 수 있으며, 무기를 장착했는지 여부에 따라 올바른 GameplayAbility가 활성화되는 GameplayTag 요건을 충족합니다. 자세한 내용은 이 함수에 대한 에픽 게임즈의 주석을 참조하세요.

UAbilitySystemComponent::GetActivatableGameplayAbilitySpecsByAllMatchingTags(const FGameplayTagContainer& GameplayTagContainer, TArray < struct FGameplayAbilitySpec* >& MatchingGameplayAbilities, bool bOnlyAbilitiesThatSatisfyTagRequirements = true)

찾고 있는 FGameplayAbilitySpec을 얻으면 IsActive()를 호출하면 됩니다.

 

4.6.7 Instancing Policy

GameplayAbility의 Instancing Policy는 활성화 시 GameplayAbility의 인스턴스화 여부와 방법을 결정합니다.

Instancing Policy 설명 및 사용 시기 예시:

Instancing Policy 내용 사용 예시
Instanced Per Actor 각 ASC에는 활성화 사이에 재사용되는 게GameplayAbility Instance 하나만 있습니다. 가장 많이 사용하는 Instancing Policy일 것입니다. 모든 어빌리티에 사용할 수 있으며 활성화 간에 지속성을 제공합니다. 디자이너는 활성화 사이에 필요한 모든 변수를 수동으로 재설정할 책임이 있습니다.
Instanced Per Execution GameplayAbility가 활성화될 때마다 GameplayAbility의 새 인스턴스가 생성됩니다. GameplayAbility의 장점은 활성화할 때마다 변수가 리셋된다는 점입니다. 활성화할 때마다 새 GameplayAbility를 스폰하므로 액터별 인스턴스보다 퍼포먼스가 떨어집니다. 샘플 프로젝트는 이 중 어느 것도 사용하지 않습니다.
Non-Instanced GameplayAbility는 해당 클래스 디폴트 오브젝트에서 작동합니다. 인스턴스가 생성되지 않습니다. 이 세 가지 중 퍼포먼스가 가장 좋지만, 할 수 있는 작업이 가장 제한적입니다. 인스턴스화되지 않은 GameplayAbility는 상태를 저장할 수 없으므로 동적 변수도 없고 AbilityTask 델리게이트에 바인딩할 수도 없습니다. 이 어빌리티를 사용하기에 가장 좋은 곳은 MOBA나 RTS에서 미니언 기본 공격과 같이 자주 사용되는 간단한 어빌리티입니다. 샘플 프로젝트의 점프 GameplayAbility는 인스턴스화되지 않았습니다.

 

4.6.8 Net Execution Policy

GameplayAbility의 Net Execution Policy은 GameplayAbility를 누가 어떤 순서로 실행할지 결정합니다.

Net Execution Policy 설명:

Net Execution Policy Description
Local Only GameplayAbility는 소유 클라이언트에서만 실행됩니다. 로컬 외형만 변경하는 어빌리티에 유용할 수 있습니다. 싱글 플레이어 게임은 서버 전용을 사용해야 합니다.
Local Predicted 로컬 예측 GameplayAbility는 소유 클라이언트에서 먼저 활성화된 다음 서버에서 활성화됩니다. 서버 버전은 클라이언트가 잘못 예측한 모든 것을 수정합니다. https://github.com/tranek/GASDocumentation#concepts-p 참조.
Server Only GameplayAbility는 서버에서만 실행됩니다. 패시브 GameplayAbility는 일반적으로 서버 전용입니다. 싱글 플레이어 게임에서 사용해야 합니다.
Server Initiated 서버에서 시작된 GameplayAbility는 서버에서 먼저 활성화된 다음 소유한 클라이언트에서 활성화됩니다. 저는 개인적으로 이 기능을 많이 사용하진 않았습니다.

 

4.6.9 어빌리티 태그

GameplayAbility는 로직이 내장된 GameplayTagContainer와 함께 제공됩니다. 이러한 GameplayTag는 리플리케이트되지 않습니다.

GameplayTagContainer 설명:

GameplayTagContainer  내용
Ability Tags GameplayAbility가 소유한 GameplayTag입니다. GameplayAbility를 설명하기 위한 GameplayTag일 뿐입니다.
Cancel Abilities with Tag 어빌리티 태그에 이러한 GameplayTag가 있는 다른 GameplayAbility는 이 GameplayAbility가 활성화되면 취소됩니다.
Block Abilities with Tag 어빌리티 태그에 이러한 GameplayTag가 있는 다른 GameplayAbility는 이 GameplayAbility가 활성화되어 있는 동안 활성화가 차단됩니다.
Activation Owned Tags GameplayTag는 이 GameplayAbility가 활성화되어 있는 동안 GameplayAbility의 소유자에게 주어집니다. 리플리케이트되지 않는다는 점을 기억하세요.
Activation Required Tags GameplayAbility는 소유자가 이러한 GameplayTag를 모두 보유한 경우에만 활성화할 수 있습니다.
Activation Blocked Tags 소유자에게 이러한 GameplayTag가 있는 경우 이 GameplayAbility를 활성화할 수 없습니다.
Source Required Tags GameplayAbility는 소스에 이러한 GameplayTag가 모두 있는 경우에만 활성화할 수 있습니다. 소스 GameplayTagGameplayAbility가 이벤트에 의해 트리거된 경우에만 설정됩니다.
Source Blocked Tags 이 게GameplayAbility는 소스에 이러한 GameplayTag가 있는 경우 활성화할 수 없습니다. 소스 GameplayTagGameplayAbility가 이벤트에 의해 트리거된 경우에만 설정됩니다.
Target Required Tags GameplayAbility는 타깃에 이러한 GameplayTag가 모두 있는 경우에만 활성화할 수 있습니다. 타깃GameplayTagGameplayAbility가 이벤트에 의해 트리거되는 경우에만 설정됩니다.
Target Blocked Tags 타겟에 이러한 GameplayTag가 있는 경우 이 GameplayAbility를 활성화할 수 없습니다. 타겟 GameplayTagGameplayAbility가 이벤트에 의해 트리거되는 경우에만 설정됩니다.

 

4.6.10 GameplayAbilitySpec

GameplayAbilitySpec은 GameplayAbility가 부여된 후 ASC에 존재하며 활성화 가능한 GameplayAbility - GameplayAbility 클래스, 레벨, 입력 바인딩, 런타임 상태를 정의하며 GameplayAbility 클래스와는 별도로 유지해야 합니다.
서버에서 GameplayAbility가 부여되면 서버는 소유 클라이언트가 활성화할 수 있도록 GameplayAbilitySpec을 리플리케이트합니다.


GameplayAbilitySpec을 활성화하면 Instancing Policy에 따라 GameplayAbility 의 인스턴스(인스턴스화되지 않은 GameplayAbility의 경우)를 생성하거나 생성하지 않습니다.

 

4.6.11 어빌리티에 데이터 전달하기

GameplayAbility의 일반적인 패러다임은 활성화->데이터 생성->적용->종료입니다. 때로는 기존 데이터에 작업을 수행해야 할 때가 있습니다. GAS는 외부 데이터를 GameplayAbility로 가져오는 몇 가지 옵션을 제공합니다:

메서드 설명:

Method 내용
Activate GameplayAbility by Event DataPayload가 포함된 이벤트로 GameplayAbility를 활성화합니다. 이벤트의 페이로드는 로컬 예측 GameplayAbility를 위해 클라이언트에서 서버로 리플리케이트됩니다. 기존 변수에 맞지 않는 임의 데이터의 경우 두 개의 옵션 오브젝트 또는 https://github.com/tranek/GASDocumentation#concepts-targeting-data 변수를 사용합니다. 단점은 입력 바인딩으로 어빌리티를 활성화할 수 없다는 것입니다. 이벤트별로 GameplayAbility를 활성화하려면 GameplayAbility에 트리거가 설정되어 있어야 합니다. GameplayTag를 할당하고 GameplayEvent에 대한 옵션을 선택합니다. 이벤트를 전송하려면 UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload) 함수를 사용합니다.
Use WaitGameplayEvent AbilityTask WaitGameplayEvent AbilityTask를 사용하여 GameplayAbility가 활성화된 후 페이로드 데이터가 있는 이벤트를 수신 대기하도록 지시합니다. 이벤트 페이로드와 이를 전송하는 프로세스는 이벤트별로 GameplayAbility를 활성화하는 것과 동일합니다. 단점은 이벤트가 AbilityTask에 의해 리플리케이트되지 않으며 로컬 전용 및 서버 전용 GameplayAbility에만 사용해야 한다는 점입니다. 이벤트 페이로드를 리플리케이트하는 자체 AbilityTask를 직접 작성할 수도 있습니다.
Use TargetData 사용자 지정 TargetData 구조체는 클라이언트와 서버 간에 임의의 데이터를 전달할 수 있는 좋은 방법입니다.
Store Data on the OwnerActor or AvatarActor OwnerActor, AvatarActor 또는 참조할 수 있는 다른 오브젝트에 저장된 리플리케이트된 변수를 사용합니다. 이 메서드는 가장 유연하며 입력 바인딩으로 활성화된 GameplayAbility에서 작동합니다. 하지만 사용 시점에 데이터가 리플리케이션에서 동기화된다는 보장은 없습니다. 즉, 리플리케이트된 변수를 설정한 후 즉시 GameplayAbility를 활성화하면 잠재적인 패킷 손실로 인해 수신기에서 어떤 순서가 발생할지 보장할 수 없으므로 미리 확인해야 합니다.

 

4.6.12 어빌리티 코스트 그리고 쿨타임

GameplayAbility에는 선택적 비용과 재사용 대기시간에 대한 기능이 있습니다. 비용은 인스턴트 GameplayEffect(Cost GE)로 구현된 GameplayAbility를 활성화하기 위해 ASC가 가져야 하는 미리 정의된 Attribute의 양입니다. 쿨타임은

GameplayAbility가 만료될 때까지 다시 활성화되지 않도록 막는 타이머로, 지속시간 GameplayEffect(쿨타임 GE)로 구현됩니다.

GameplayAbility는 UGameplayAbility::Activate()를 호출하기 전에 UGameplayAbility::CanActivateAbility()를 호출합니다. 이 함수는 소유한 ASC가 비용을 감당할 수 있는지 확인하고(UGameplayAbility::CheckCost()), GameplayAbility가 쿨타임 중안자 아닌지 확인합니다(UGameplayAbility::CheckCooldown()).

GameplayAbility가 Activate()를 호출한 이후에는 UGameplayAbility::CommitCost() 및 UGameplayAbility::CommitCooldown()을 호출하는 UGameplayAbility::CommitAbility() 를 사용하여 언제든지 비용과 쿨타임을 선택적으로 커밋할 수 있습니다. 동시에 커밋해서는 안 되는 경우 디자이너는 CommitCost() 또는 CommitCooldown()을 따로 호출하도록 선택할 수 있습니다. 비용과 쿨타임을 커밋하면 CheckCost() 및 CheckCooldown()이 한 번 더 호출되며, 이는 GameplayAbility가 이와 관련하여 실패할 수 있는 마지막 기회입니다. GameplayAbility가 활성화된 후 소유 ASC의 어트리뷰트가 변경되어 커밋 시점에 비용을 충족하지 못할 수 있습니다. 커밋 시점에 예측 키가 유효한 경우 비용과 쿨타임을 로컬에서 예측할 수 있습니다.

구현에 대한 자세한 내용은 CostGE  CooldownGE를 참조하세요.

 

4.6.13 어빌리티 레벨업

어빌리티 레벨을 올리는 일반적인 방법은 두 가지가 있습니다:

레벨업 방법설명:

레벨업 함수 설명
Ungrant and Regrant at the New Level UASC에서 GameplayAbility를 언그랜트(제거)하고 서버의 다음 레벨에서 다시 그랜트(부여)합니다. 이렇게 하면 당시 활성화되어 있던 GameplayAbility가 종료됩니다.
Increase the GameplayAbilitySpec's Level 서버에서 GameplayAbilitySpec을 찾아 레벨을 높이고 더티로 표시하여 소유 클라이언트에 리플리케이트되도록 합니다. 이 메서드는 GameplayAbility가 당시 활성화된 경우 종료하지 않습니다.

두 방법의 주요 차이점은 레벨 업 시 활성화된 GameplayAbility를 취소할지 여부입니다. GameplayAbility에 따라 두 메서드를 모두 사용할 가능성이 높습니다. 사용할 메서드를 지정하는 부울을 UGameplayAbility 서브클래스에 추가하는 것이 좋습니다.

 

4.6.14 GameplayAbilitySet

GameplayAbilitySetGameplayAbility를 부여하는 로직이 있는 캐릭터의 시작GameplayAbility의 입력 바인딩과 목록을 보관하기 위한 편의성 데이터 에셋 클래스입니다. 서브클래스에는 추가 로직이나 프로퍼티를 포함할 수도 있습니다. 파라곤에는 영웅마다 주어진 모든 GameplayAbility를 포함하는 GameplayAbilitySet이 있었습니다.
이 클래스는 적어도 지금까지 살펴본 바에 따르면 불필요한 클래스입니다. 샘플 프로젝트는 GDCharacterBase와 그 서브클래스 내부에서 GameplayAbilitySet의 모든 기능을 처리합니다.

 

4.6.15 어빌리티 일괄 처리

기존 GameplayAbility의 수명 주기에는 클라이언트에서 서버까지 최소 2~3개의 RPC가 포함됩니다.

  1. CallServerTryActivateAbility()
  2. ServerSetReplicatedTargetData() (Optional)
  3. ServerEndAbility()

GameplayAbility가 한 프레임에서 이러한 모든 액션을 하나의 원자 그룹으로 수행하는 경우, 이 워크플로를 최적화하여 두세 개의 RPC를 모두 하나의 RPC로 일괄 처리(결합)할 수 있습니다. GAS에서는 이러한 RPC 최적화를 어빌리티 일괄 처리라고 합니다. 어빌리티 배칭을 사용하는 일반적인 예는 히트스캔 건입니다. 히트스캔 건은 한 프레임에 하나의 원자 그룹에서 어빌리티를 활성화하고, 라인 트레이스를 수행하고, 타겟 데이터를 서버로 전송하고, 어빌리티를 종료합니다. 가스슈터 샘플 프로젝트는 히트스캔 건에 이 기술을 보여줍니다.


반자동 총은 가장 좋은 시나리오로, CallServerTryActivateAbility(), ServerSetReplicatedTargetData() (총알 명중 결과), ServerEndAbility()를 3개의 RPC가 아닌 하나의 RPC로 일괄 처리합니다.

완전 자동/버스트 건은 첫 번째 탄환에 대한 CallServerTryActivateAbility() 및 ServerSetReplicatedTargetData()를 두 개의 RPC가 아닌 하나의 RPC로 일괄 처리합니다. 이후 각 글머리 기호는 자체 ServerSetReplicatedTargetData() RPC입니다. 마지막으로, 총이 사격을 멈추면 ServerEndAbility()가 별도의 RPC로 전송됩니다. 이것은 첫 번째 탄환에 대해 두 개가 아닌 하나의 RPC만 저장하는 최악의 시나리오입니다. 이 시나리오는 GameplayEvent를 통해 어빌리티를 활성화하여 총알의 타겟 데이터를 클라이언트에서 서버로 EventPayload와 함께 전송하는 방식으로 구현할 수도 있습니다. 후자의 접근 방식은 어빌리티 외부에서 타깃 데이터를 생성해야 하는 반면, 일괄 처리 방식은 어빌리티 내부에서 타깃 데이터를 생성한다는 단점이 있습니다.


어빌리티 일괄 처리는 ASC에서 기본적으로 비활성화되어 있습니다. 어빌리티 일괄 처리를 활성화하려면 ShouldDoServerAbilityRPCBatch()를 재정의하여 참을 반환합니다:

virtual bool ShouldDoServerAbilityRPCBatch() const override { return true; }

이제 어빌리티 일괄 처리가 활성화되었으므로, 일괄 처리하려는 어빌리티를 활성화하기 전에 FScopedServerAbilityRPCBatcher 구조체를 미리 생성해야 합니다. 이 특수 구조체는 그 범위 안에서 그 뒤에 오는 어빌리티를 일괄 처리하려고 시도합니다. FScopedServerAbilityRPC배처가 범위를 벗어나면, 활성화된 어빌리티는 일괄 처리를 시도하지 않습니다. FScopedServerAbilityRPC배처는 일괄 처리할 수 있는 각 함수에 특수 코드를 넣어 RPC 전송 호출을 가로채는 대신 메시지를 일괄 구조체에 패킹하는 방식으로 작동합니다. FScopedServerAbilityRPCBatcher가 범위를 벗어나면, 이 배치 구조체를 UAbilitySystemComponent::EndServerAbilityRPCBatch() 에서 서버로 자동 RPC 합니다. 서버는 UAbilitySystemComponent::ServerAbilityRPCBatch_Internal(FServerAbilityRPCBatch& BatchInfo) 에서 배치 RPC를 받습니다. BatchInfo 파라미터에는 어빌리티 종료 여부와 활성화 시 입력이 눌렸는지 여부에 대한 플래그와 타깃 데이터가 포함되어 있는 경우 타깃 데이터가 포함됩니다. 일괄 처리가 제대로 작동하는지 확인하기 위해 중단점을 설정하는 데 좋은 함수입니다. 또는 특수 능력 일괄 처리 로깅을 활성화하려면 콘솔 변수에 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는 반자동 및 완전 자동 총에 동일한 일괄 처리된 GameplayAbility를 재사용하며, 이 경우 EndAbility() 를 직접 호출하지 않습니다(현재 사격 모드에 따라 플레이어 입력과 일괄 처리된 어빌리티 호출을 관리하는 로컬 전용 어빌리티에 의해 어빌리티 외부에서 처리됩니다). 모든 RPC는 FScopedServerAbilityRPC배처의 범위 내에서 발생해야 하므로, 제어/관리하는 로컬 전용 어빌리티가 이 어빌리티가 EndAbility() 호출을 일괄 처리할지(반자동), 아니면 일괄 처리하지 않을지(완전 자동) 지정할 수 있도록 EndAbilityImmediately 파라미터를 제공하여 나중에 자체 RPC에서 EndAbility() 호출이 발생하도록 할 수 있도록 합니다.

GASShooter는 블루프린트 노드를 노출하여 앞서 언급한 로컬 전용 어빌리티가 배치 어빌리티를 트리거하는 데 사용하는 어빌리티를 배치할 수 있도록 합니다.

 

4.6.16 Net Security Policy

GameplayAbility의 NetSecurityPolicy는 어빌리티가 네트워크에서 실행될 위치를 결정합니다. 제한된 어빌리티를 실행하려는 클라이언트로부터 보호합니다.


NetSecurityPolicy 설명:

NetSecurityPolicy 내용
ClientOrServer 보안 요구 사항이 없습니다. 클라이언트 또는 서버가 이 기능의 실행 및 종료를 자유롭게 트리거할 수 있습니다.
ServerOnlyExecution 이 기능의 실행을 요청하는 클라이언트는 서버에서 무시됩니다. 클라이언트는 여전히 서버에 이 기능을 취소하거나 종료하도록 요청할 수 있습니다.
ServerOnlyTermination 이 기능의 취소 또는 종료를 요청하는 클라이언트는 서버에서 무시됩니다. 클라이언트는 여전히 어빌리티 실행을 요청할 수 있습니다.
ServerOnly 서버는 이 기능의 실행과 종료를 모두 제어합니다. 클라이언트의 모든 요청은 무시됩니다.