[UE5] Gameplay Ability System(GAS)

kkado·2024년 8월 13일
0

UE5

목록 보기
54/61
post-thumbnail

새로운 프로젝트로 GAS를 적극 활용하게 되었기 때문에, GAS가 무엇인지 공부해보면서 개인적으로 정리하는 글입니다.

https://github.com/tranek/GASDocumentation

https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-the-unreal-engine-gameplay-ability-system

잘 정리된 위의 글들을 한글로 옮겨 정리하면서 공부한 내용을 업로드합니다.


1. GAS란

Gameplay Ability System의 약자.

RPG 또는 MOBA류 게임에서 많이 볼 수 있는, 캐릭터의 능력이나 속성 등을 편리하게 관리할 수 있게 도와주는 프레임워크의 일종.

다음과 같은 기능을 제공한다고 정리할 수 있다.

  • 캐릭터의 능력 또는 스킬 (코스트나 쿨다운 개념을 포함한) 의 구현 (GameplayAbilites)
  • 액터에 속한 수치화된 (즉 숫자값) Attribute(속성) 을 다루기 (Attributes)
  • 액터에 특정한 Effect(효과)를 적용하기 (GameplayEffects)
  • 액터에 GameplayTag 를 적용하기 (GameplayTags)
  • 시각/음향 효과를 생성 (GameplayCue)
  • 위 요소들의 Replication 및 Client Side Prediction

2. 프로젝트에서 GAS 사용하기

Gameplay Abilities 플러그인을 프로젝트에 추가

.Build.cs 파일의 PrivateDependencyModuleNames"GameplayAbilities", "GameplayTags", "GameplayTasks" 추가

(4.24 ~ 5.2 버전의 경우) TargetData 를 사용하기 위해서 UAbilitySystemGlobals::Get().InitGlobalData() 을 호출


3. Gas Concepts

3-1. Ability System Component

AbilitySystemComponent (이하 ASC) 는 액터 컴포넌트의 일종이며 모든 시스템의 상호작용을 책임진다. GameplayAbilities, Attribute, GameplayEffects 등을 사용하려는 액터는 반드시 ASC가 부착되어 있어야 한다.

OwnerActor vs AvatarActor

그리고 이렇게 ASC가 부착된 액터는 그 ASC의 OwnerActor 로 간주된다. 또한 ASC의 '물리적인 존재' (physical representation)은 AvatarActor 라고 지칭한다. 이 둘은 같을 수도 있고 다를 수도 있다.

예를 들어 리그오브레전드에서 '미니언' 은 스스로 ASC를 가지고 있으며 AI에 따라 움직이는데 이는 두 개가 같은 케이스이고, 유저가 조종하는 챔피언의 경우 OwnerActor는 PlayerState이고, 챔피언의 캐릭터 클래스는 AvatarActor가 된다. 따라서 이는 두 개가 서로 다른 케이스이다. 캐릭터가 죽고 나서 리스폰되는 등의 경우에 애트리뷰트나 이펙트 등은 일관되게 유지되어야 하는 경우가 있기 때문에, ASC의 이상적인 위치는 PlayerState인 것이다.

Ability Scope Lock

ASC는 현재 활성화된 GameplayEffectsFActiveGameplayEffectsContainer ActiveGameplayEffects 에 저장하며, 부여된 Gameplay AbilitiesFGameplayAbilitySpecContainer ActivatableAbilities 라는 구조에 저장하는데 (이름은 알 필요 없고 그냥 이러한 이름의 이런 자료구조가 있다 정도), 만약 이 아이템들을 순회할 일이 있다면 먼저 ABILITYLIST_SCOPE_LOCK() 을 잡아야 한다. 이는 순회 도중 어빌리티 리스트 정보가 변경될 우려가 있기 때문인데, 이와 비슷한 맥락으로 순회하는 과정에서 리스트를 훼손해선 안 된다.

내부적으로는 AbilityScopeLockCount 라고 하는, 세마포어와 비슷한 역할을 하는 값이 존재하며, 리스트를 clear 하는 등의 함수에서 이 값을 확인하여 현재 락이 잡혀있는지 판단하는 데 사용한다.

Replication

