https://github.com/tranek/GASDocumentation?tab=readme-ov-file#concepts-gc

 

GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer s

My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer sample project. - tranek/GASDocumentation

github.com

Definition

  • Gameplay와 직접적인 관계가 없는 것들을 실행
    • Sound, Particle, Camer Shake 등
  • 보통은 Replicate되고, predict 가능하다.
  • Gameplay Cue의 필수 상위 이름이 포함된 Gameplay Tag와 EventType을
    ASC를 통해 GameplayCueManager에 전송해 실행한다.
  • 다음 조건을 만족하는 것들은 GameplayCue의 GameplayTag를 기반으로 한 이벤트를 구독할 수 있다.
    • GameplayCueNotify 객체
    • IGameplayCueInterface를 Implement 한 Actor

GameplayCueNotify Class

  • GameplayCueNotify는 어느 것이든 어떤 Event에도 반응하도록 작업이 되어 있다.
    • 다만 특정 Event에 더 적합한 GameplayCueNotify를 제공하고 있다.

GameplayCueNotify_Static

  • GameplayCue와 관련된 정보를 저장하지 않고 즉시 실행 하는 경량 Notify
    • ClassDefaultObject에서 작업이 이루어짐
  • Instant나 Periodic GE와 같이 한번만 즉발로 효과가 나타나야 하는 케이스에 적합함

GameplayCueNotify_Actor

  • GameplayCue와 관련된 정보를 저장할 수 있음
    • Spawn 될 때 새 Instance를 생성
    • 생성된 Instance를 통해 Remove 시점에 필요한 정보를 확인
  • 또한 동시에 얼마나 많은 GameplayCue가 존재할 수 있는지에 대한 정보를 지정할 수도 있다.
  • Duration이나 Infinite와 같이 종료 시점이 정해져 있거나, 그 시점에 어떤 행동을 요구할 때 적합함.
  • AutoDestoryOnRemove 옵션 값을 유의해서 사용해야 한다.
    • 활성화 되어 있을 시 Add에서 발생하는 후속 작업이 호출되지 않을 수 있다.
  • ASC의 Replication Mode가 Full이 아닌 경우,
    Listen Server 환경에서 Add와 Remove 이벤트가 Server에 2번 발생한다.
    • 한 번은 Gameplay Event가 등록될 때
    • 다른 한번은 Client에 Minimal NetMultiCast가 발동할 때.
  • 하지만 WhileActive 이벤트는 여전히 한번만 발생한다.
  • Client에서는 모든 Event가 1번씩만 발생한다.

Trigger Gameplay Cue

  • 내부에서 GE가 성공적으로 등록되면, 실행되어야만 하는 GameplayCue의 모든 GmeplayTag가 채워진다.

  • UGameplayAbility에서는 GameplayCue에 대한 Execute/Add/Remove BP Node를 제공한다.
  • C++에서는 아래 함수들을 통해 사용할 수 있다.
더보기
/** 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);

/** Add gameplaycue for minimal replication mode. Should only be called in paths that would replicate gameplaycues in other ways (through GE for example) if not in minimal replication mode */
void AddGameplayCue_MinimalReplication(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());

/** Remove a persistent gameplay cue */
void RemoveGameplayCue(const FGameplayTag GameplayCueTag);

/** Remove gameplaycue for minimal replication mode. Should only be called in paths that would replicate gameplaycues in other ways (through GE for example) if not in minimal replication mode */
void RemoveGameplayCue_MinimalReplication(const FGameplayTag GameplayCueTag);

/** Removes any GameplayCue added on its own, i.e. not as part of a GameplayEffect. */
void RemoveAllGameplayCues();

/** Handles gameplay cue events from external sources */
void InvokeGameplayCueEvent(const FGameplayEffectSpecForRPC& Spec, EGameplayCueEvent::Type EventType);
void InvokeGameplayCueEvent(const FGameplayTag GameplayCueTag, EGameplayCueEvent::Type EventType, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());
void InvokeGameplayCueEvent(const FGameplayTag GameplayCueTag, EGameplayCueEvent::Type EventType, const FGameplayCueParameters& GameplayCueParameters);

Local Gameplay Cues

  • ASC와 Gameplay Ability에서 제공하는 GameplayCue 실행 함수는 기본적으로 replicate 된다.
    • 각 GameplayCue Event는 multicast RPC이다.