ASC가 정의하는 Replication Mode에는 3가지 종류가 있다.
여기서 다루는 Replication의 대상은 GameplayEffects, GameplayTags, GameplayCues 이다. 예외적으로 Attributes는 그들의 AttributeSet에 의해 복제된다.

  • Full : 싱글플레이어에서 사용. 모든 이펙트가 모든 클라에 복제.
  • Mixed : 멀티플레이어, 플레이어에 의해 컨트롤되는 액터에서 사용. 이펙트는 owning client에게만 복제되며, 태그와 큐는 모두에게 복제됨.
  • Minimal : 멀티플레이어, AI에 의해 컨트롤되는 액터에서 사용. 이펙트는 복제되지 않고 태그와 큐는 모두에게 복제.

Mixed 모드를 사용할 때 주의사항은 OwnerActorOwner가 컨트롤러여야 한다는 것. PlayerState의 Owner는 기본적으로 컨트롤러이지만 캐릭터는 그렇지 않다. 만약 PlayerState가 아닌 다른 액터를 OwnerActor로 사용할 때에는 SetOwner()를 통해 유효한 컨트롤러를 직접 지정해 주어야 함.

생성 및 초기화

OwnerActor의 생성자에서 ASC에 대한 초기화가 이루어지는 것이 보편적. 이 작업은 반드시 C++에서 이루어져야 함.

AGDPlayerState::AGDPlayerState()
{
	// Create ability system component, and set it to be explicitly replicated
	AbilitySystemComponent = CreateDefaultSubobject<UGDAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
	AbilitySystemComponent->SetIsReplicated(true);
	//...
}

ASC는 반드시 서버와 클라이언트 모두에서 초기화되어야 한다.

ASC가 Pawn에 부착되어 있는 플레이어 컨트롤-캐릭터의 경우, 서버에 대해선 PossessedBy() 함수에서, 클라이언트에 대해선 컨트롤러AcknowledgePossession() 에서 초기화하는 편.

void APACharacterBase::PossessedBy(AController * NewController)
{
	Super::PossessedBy(NewController);

	if (AbilitySystemComponent)
	{
		AbilitySystemComponent->InitAbilityActorInfo(this, this);
	}

	// ASC MixedMode replication requires that the ASC Owner's Owner be the Controller.
	SetOwner(NewController);
}
void APAPlayerControllerBase::AcknowledgePossession(APawn* P)
{
	Super::AcknowledgePossession(P);

	APACharacterBase* CharacterBase = Cast<APACharacterBase>(P);
	if (CharacterBase)
	{
		CharacterBase->GetAbilitySystemComponent()->InitAbilityActorInfo(CharacterBase, CharacterBase);
	}

	//...
}

ASC가 PlayerState에 부착되어 있는 플레이어 컨트롤-캐릭터의 경우, 서버에 대해선 PossessedBy() 그리고 클라이언트에 대해선 OnRep_PlayerState() 에서 초기화하는 편.

// Server only
void AGDHeroCharacter::PossessedBy(AController * NewController)
{
	Super::PossessedBy(NewController);

	AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
	if (PS)
	{
		// Set the ASC on the Server. Clients do this in OnRep_PlayerState()
		AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());

		// AI won't have PlayerControllers so we can init again here just to be sure. No harm in initing twice for heroes that have PlayerControllers.
		PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
	}
	
	//...
}
// Client only
void AGDHeroCharacter::OnRep_PlayerState()
{
	Super::OnRep_PlayerState();

	AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
	if (PS)
	{
		// Set the ASC for clients. Server does this in PossessedBy.
		AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());

		// Init ASC Actor Info for clients. Server will init its ASC when it possesses a new Actor.
		AbilitySystemComponent->InitAbilityActorInfo(PS, this);
	}

	// ...
}

3-2. Gameplay Tags

태그는 GameplayTagManager에 등록되며, 점 . 으로 구분되는 계층 구조를 가진다. 이러한 구조는 태그를 이해하고 분류하는 데 효과적이다.

예를 들어 플레이어가 '스턴' 상태에 빠졌다고 가정하면, State.Debuff.Stun 태그를 적용할 수 있다. 그럼 태그만 보고도 '디버프의 일종인 스턴 상태가 적용돼 있다' 는 것을 알 수 있다.

태그를 적용할 때는 ASC에 존재하는 IGameplayTagAssetInterface 를 통해 함수를 사용하는 것이 보편적

자료구조적 측면

FGameplayTagContainer 라는 자료구조에 태그들을 저장할 수 있는데, 단순한 TArray 보다 효율적이다.

프로젝트 세팅에서 Fast Replication 이 활성화 돼있다면 FGameplayTagContainer는 태그들을 효율적으로 복제할 수 있는데, Fast Replication을 켠다고 해서 문제될 건 보통 없으므로 웬만해선 켜는 것이 좋다. Fast Replication는 클라이언트와 서버가 같은 태그 리스트를 갖고 있어야 한다.

FGameplayTagCountContainer 에 저장된 태그는 TagMap이 있으며 이 태그의 인스턴스 개수를 저장하고 있다. TagMapCount 라는 변수는 HasTag(), HasMatchingTag() 등의 함수에서 태그가 있는지 확인하는 데에 사용된다. (잘 이해 못 함)


태그는 DefaultGameplayTags.ini에 정의되어 있어야 한다. 언리얼 엔진은 프로젝트 세팅에서 이 태그들을 관리할 수 있는 인터페이스를 제공한다.

태그들은 GameplayEffect에 의해 복제가 되지만 LooseGameplayTag 는 복제되지 않게 할 수 있으며 이 경우 직접 관리해 주어야 한다.

사용

태그의 레퍼런스 얻기

FGameplayTag::RequestGameplayTag(FName("Your.GameplayTag.Name"))

GameplayTagManager는 많은 복잡한 함수들을 제공하며, UGameplayTagManager::Get().FunctionName 와 같이 사용한다.

ASC는 태그가 추가되거나 삭제됨에 따른 델리게이트를 가지고 있으며 EGameplayTagEventType 라는 타입을 통해 추가됐을 때/삭제됐을 때 등의 조건을 걸 수 있다.

콜백 함수는 태그와 새로운 TagCount를 파라미터로 갖는다.

virtual void StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount);

3-3. Attributes

FGameplayAttributeData 라는 구조체에 정의된 float 값이다. 캐릭터의 HP, 가지고 있는 포션의 수, 현재 레벨 등 다양한 값들을 표시할 수 있다. 만약 액터와 관련된 float 값을 사용할 것이라면 애트리뷰트의 사용을 고려해봄직하다.

애트리뷰트는 AttributeSet 에서 정의되며 존재한다. 이 셋은 복제 대상 애트리뷰트를 복제하는 역할을 수행한다.

BaseValue vs CurrentValue

애트리뷰트는 두 종류의 값으로 구성된다. BaseValueCurrentValue 이다. BaseValue는 영구적인 값이며 CurrentValue는 BaseValue에 더해 특정한 수정이 들어간 값이라고 보면 된다.

예컨대 캐릭터가 600unit/s의 이동 속도를 BaseValue로 가지고 있다고 가정해 보자. GameplayEffects에서 어떠한 효과도 적용하지 않았다면 당연히 CurrentValue도 600unit/s 이다. 여기에 50unit/s 의 이동속도를 더해주는 버프를 받았다고 하면, CurrentValue 값만 650unit/s로 바뀐다.

간혹 BaseValue를 애트리뷰트의 '최댓값' 으로 생각하고, 또 그렇게 사용하는 경우가 있는데 이는 틀린 접근이라고 한다.
변경될 수 있는 최댓값 등은 별개의 애트리뷰트로 처리하는 것이 좋고, 만약 클램핑을 위해 최댓값/최솟값을 하드코딩해야 한다면 FAttributeMetaData를 이용해 DataTable을 만드는 방법이 있다. 아니면 이 또한 애트리뷰트 셋에 저장하는 방법도 있다.

Responding to Attribute Changes

UI나 다른 게임플레이를 업데이트하는 애트리뷰트의 변화에 대한 콜백 함수를 델리게이트 바인딩할 수 있다.

UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute) 함수를 통해 애트리뷰트가 바뀔 때마다 호출되는 델리게이트를 얻을 수 있고, FOnAttributeChangeData 등의 파라미터를 제공한다.

AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSetBase->GetHealthAttribute()).AddUObject(this, &AGDPlayerState::HealthChanged);
virtual void HealthChanged(const FOnAttributeChangeData& Data);

3-4. Attribute Set

애트리뷰트를 정의하고, 저장하고 변화를 관리하는 셋이다. UAttributeSet 타입의 TSubclass로 사용할 수 있다.

디자인 측면