더보기
// Do not call these functions directly, call the wrappers on GameplayCueManager instead
UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCueExecuted_FromSpec(const FGameplayEffectSpecForRPC Spec, FPredictionKey PredictionKey) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCueExecuted(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayEffectContextHandle EffectContext) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCuesExecuted(const FGameplayTagContainer GameplayCueTags, FPredictionKey PredictionKey, FGameplayEffectContextHandle EffectContext) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCueExecuted_WithParams(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCuesExecuted_WithParams(const FGameplayTagContainer GameplayCueTags, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCueAdded(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayEffectContextHandle EffectContext) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCueAdded_WithParams(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayCueParameters Parameters) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCueAddedAndWhileActive_FromSpec(const FGameplayEffectSpecForRPC& Spec, FPredictionKey PredictionKey) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCueAddedAndWhileActive_WithParams(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;

UFUNCTION(NetMulticast, unreliable)
void NetMulticast_InvokeGameplayCuesAddedAndWhileActive_WithParams(const FGameplayTagContainer GameplayCueTags, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;
  • 이는 많은 RPC를 야기하는데,
    GAS는 동일한 Gameplay Cue RPC가 net update 당 최대 2개만 발생하도록 제한하고 있다.
    • 우리는 이 문제를 Local Gameplay Cue를 사용함으로 해결할 수 있다.
      • Local Gameplay Cue는 GAS에서 제공하는 기능이 아니라 Gameplay Cue 사용 권장 사항이다.
    • Local Gameplay Cue는 각 Client에서 오직 Execute, Add, Remove를 시행한다.
  • Local GameplayCue를 호출하기 적절한 케이스는 다음과 같다.
    • 투사체 
    • 물리 공격 
    • Animation Montage에서 발생하는 GameplayCue
  • 만약 GameplayCue가 Local에서 Add 되었다면, Remove 역시 Local에서 수행 되어야만 한다.

Gameplay Cue Parameter

  • GameplayCue는 외부 정보를 FGameplayCueParameters라는 구조체에 담아 파라미터로 전달받는다.
  • GameplayAbility나 ASC의 함수에서 GameplayCue를 실행하고 싶다면,
    반드시 GameplayCueparameters를 채워서 GameplayCue에 전달해야만 한다.
  • 만약 GameplayCue가 GE로부터 발생했다면, FGameplayCueParameters에서 아래 변수들이 자동으로 채워진다.
    • AggregatedSourceTags
    • AggregatedTargetTags
    • GameplaEffectLevel
    • AbilityLevel
    • EffectContext
    • Magnitude
  • SourceObject 항목은 GameplayCue를 실행시킬 때 임시 데이터를 전달하기 적절한 변수이다.
  • Instigator와 같은 FGameplayCueParameters 안의 일부 변수들은 EffectContext에 이미 존재할 수 있다.
    • 또한 EffectContext는 GameplayCue가 World에서 Spawn 된 Location을 FHitResult로 가지고 있기도 하다.
  • 하지만 EffectContext를 확장할 때에는 GameplayCue에 더 많은 정보를 가지게 하는 것이 적절할 것이다.
    • 특히 GameplayCue가 GE에서 발생을 한다면 더더욱이.
  • UAbilitySystemGlobals에서는 FGameplayCueParameters를 채우는 함수들을 제공한다.
    • 이들은 virtual로 선언되어 있어 override를 통해 더 많은 정보를 자동으로 채워주도록 확장할 수 있다.
더보기
/** Initialize GameplayCue Parameters */
virtual void InitGameplayCueParameters(FGameplayCueParameters& CueParameters, const FGameplayEffectSpecForRPC &Spec);
virtual void InitGameplayCueParameters_GESpec(FGameplayCueParameters& CueParameters, const FGameplayEffectSpec &Spec);
virtual void InitGameplayCueParameters(FGameplayCueParameters& CueParameters, const FGameplayEffectContextHandle& EffectContext);

Gameplay Cue Manager

  • GameplayCueManager는 Default로 다음 기능이 동작한다.
    • GameplayCueNotifier에 대한 전체 Game Directory를 Scan
    • Scan 한 Directory들을 Play 할 때 Memory에 Load
  • 또한 defaultGame.ini에서 Scan 할 Game Directory를 변경할 수 있다.
더보기
[/Script/GameplayAbilities.AbilitySystemGlobals]
GameplayCueNotifyPaths="/Game/GASDocumentation/Characters"
  • 우리는 GameplayCueManager가 모든 GameplayCueNotifier를 Scan하기를 원한다.
    • 하지만 이들을 하나하나 Async Load 하는 것은 원하지 않는다.
    • GameplayCueManager는 Level에서 사용되는지 여부와 무관하게 모든 GameplayCueNotifier와
      관련 Sound, Particle을 Memory에 올려둘 것이다.
  • GameplayCue가 게임 시작 할 때 Async Load 하는 것에 대한 대안은 게임 중 실행 될 때 Async Load 하는 것이다.
    • 이는 Play 중 GameplayCue가 처음 호출 될 때 해당 Effect에 대해 약간의 Delay를 안고 가는 대신,
      불필요한 메모리 사용과 Async Load를 하는 동안 발생할 수 있는 Hard Freeze를 줄여준다.
    • Editor에서는 Particle System을 컴파일 해야 하는 경우 GameplayCue를 처음 Load 하는 동안
      약간의 히칭이나 Freeze가 발생할 수 있다.
  • 이러한 기능을 수행하기 위해, 가장 먼저 UGameplayCueManager를 확장해 아래와 같이 함수를 override 해야 한다.
더보기
virtual bool ShouldAsyncLoadRuntimeObjectLibraries() const override
{
	return false;
}
  • 그리고 확장한 Class Type을 DefaultGame.ini를 통해 AbilitySystemGlobals에 전달해야 한다.
더보기
[/Script/GameplayAbilities.AbilitySystemGlobals]
GlobalGameplayCueManagerClass="/Script/ParagonAssets.PBGameplayCueManager"

Prevent Gameplay Cue from Firing

  • 때로는 GameplayCue가 발생하는 것을 막아야 하는 경우가 있다.
    • 공격이 막혔다던가.
    • Damage GE에 연결된 Hit Impact를 재생하고 싶지 않다던가.
    • 별개의 Hit Impact를 재생하고 싶다던가.
  • 그럴 때에는 아래 함수를 호출하고, 명시적으로 GameplayCue Event를 Targer/Source ASC에 전달하면 된다.
더보기
void FGameplayEffectCustomExecutionOutput::MarkGameplayCuesHandledManually()
{
	bHandledGameplayCuesManually = true;
}
  • 만약 특정 ASC에서 영구적으로 GameplayCue가 발생하지 않기 원한다면, 아래 변수 값을 수정해주면 된다.
더보기
/** Suppress all GameplayCues on this component */
UPROPERTY()
bool bSuppressGameplayCues;

Batch Gameplay Cue

  • 각 Gameplay Cue는 unreliable NetMulticast RPC로 호출된다.
  • 만약 동시에 여러개의 Gameplay Cue가 발생 한다면, 몇 가지 최적화를 적용할 수 있다.
    • RPC를 하나로 합치던가
    • Data를 압축하여 더 적은 정보를 패킷으로 전송하던가.

Manual RPC

  • 예를 들어 산탄을 사격한다고 가정하자.
    • 산탄이 발사하면 pellet의 수 만큼 TargetData에 대한 EffectContext가 발생을 한다.
    • 이걸 1개로 합친다 하더라도 Data 용량은 1회 RPC 용량(500 byte)를 초과할 것이다.
  • 이를 좀 더 최적화 하는 방법은 별도의 구조체로 정보를 보내는 것이다.
    • 이 구조체는 Hit Location은 효과적으로 encode 하였거나,
      Client에서 Impact Location을 재생성/근사 할 수 있도록 Random Seed Number를 가지고 있을 것이다.
    • Client는 이 정보를 통해 Local Gameplay Cue를 발생시켜 작업을 복구할 것이다.
  • 이 방식은 보통 다음 조건에서 사용된다.
    • GameplayCue에서 필요로 하는 일부 parameter가 GameplayCueParameter에서 제공한 것과 맞지 않은 경우
    • GameplayCue에서 필요로 하는 일부 parameter를 EffectContext에 추가하고 싶지 않은 경우
      • DamageID, 치명타 계수, 실드 파괴 지수, 결정타 지수 등

사용 방법

  • FScopedGameplayCueSendContext를 선언한다.
    • 이 구조체는 Scope를 벗어날 때까지 UGameplayCueManager::FlushPendingCues() 함수를 억제한다.
    • 즉, 모든 GameplayCue는 이 구조체 Scope를 벗어날 때까지 queue에 쌓이게 된다.
더보기
/**
 *	FScopedGameplayCueSendContext
 *	Add this around code that sends multiple gameplay cues to allow grouping them into a smalkler number of cues for more efficient networking
 */
struct GAMEPLAYABILITIES_API FScopedGameplayCueSendContext
{
	FScopedGameplayCueSendContext();
	~FScopedGameplayCueSendContext();
};
  • FlushPendingCues() 함수를 override하여 Custom Struct에서
    몇몇 Custom Gamplay Tag로 batch 할수 있는 GameplayCue를 Merge하고, Client로 RPC 한다.
더보기
void UGameplayCueManager::FlushPendingCues()
{
	OnFlushPendingCues.Broadcast();

	TArray<FGameplayCuePendingExecute> LocalPendingExecuteCues = PendingExecuteCues;
	PendingExecuteCues.Empty();
	for (int32 i = 0; i < LocalPendingExecuteCues.Num(); i++)
	{
		FGameplayCuePendingExecute& PendingCue = LocalPendingExecuteCues[i];

		// Our component may have gone away
		if (PendingCue.OwningComponent)
		{
			bool bHasAuthority = PendingCue.OwningComponent->IsOwnerActorAuthoritative();
			bool bLocalPredictionKey = PendingCue.PredictionKey.IsLocalClientKey();

			IAbilitySystemReplicationProxyInterface* RepInterface = PendingCue.OwningComponent->GetReplicationInterface();
			if (RepInterface == nullptr)
			{
				// If this returns null, it means "we are replicating througha proxy and have no avatar". Which in this case, we should skip
				continue;
			}

			// TODO: Could implement non-rpc method for replicating if desired
			if (PendingCue.PayloadType == EGameplayCuePayloadType::CueParameters)
			{
				if (ensure(PendingCue.GameplayCueTags.Num() >= 1))
				{
					if (bHasAuthority)
					{
						RepInterface->ForceReplication();
						if (PendingCue.GameplayCueTags.Num() > 1)
						{
							RepInterface->Call_InvokeGameplayCuesExecuted_WithParams(FGameplayTagContainer::CreateFromArray(PendingCue.GameplayCueTags), PendingCue.PredictionKey, PendingCue.CueParameters);
						}
						else
						{
							RepInterface->Call_InvokeGameplayCueExecuted_WithParams(PendingCue.GameplayCueTags[0], PendingCue.PredictionKey, PendingCue.CueParameters);

							static FName NetMulticast_InvokeGameplayCueExecuted_WithParamsName = TEXT("NetMulticast_InvokeGameplayCueExecuted_WithParams");
							CheckForTooManyRPCs(NetMulticast_InvokeGameplayCueExecuted_WithParamsName, PendingCue, PendingCue.GameplayCueTags[0].ToString(), nullptr);
						}
					}
					else if (bLocalPredictionKey)
					{
						for (const FGameplayTag& Tag : PendingCue.GameplayCueTags)
						{
							PendingCue.OwningComponent->InvokeGameplayCueEvent(Tag, EGameplayCueEvent::Executed, PendingCue.CueParameters);
						}
					}
				}
			}
			else if (PendingCue.PayloadType == EGameplayCuePayloadType::FromSpec)
			{
				if (bHasAuthority)
				{
					RepInterface->ForceReplication();
					RepInterface->Call_InvokeGameplayCueExecuted_FromSpec(PendingCue.FromSpec, PendingCue.PredictionKey);

					static FName NetMulticast_InvokeGameplayCueExecuted_FromSpecName = TEXT("NetMulticast_InvokeGameplayCueExecuted_FromSpec");
					CheckForTooManyRPCs(NetMulticast_InvokeGameplayCueExecuted_FromSpecName, PendingCue, PendingCue.FromSpec.Def ? PendingCue.FromSpec.ToSimpleString() : TEXT("FromSpecWithNoDef"), PendingCue.FromSpec.EffectContext.Get());
				}
				else if (bLocalPredictionKey)
				{
					PendingCue.OwningComponent->InvokeGameplayCueEvent(PendingCue.FromSpec, EGameplayCueEvent::Executed);
				}
			}
		}
	}
}
  • Client는 Custom Struct를 받아 Local GameplayCue로 실행한다.

Multiple Gameplay Cue on One Gameplay Effect

  • GameplayEffect에서의 Gameplay Cue는 이미 하나의 RPC로 전달된다.
  • 아래 함수에서 모든 GameplayEffectSpec를 FGameplayEffectSpecForRPC로 변환하여
    ASC의 Replication Mode와 무관하게 unreliable NetMulticast로 전달한다.
더보기
virtual void InvokeGameplayCueAddedAndWhileActive_FromSpec(UAbilitySystemComponent* OwningComponent, const FGameplayEffectSpec& Spec, FPredictionKey PredictionKey);

 

  • 하지만 이 방식은 GameplayEffectSpec에 어떤 정보가 있는지에 따라 매우 큰 Bandwidth를 가질 수 있다.
    • 여기에 대해 아래 변수를 통해 좀 더 기능을 최적화 할 수 있다.
더보기
/**
 *	Enabling AbilitySystemAlwaysConvertGESpecToGCParams will mean that all calls to gameplay cues with GameplayEffectSpecs will be converted into GameplayCue Parameters server side and then replicated.
 *	This potentially saved bandwidth but also has less information, depending on how the GESpec is converted to GC Parameters and what your GC's need to know.
 */

int32 AbilitySystemAlwaysConvertGESpecToGCParams = 0;
static FAutoConsoleVariableRef CVarAbilitySystemAlwaysConvertGESpecToGCParams(TEXT("AbilitySystem.AlwaysConvertGESpecToGCParams"), AbilitySystemAlwaysConvertGESpecToGCParams, TEXT("Always convert a GameplayCue from GE Spec to GC from GC Parameters on the server"), ECVF_Default );
  • 이 변수는 FGameplayEffectSpec를 FGameplayCueParameter로 변환하여 RPC에 전달한다.
  • Bandwidth를 절약할 수 있는 대신 더 적은 정보를 담을 수 밖에 없다.
    • 때문에 GESpec을 GameplayCueParameter로 어떻게 변환할지가 중요하다.

Gameplay Cue Event

더보기
UENUM(BlueprintType)
namespace EGameplayCueEvent
{
	/** Indicates what type of action happened to a specific gameplay cue tag. Sometimes you will get multiple events at once */
	enum Type : int
	{
		/** Called when a GameplayCue with duration is first activated, this will only be called if the client witnessed the activation */
		OnActive,

		/** Called when a GameplayCue with duration is first seen as active, even if it wasn't actually just applied (Join in progress, etc) */
		WhileActive,

		/** Called when a GameplayCue is executed, this is used for instant effects or periodic ticks */
		Executed,

		/** Called when a GameplayCue with duration is removed */
		Removed
	};
}

OnActive

  • GameplayCue가 활성화(Add) 될 때 호출
  • GameplayCue가 시작할 때 발생하는 모든 것에서 사용
    • 단, Joiner가 늦어서 이 Event를 놓치더라도 WhileActive에서 보강해줄 수 있다.

WhileActive

  • GameplayCue가 동작 할 때 호출된다.
    • 심지어 아직 등록이 완료되지 않더라도 동작만 하면 호출된다.
  • GameplayCueNotify_Actor가 Add 되거나 관련 작업이 생기면 OnActive와 같이 한번만 호출된다.
    • 따라서 Tick이 아니다.
    • Actor가 GameplayCueNotify_Actor 범위 안에 들어올 때마다 한번씩 발생한다.
  • 만약 Tick이 필요하다면, GameplayCueNotify_Actor::Tick()을 사용해야 한다.

Removed

  • GameplayCue가 제거 될 때 호출된다.
    • BP에서는 OnRemove Event가 이 기능과 관련 있다.
  • OnActive/WhileActive에서 추가한 것들을 반드시지워야 한다.
  • Actor가 GameplayCueNotify_Actor 범위 밖을 나갈 때마다 한번씩 발생한다.

Executed

  • Instant Effect나 Periodic Tick에서 GameplayCue가 실해오딜 때 호출된다.
    • BP에서는 OnExecute가 관련있다.

Gameplay Cue Reliability

  • 일반적으로 GameplayCue는 Unreliable 함을 염두해야만 한다.
    • 때문에 Gameplay에 직접적으로 영향을 주는 모든 겨우에 대해서는 적합하지 않다.
  • 만약 GameplayCue를 Relaible하게 사용하고 싶다면, 
    GameplayEffect으로부터 등록하여 WhileActive에서 FX를 추가하고, OnRevmoe에서 제거해야 한다.

Executed GameplayCues

  • 이 경우에는 Unreliable Multicast로 등록이 되기 때문에 항상 Unreliable하다.

GameplayCues applied from GameplayEffect

  • Autonomous Proxy의 경우 OnActive, WhileActive, OnRemove에 대해 Reliable하다.
    • OnActive, WhileActive는 다음 함수에서 호출된다.
    • 더보기
      void FActiveGameplayEffectsContainer::PostReplicatedReceive(const FFastArraySerializer::FPostReplicatedReceiveParameters& Parameters)
      {
      	// After the array has been replicated, invoke GC events ONLY if the effect is not inhibited
      	// We postpone this check because in the same net update we could receive multiple GEs that affect if one another is inhibited	
      	if (Owner != nullptr)
      	{
      		QUICK_SCOPE_CYCLE_COUNTER(STAT_ActiveGameplayEffectsContainer_NetDeltaSerialize_CheckRepGameplayCues);
      
      		if ( LIKELY(UE::GameplayEffect::bSkipUnmappedReferencesCheckForGameplayCues) )
      		{
      			if (Owner->IsReadyForGameplayCues())
      			{
      				Owner->HandleDeferredGameplayCues(this);
      			}
      		}
      		else
      		{
      // Keep this until we have actually deprecated the parameter just in case.
      PRAGMA_DISABLE_DEPRECATION_WARNINGS
      			if (!Parameters.bHasMoreUnmappedReferences) // Do not invoke GCs when we have missing information (like AActor*s in EffectContext)
      			{
      				NumConsecutiveUnmappedReferencesDebug = 0;
      				if (Owner->IsReadyForGameplayCues())
      				{
      					Owner->HandleDeferredGameplayCues(this);
      				}
      			}
      			else
      			{
      				++NumConsecutiveUnmappedReferencesDebug;
      
      				constexpr uint32 HighNumberOfConsecutiveUnmappedRefs = 30;
      				ensureMsgf(NumConsecutiveUnmappedReferencesDebug < HighNumberOfConsecutiveUnmappedRefs, TEXT("%hs: bHasMoreUnmappedReferences is preventing GameplayCues from firing"), __func__);
      				UE_CLOG((NumConsecutiveUnmappedReferencesDebug % HighNumberOfConsecutiveUnmappedRefs) == 0, LogAbilitySystem, Error, TEXT("%hs: bHasMoreUnmappedReferences is preventing GameplayCues from firing (%u consecutive misses)"), __func__, NumConsecutiveUnmappedReferencesDebug);
      			}
      PRAGMA_ENABLE_DEPRECATION_WARNINGS
      		}
      	}
      }

       

      void UAbilitySystemComponent::HandleDeferredGameplayCues(const FActiveGameplayEffectsContainer* GameplayEffectsContainer)
      {
      	for (const FActiveGameplayEffect& Effect : GameplayEffectsContainer)
      	{
      		if (Effect.bIsInhibited == false)
      		{
      			if (Effect.bPendingRepOnActiveGC)
      			{
      				InvokeGameplayCueEvent(Effect.Spec, EGameplayCueEvent::OnActive);
      			}
      			if (Effect.bPendingRepWhileActiveGC)
      			{
      				InvokeGameplayCueEvent(Effect.Spec, EGameplayCueEvent::WhileActive);
      			}
      		}
      
      		Effect.bPendingRepOnActiveGC = false;
      		Effect.bPendingRepWhileActiveGC = false;
      	}
      }


    • OnRemoved의 경우 다음 함수에서 호출된다.
    • 더보기
      void FActiveGameplayEffectsContainer::RemoveActiveGameplayEffectGrantedTagsAndModifiers(const FActiveGameplayEffect& Effect, bool bInvokeGameplayCueEvents)
      {
      	// Update AttributeAggregators: remove mods from this ActiveGE Handle
      	if (Effect.Spec.GetPeriod() <= UGameplayEffect::NO_PERIOD)
      	{
      		for (const FGameplayModifierInfo& Mod : Effect.Spec.Def->Modifiers)
      		{
      			if (Mod.Attribute.IsValid())
      			{
      				if (const FAggregatorRef* RefPtr = AttributeAggregatorMap.Find(Mod.Attribute))
      				{
      					RefPtr->Get()->RemoveAggregatorMod(Effect.Handle);
      				}
      			}
      		}
      	}
      
      	// Update gameplaytag count and broadcast delegate if we are at 0
      	Owner->UpdateTagMap(Effect.Spec.Def->GetGrantedTags(), -1);
      	Owner->UpdateTagMap(Effect.Spec.DynamicGrantedTags, -1);
      
      	// Update our owner with the blocked ability tags this GameplayEffect adds to them
      	Owner->UnBlockAbilitiesWithTags(Effect.Spec.Def->GetBlockedAbilityTags());
      
      	// Update minimal replication if needed.
      	if (ShouldUseMinimalReplication())
      	{
      		Owner->RemoveMinimalReplicationGameplayTags(Effect.Spec.Def->GetGrantedTags());
      		Owner->RemoveMinimalReplicationGameplayTags(Effect.Spec.DynamicGrantedTags);
      	}
      
      PRAGMA_DISABLE_DEPRECATION_WARNINGS
      	// Cancel/remove granted abilities
      	if (IsNetAuthority())
      	{
      		for (const FGameplayAbilitySpecDef& AbilitySpecDef : Effect.Spec.GrantedAbilitySpecs)
      		{
      			if (AbilitySpecDef.AssignedHandle.IsValid())
      			{
      				switch(AbilitySpecDef.RemovalPolicy)
      				{
      				case EGameplayEffectGrantedAbilityRemovePolicy::CancelAbilityImmediately:
      					{
      						Owner->ClearAbility(AbilitySpecDef.AssignedHandle);
      						break;
      					}
      				case EGameplayEffectGrantedAbilityRemovePolicy::RemoveAbilityOnEnd:
      					{
      						Owner->SetRemoveAbilityOnEnd(AbilitySpecDef.AssignedHandle);
      						break;
      					}
      				default:
      					{
      						// Do nothing to granted ability
      						break;
      					}
      				}
      			}
      		}
      	}
      PRAGMA_ENABLE_DEPRECATION_WARNINGS
      
      	// Update GameplayCue tags and events
      	if (!Owner->bSuppressGameplayCues)
      	{
      		for (const FGameplayEffectCue& Cue : Effect.Spec.Def->GameplayCues)
      		{
      			// If we use Minimal/Mixed Replication, then this will cause EGameplayCueEvent::Removed
      			if (ShouldUseMinimalReplication())
      			{
      				for (const FGameplayTag& CueTag : Cue.GameplayCueTags)
      				{
      					Owner->RemoveGameplayCue_MinimalReplication(CueTag);
      				}
      			}
      			else
      			{
      				// Perform pseudo-RemoveCue (without affecting ActiveGameplayCues, as we were not inserted there - see AddActiveGameplayEffectGrantedTagsAndModifiers)
      				Owner->UpdateTagMap(Cue.GameplayCueTags, -1);
      
      				if (bInvokeGameplayCueEvents)
      				{
      					Owner->InvokeGameplayCueEvent(Effect.Spec, EGameplayCueEvent::Removed);
      				}
      			}
      		}
      	}
      }
  • Simulated Proxy의 경우 WhileActive와 OnRemove가 Reliable하다.
    • 아래 함수에서의 Replication이 WhileActive, OnRemove Event를 호출한다.
    • 더보기
      /** Replicated gameplaycues when in minimal replication mode. These are cues that would come normally come from ActiveGameplayEffects (but since we do not replicate AGE in minimal mode, they must be replicated through here) */
      UPROPERTY(Replicated)
      FActiveGameplayCueContainer MinimalReplicationGameplayCues;

       

    • OnActive Event는 Unreliable Multicast로 전달된다.

GameplayCues applied withou a GameplayEffect 

  • Autonomous Proxy의 경우 OnRemove Event를 Reliable하게 받는다.
    • OnActive, WhileActive Event는 Unreliable Multicast로 전달된다.
  • Simulated Proxy의 경우 WhileActive, OnRemove Event를 Reliable하게 받는다.
    • UASC::MinimalReplicationGameplayCue의 Replication이 WhileActive, OnRemove를 호출한다.
    • OnActive Event는 Unreliable Multicast로 전달된다.

'UE5 > GAS' 카테고리의 다른 글

[GAS] Prediction  (0) 2024.05.21
[GAS] Gameplay Ability  (0) 2024.05.17
[GAS] Ability Task  (0) 2024.05.16
[GAS] Gameplay Effect  (0) 2024.05.15
[GAS] Attribute Set  (1) 2024.05.14

+ Recent posts