하나의 ASC는 하나 이상의 애트리뷰트 셋을 가질 수 있다. 무시할 만한 수준의 낮은 메모리 오버헤드를 갖고 있기 때문에 몇 개의 애트리뷰트 셋을 가지느냐는 디자인 재량이다. 하나의 큰(monolithic한) 셋을 가지고 액터들끼리 공유하게 할 수도 있고 또는 필드별로 세부적으로 셋을 분리할 수도 있다. 예컨대 체력과 관련된 애트리뷰트 셋, 마나와 관련된 애트리뷰트 셋 이런 식으로.

그러나 둘 이상의 애트리뷰트 셋을 사용할 것이라면 ASC에 같은 클래스의 애트리뷰트 셋을 둘 이상 사용해선 안 된다. 만약 같은 클래스의 애트리뷰트 셋을 여럿 가지고 있다면 이들 중 어떤 것을 사용할지 지정할 수 없다.

(애트리뷰트 셋의 '클래스' 개념이 아직은 모호한데 아마 Subclass 언급이 있는 것으로 보아 하나의 애트리뷰트셋 클래스에서 파생하는 식으로 사용하는 듯)


애트리뷰트 셋은 런타임에서 ASC에 추가/삭제 될 수 있다.
그러나 셋을 삭제하는 것은 다소 위험할 수 있다. 예컨대 애트리뷰트 셋이 서버보다 클라이언트에서 먼저 삭제되었는데 이 때 애트리뷰트 값의 변경이 클라이언트로 복제되지 않은 상황이라면 애트리뷰트 값은 셋을 찾지 못하게 되므로 크래쉬가 발생한다.

애트리뷰트 정의

애트리뷰트는 오직 C++에서만, 애트리뷰트셋의 헤더 파일에서만 정의될 수 있다. 모든 애트리뷰트 셋의 헤더 파일에서 아래와 같은 매크로 블록을 정의하는 것이 추천된다.

// Uses macros from AttributeSet.h
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

만약 복제되는 체력 애트리뷰트라면 다음과 같이 정의될 수 있다:

UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UGDAttributeSetBase, Health)

UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);

FGameplayAttributeData 형 데이터를 선언하고 ATTRIBUTE_ACCESSORS 에 바인딩한다.

그리고 RepNotify 함수는 아래와 같이 Prediction system에서 사용되는 GAMEPLAYATTRIBUTE_REPNOTIFY 를 명시

void UGDAttributeSetBase::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UGDAttributeSetBase, Health, OldHealth);
}

또한 복제의 대상이기 때문에 GetLifetimeReplicatedProps 에도 추가해 주어야 함.

void UGDAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME_CONDITION_NOTIFY(UGDAttributeSetBase, Health, COND_None, REPNOTIFY_Always);
}

초기화

Attribute를 정의할 때 ATTRIBUTE_ACCESSORS 매크로를 사용한 경우 C++에서 호출할 수 있는 각 AttributeSet에 자동으로 초기화 함수가 생성된다.

// InitHealth(float InitialValue) is an automatically generated function for an Attribute 'Health' defined with the `ATTRIBUTE_ACCESSORS` macro
AttributeSet->InitHealth(100.0f);

PreAttributeChange()

PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) 는 애트리뷰트의 Current value의 변경이 일어나기 전 반응할 수 있는 함수이다.

PostGameplayEffectExecute()

PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data) 함수는
애트리뷰트의 BaseValue가 변경된 이후 트리거된다.

3-5. Gameplay Effects

애트리뷰트나 태그에 변화를 줄 수 있다. 데미지를 가하거나 회복을 시키는 등 즉각적인 변화, 그리고 이로운 버프를 제공하거나 해로운 디버프를 가하는 등 일정 시간동안 변화를 주는 이펙트들이 포함된다.

UGameplayEffect 클래스는 data-only 클래스여야 하며 하나의 게임플레이 이펙트만 정의해야 한다. 다른 추가적인 로직이 추가되면 안 된다.

3가지 타입의 Effect

  • Instant : 애트리뷰트의 BaseValue의 영구적인 변화
  • Duration : 애트리뷰트의 CurrentValue의 일시적인 변화. 스스로 만료되거나 직접 제거할 수 있다.
  • Infinite : 애트리뷰트의 CurrentValue의 일시적인 변화. GameplayEfffect가 제거되면 함께 제거된다. 스스로 만료되진 않으며 반드시 직접 제거해야 한다.

Applying Gameplay Effects

GameplayAbilites, 그리고 ASC 의 다양한 함수들을 이용해 적용할 수 있다.

또한 ASC에 infinite/duration 이펙트가 적용된 것을 listen할 수 있는 델리게이트 및 콜백 함수가 존재한다.

AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(this, &APACharacterBase::OnActiveGameplayEffectAddedCallback);
virtual void OnActiveGameplayEffectAddedCallback(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle);

Removing Gameplay Effects

Apply와 마찬가지로 델리게이트 및 콜백 함수가 존재

AbilitySystemComponent->OnAnyGameplayEffectRemovedDelegate().AddUObject(this, &APACharacterBase::OnRemoveGameplayEffectCallback);
virtual void OnRemoveGameplayEffectCallback(const FActiveGameplayEffect& EffectRemoved);

Gameplay Effect Modifier

Modifier는 애트리뷰트를 변경할 수 있으며, 이펙트는 모디파이어를 갖지 않을 수 있고 여러 개도 가질 수 있다.

하나의 모디파이어는 특정한 operation를 통한 하나의 애트리뷰트 변화만을 담당한다.

  • Add : 모디파이어의 지정된 애트리뷰트 값을 더함
  • Multiply : 모디파이어의 지정된 애트리뷰트 값을 곱함
  • Divide : 모디파이어의 지정된 애트리뷰트 값을 나눔
  • Override : 모디파이어의 지정된 애트리뷰트 값을 오버라이드 (덮어씀)

3-6. Gameplay Abilities

액터가 게임 내에서 할 수 있는 특정한 액션 또는 스킬. 동시에 여러 어빌리티를 수행할 수 있다. (예: 달리면서 총 쏘기) 점프, 달리기, 총쏘기, 문 열기, 포션 마시기 등등 정말 많은 기능들이 있을 수 있다.

기본 움직임 인풋이나 UI와의 상호작용은 GameplayAbilities 에서 구현하지 않는다. (꼭 그런건 아니지만, 추천 사항)

Flowchart

간단한 어빌리티의 실행 흐름을 플로우차트로 나타낸 것.
어빌리티를 활성화 할 수 있는지를 검증하고, 활성화하고, GameplayEffects에 적용하고 끝마치는 등의 순서를 따른다.

좀 더 복잡한 어빌리티의 예시. 어빌리티들 중에서는 여러 어빌리티와 연관될 수 있다. 예컨대 기존의 동작을 취소하고 다른 동작을 취한다든가 하는 것들이 해당한다.

Replication

GameplayAbilities는 Simulated Proxy에서 동작하지 않는다. AbilityTasks, GameplayCues 를 통해 Simulated Proxy에게 복제된 것처럼 시각 효과를 적용한다.

(아직 잘은 모르겠으나, 에픽 측의 코멘트를 보았을 때 어빌리티들은 클라이언트들에 복제되지 않는 것이 바람직하다는 듯 보임)

Input Bind to ASC

ASC에 다이렉트로 인풋을 바인딩해서 특정한 어빌리티를 수행하게 할 수 있다. 먼저 enum 클래스를 만들어 인풋 액션을 바이트 단위로 매핑하는 작업이 필요하다. 예시 코드를 보면 :

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에 대한 바인딩을 수행할 수 있고,

PlayerState에 존재한다면 잠재적으로 Race condition이 발생할 수 있으므로 SetupPlayerInputComponent()OnRep_PlayerState() 에서 인풋 바인딩을 진행하는 것이 좋다.


3-7. Ability Task

GameplayAbilities 는 오직 한 프레임만 실행한다. 따라서 시간이 지난 후 발생하는 이벤트, 또는 델리게이트의 fire에 반응해야 하는 포인트에서는 적합하지 않다. 이러한 작업을 수행하기 위해서 Ability Task 라는 동작을 사용한다.

GAS는 다음과 같은 태스크를 지원한다 :

  • RootMotionSource 를 사용한 캐릭터의 이동
  • 애니메이션 몽타주 실행
  • 애트리뷰트의 변화에 대응
  • 이펙트의 변화에 대응
  • 플레이어 인풋에 대응

이외에도 많은 태스크를 지원한다.

3-8. Gameplay Cues

게임플레이 큐는 게임플레이와 직접적 연관이 없는 것들, 예컨대 사운드, 파티클 이펙트, 카메라의 흔들림 등을 실행한다. 이 큐들은 보통 복제 및 예측된다.

적절한 게임플레이 태그를 이벤트 타입과 함께 보냄으로써 큐를 트리거할 수 있다.

Triggering Gameplay Cue

트리거되어야 하는 게임플레이 큐 태그를 명시한다.

/** GameplayCues can also come on their own. These take an optional effect context to pass through hit result, etc */
void ExecuteGameplayCue(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());
void ExecuteGameplayCue(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

/** Add a persistent gameplay cue */
void AddGameplayCue(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());
void AddGameplayCue(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

/** Remove a persistent gameplay cue */
void RemoveGameplayCue(const FGameplayTag GameplayCueTag);
	
/** Removes any GameplayCue added on its own, i.e. not as part of a GameplayEffect. */
void RemoveAllGameplayCues();

위 블루프린트 노드들 또는 C++ 함수들을 이용해 트리거할 수 있다.


Local Gameplay Cue

게임플레이 큐를 트리거하기 위한 함수들은 기본적으로 복제의 대상이 된다. 모든 큐 이벤트는 멀티캐스트 RPC이다. 이것은 상당히 많은 RPC 호출을 초래하게 되는데, 따라서 GAS는 한 번의 net update 동안 최대 두 번의 같은 게임플레이 태그를 시행한다. 따라서 사용할 수 있는 한 Local GameplayCue를 사용하여 이것을 피할 수 있다.

UFUNCTION(BlueprintCallable, Category = "GameplayCue", Meta = (AutoCreateRefTerm = "GameplayCueParameters", GameplayTagFilter = "GameplayCue"))
void ExecuteGameplayCueLocal(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

UFUNCTION(BlueprintCallable, Category = "GameplayCue", Meta = (AutoCreateRefTerm = "GameplayCueParameters", GameplayTagFilter = "GameplayCue"))
void AddGameplayCueLocal(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

UFUNCTION(BlueprintCallable, Category = "GameplayCue", Meta = (AutoCreateRefTerm = "GameplayCueParameters", GameplayTagFilter = "GameplayCue"))
void RemoveGameplayCueLocal(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);
void UPAAbilitySystemComponent::ExecuteGameplayCueLocal(const FGameplayTag GameplayCueTag, const FGameplayCueParameters & GameplayCueParameters)
{
	UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::Executed, 
    
    GameplayCueParameters);
}

void UPAAbilitySystemComponent::AddGameplayCueLocal(const FGameplayTag GameplayCueTag, const FGameplayCueParameters & GameplayCueParameters)
{
	UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::OnActive, GameplayCueParameters);
	UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::WhileActive, GameplayCueParameters);
}

void UPAAbilitySystemComponent::RemoveGameplayCueLocal(const FGameplayTag GameplayCueTag, const FGameplayCueParameters & GameplayCueParameters)
{
	UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(GetOwner(), GameplayCueTag, EGameplayCueEvent::Type::Removed, GameplayCueParameters);
}

Parameter

큐는 추가적인 정보를 담고 있는FGameplayCueParameters 구조체 타입의 파라미터를 받는다. 만약 큐를 직접 트리거할 일이 있다면 이 FGameplayCueParameters를 직접 채워서 전달해야 한다. 게임플레이 이펙트에 의해 큐가 트리거 될 때는 아래 변수들이 자동으로 파라미터를 채워 주게 된다.

  • AggregatedSourceTags
  • AggregatedTargetTags
  • GameplayEffectLevel
  • AbilityLevel
  • EffectContext
  • Magnitude

파라미터 구조체 내부의 SourceObject 변수는 큐를 수동으로 트리거할 때 임의 데이터를 전달하기에 좋은 장소가 된다.

Prevent Cues from Firing

가끔 큐를 트리거하고 싶지 않을 때가 있다. 예를 들어 공격을 가드했을 때는 충돌 이벤트와 바인딩 된 충돌 이펙트를 트리거하지 않을 수 있다.

이것을 GameplayEffectExecutionCalculations 내부에서 OutExecutionOutput.MarkGameplayCueHandleManually() 함수를 통해 큐 이벤트를 타겟 또는 소스의 ASC에 직접 전달함으로써 구현할 수 있다.

profile
울면안돼 쫄면안돼 냉면됩니다

0개의 댓글