Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

게임 개발 메모장

[ UE5 ] GameplayAbilitySystem : GameplayCue 본문

언리얼 엔진/기능

[ UE5 ] GameplayAbilitySystem : GameplayCue

Dev_Moses 2024. 1. 10. 10:56

GameplayCue 정의

GameplayCue(GC)는 사운드 효과, 파티클 효과, 카메라 흔들림 등 게임 플레이와 관련이 없는 것들을 실행합니다. GameplayCue는 일반적으로 (명시적으로 로컬에서 실행, 추가 또는 제거되지 않는 한) 리플리케이트되고 예측됩니다.

GameplayCue를 트리거하기 위해서는 ASC를 통해 GameplayCue의 필수 부모 이름과 이벤트 유형(실행, 추가 또는 제거)이 포함된 해당 GameplayTag를 GameplayCueManager에 전송합니다. GameplayCueNotify 오브젝트와 IGameplayCueInterface 를 구현하는 다른 액터는 GameplayCue의 GameplayCueTag를 기반으로 이러한 이벤트에 구독할 수 있습니다.

💡Note: 다시 한 번 말씀드리자면, GameplayCue GameplayTag는 GameplayCue의 부모 GameplayTag에서 시작해야 합니다. 예를 들어, 유효한 GameplayCue GameplayTag는 GameplayCue.A.B.C일 수 있습니다.

 

GameplayCueNotify에는 Static과 Actor의 두 가지 클래스가 있습니다. 이들은 서로 다른 이벤트에 반응하며, 서로 다른 유형의 GameplayEffect가 이를 트리거할 수 있습니다. 로직으로 해당 이벤트를 오버라이드하세요.

GameplayCue Class Event  GameplayEffect Type 내용
GameplayCueNotify_Static 실행 Instant or Periodic Static GameplayCueNotify는 클래스 디폴트 오브젝트에서 작동하며(인스턴스가 없음을 의미) 타격 임팩트와 같은 일회성 이펙트에 적합합니다.
GameplayCueNoify_Actor 추가 혹은 삭제 Duration or Infinite Actor GameplayCueNotify가 추가되면 새 인스턴스를 스폰합니다. 인스턴스화되어 있기 때문에 제거될 때까지 계속 동작을 할 수 있습니다. 백킹 지속 시간 또는 무한 GameplayEffect가 제거되거나 수동으로 remove를 호출하면 제거되는 사운드 및 파티클 이펙트를 루핑하는 데 좋습니다. 또한 동시에 추가할 수 있는 개수를 관리할 수 있는 옵션도 제공되므로 동일한 효과를 여러 번 적용할 때 사운드나 파티클이 한 번만 시작되도록 할 수 있습니다.

 

GameplayCueNotify는 기술적으로 모든 이벤트에 응답할 수 있지만 일반적으로 이 방식으로 사용합니다.

💡Note: GameplayCueNotify_Actor를 사용하는 경우, 제거에서 자동 소멸을 체크하지 않으면 해당 GameplayCueTag를 추가하는 후속 호출이 작동하지 않습니다.

 

전체가 아닌 ASC Replication Mode를 사용하는 경우, 서버 플레이어(리스닝 서버)에서 추가 및 제거 GC 이벤트가 두 번 발생합니다. 한 번은 GE를 적용할 때, 다른 한 번은 "최소" NetMultiCast에서 클라이언트로 전송할 때 발생합니다. 하지만 WhileActive 이벤트는 여전히 한 번만 발동합니다. 모든 이벤트는 클라이언트에서 한 번만 발생합니다.

샘플 프로젝트에는 기절 및 질주 이펙트를 위한 GameplayCueNotify_Actor가 포함되어 있습니다. 또한 파이어건의 발사체 임팩트를 위한 GameplayCueNotify_Static도 있습니다. 이러한 GC는 GE를 통해 리플리케이트하는 대신 로컬에서 트리거하여 더욱 최적화할 수 있습니다. 저는 샘플 프로젝트에서 초보자도 쉽게 사용할 수 있는 방법을 보여드리기로 했습니다.

 

GameplayCue 트리거링

GameplayEffect가 성공적으로 적용되었을 때(태그나 면역에 의해 차단되지 않았을 때) GameplayEffect 내부에서 트리거되어야 하는 모든 GameplayCue의 GameplayTag를 채웁니다.

 

UGameplayAbility는 GameplayCue를 실행, 추가, 제거할 수 있는 블루프린트 노드를 제공합니다.

 

C++ 에서는 ASC 에서 직접 함수를 호출하거나 ASC 서브클래스의 블루프린트에 노출시킬 수 있습니다:

/** GameplayCue는 자체적으로 올 수도 있습니다. 이는 히트 결과 등을 전달하기 위해 선택적 EffectContext를 사용합니다. */
void ExecuteGameplayCue(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());
void ExecuteGameplayCue(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

/** 영구적인 GameplayCue 추가 */
void AddGameplayCue(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());
void AddGameplayCue(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

/** 영구 GameplayCue 제거 */
void RemoveGameplayCue(const FGameplayTag GameplayCueTag);
	
/** GameplayEffect의 일부가 아닌, 자체적으로 추가된 모든 GameplayCue를 제거합니다. */
void RemoveAllGameplayCues();

 

로컬 GameplayCue

GameplayAbility와 ASC에서 GameplayCue를 발동하기 위해 노출된 함수는 기본적으로 리플리케이트됩니다. 각 GameplayCue 이벤트는 멀티캐스트 RPC입니다. 이로 인해 많은 RPC가 발생할 수 있습니다. 또한 GAS는 순 업데이트당 최대 두 개의 동일한 GameplayCue RPC를 적용합니다. 가능한 경우 로컬 GameplayCue를 사용하여 이를 방지합니다. 로컬 GameplayCue는 개별 클라이언트에서만 실행, 추가 또는 제거합니다.

로컬 GameplayCue를 사용할 수 있는 시나리오:

  • 발사체 충격
  • 근접 충돌 충격
  • 게애니메이션 몽타주에서 발동되는 GameplayCue

ASC 서브클래스에 추가해야 하는 로컬 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);
}

GameplayCue가 로컬로 추가된 경우, 로컬에서 제거해야 합니다. 리플리케이션을 통해 추가된 경우 리플리케이션을 통해 제거해야 합니다.

 

GameplayCue 파라미터

GameplayCue는 파라미터로 GameplayCue에 대한 추가 정보가 들어있는 FGameplayCueParameter 구조체를 받습니다. GameplayAbility 또는 ASC의 함수에서GameplayCue를 수동으로 트리거하는 경우, GameplayCue에 전달되는 GameplayCue 파라미터 구조체를 수동으로 채워야 합니다. GameplayCue가 GameplayEffect에 의해 트리거되는 경우, 다음 변수는 GameplayCueParameter 구조체에 자동으로 채워집니다:

  • AggregatedSourceTags
  • AggregatedTargetTags
  • GameplayEffectLevel
  • AbilityLevel
  • EffectContext
  • Magnitude (GameplayEffect에 GameplayCue tag container 위의 드롭다운에서 선택한 크기에 대한 어트리뷰트와 해당 어트리뷰트에 영향을 주는 해당 모디파이어가 있는 경우)

GameplayCueParameter 구조체의 SourceObject 변수는 GameplayCue를 수동으로 트리거할 때 임의의 데이터를 GameplayCue에 전달할 수 있는 좋은 장소가 될 수 있습니다.

💡Note: Instigator와 같은 파라미터 구조체의 일부 변수는 EffectContext 에 이미 존재할 수 있습니다. EffectContext에는 월드에서 GameplayCue를 스폰할 위치에 대한 FHitResult 를 포함할 수도 있습니다. EffectContext 를 서브클래싱하는 것은 GameplayCue, 특히 GameplayEffect에 의해 트리거되는 GameplayCue에 더 많은 데이터를 전달할 수 있는 잠재적으로 좋은 방법입니다.

자세한 내용은 UAbilitySystemGlobals에서 GameplayCueParameter 구조체를 채우는 세 가지 함수를 참조하세요. 이 함수는 가상이므로 오버라이드하여 추가 정보를 자동 채울 수 있습니다. 

/** GameplayCue 파라미터 초기화 */
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);

 

GameplayCue Manager

기본적으로 GameplayCueManager는 전체 게임 디렉터리에서 GameplayCueNotify를 스캔하여 플레이 시 메모리에 로드합니다. GameplayCueManager가 스캔하는 경로는 DefaultGame.ini에서 설정하여 변경할 수 있습니다.

[/Script/GameplayAbilities.AbilitySystemGlobals]
GameplayCueNotifyPaths="/Game/GASDocumentation/Characters"

GameplayCueManager가 모든 GameplayCueNotify를 스캔하고 찾도록 하되, 플레이 중 모든 GameplayCueNotify를 비동기 로드하지는 않기를 원합니다. 이렇게 하면 레벨에서 사용되었는지 여부와 관계없이 모든 GameplayCueNotify와 참조된 모든 사운드 및 파티클이 메모리에 저장됩니다. 파라곤과 같은 대규모 게임에서는 수백 메가 바이트의 불필요한 에셋이 메모리에 저장되어 시작 시 버벅거림과 게임 멈춤이 발생할 수 있습니다.

시작 시 모든 GameplayCue를 비동기 로드하는 대신 GameplayCue가 게임 내에서 트리거될 때만 비동기 로드하는 방법을 사용할 수 있습니다. 이렇게 하면 모든 GameplayCue를 비동기 로드하는 동안 불필요한 메모리 사용과 잠재적인 게임 하드 프리즈를 완화하는 대신, 플레이 도중 특정 GameplayCue가 처음 트리거될 때 이펙트가 지연될 수 있습니다. SSD에서는 이러한 잠재적 지연이 발생하지 않습니다. HDD에서는 테스트하지 않았습니다. UE 에디터에서 이 옵션을 사용하는 경우, 에디터가 파티클 시스템을 컴파일해야 하는 경우 GameplayCue를 처음 로드하는 동안 약간의 버벅임이나 멈춤 현상이 있을 수 있습니다. 빌드에서는 파티클 시스템이 이미 컴파일되어 있으므로 문제가 되지 않습니다.

먼저 UGameplayCueManager 를 서브클래싱하고 AbilitySystemGlobals 클래스에 DefaultGame.ini 에서 UGameplayCueManager 서브클래스를 사용하도록 지시해야 합니다.

[/Script/GameplayAbilities.AbilitySystemGlobals]
GlobalGameplayCueManagerClass="/Script/ParagonAssets.PBGameplayCueManager"

UGameplayCueManager 서브클래스에서 ShouldAsyncLoadRuntimeObjectLibraries()를 재정의합니다.

virtual bool ShouldAsyncLoadRuntimeObjectLibraries() const override
{
    return false;
}

 

GameplayCue가 발동되지 않도록 방지

GameplayCue가 발동되지 않기를 원할 때가 있습니다. 예를 들어 공격을 막는 경우 대미지 GameplayEffect에 첨부된 타격 효과를 재생하지 않거나 대신 커스텀 효과를 재생하고 싶을 수 있습니다. 이 작업은 GameplayEffectExecutionCalculations내에서 OutExecutionOutput.MarkGameplayCuesHandledManually()를 호출한 다음 GameplayCue 이벤트를 타겟 또는 소스의 ASC로 수동으로 전송하면 됩니다.

특정 ASC에서 GameplayCue가 발동되지 않도록 하려면 AbilitySystemComponent->bSuppressGameplayCues = true; 를 설정하면 됩니다.

 

GameplayCue 일괄 처리

트리거되는 각 GameplayCue는 신뢰할 수 없는 NetMulticast RPC입니다. 여러 GC를 동시에 발동하는 상황에서는 이를 하나의 RPC로 압축하거나 데이터를 적게 전송하여 대역폭을 절약할 수 있는 몇 가지 최적화 방법이 있습니다.

 

수동 RPC

8개의 펠릿을 발사하는 산탄총이 있다고 가정해 보겠습니다. 이는 8개의 트레이스 및 임팩트 GameplayCue입니다. GASShooter는 모든 트레이스 정보를 EffectContext TargetData로 숨겨서 하나의 RPC로 결합하는 게으른 접근 방식을 취합니다. 이렇게 하면 RPC가 8개에서 1개로 줄어들지만 여전히 네트워크를 통해 많은 양의 데이터(약 500바이트)를 하나의 RPC로 전송합니다. 보다 최적화된 접근 방식은 타격 위치를 효율적으로 인코딩하거나 임의의 시드 번호를 제공하여 수신 측에서 타격 위치를 다시 생성/근사화하는 커스텀 구조체가 포함된 RPC를 전송하는 것입니다. 그러면 클라이언트는 이 커스텀 구조체의 압축을 풀고 로컬에서 실행되는 GameplayCue로 다시 전환합니다.

 

작동 방식:

  1. FScopedGameplayCueSendContext 를 선언합니다. 그러면 범위를 벗어날 때까지 UGameplayCueManager::FlushPendingCues() 가 억제되어, FScopedGameplayCueSendContext 가 범위를 벗어날 때까지 모든 GameplayCue가 대기열에 대기합니다.
  2. UGameplayCueManager::FlushPendingCues() 를 오버라이드하여 커스텀 GameplayTag를 기반으로 일괄 처리할 수 있는 GameplayCue를 커스텀 구조체에 병합하고 클라이언트에 RPC할 수 있습니다.
  3. 클라이언트는 커스텀 구조체를 받아 로컬에서 실행되는 GameplayCue에 언패킹합니다.

이 메서드는 피해 수치, 치명타 표시기, 깨진 방패 표시기, 치명타 맞았음 표시기 등과 같이 GameplayCueParameters가 제공하는 것과 맞지 않는 GameplayCue에 대한 특정 파라미터가 필요하지만 EffectContext에 추가하고 싶지 않을 때도 사용할 수 있습니다.

 

하나의 GE에 여러 GC

GameplayEffect의 모든 GameplayCue는 이미 하나의 RPC에 전송되어 있습니다. 기본적으로 UGameplayCueManager::InvokeGameplayCueAddedAndWhileActive_FromSpec()은 ASC의 리플리케이션 모드와 상관없이 신뢰할 수 없는 NetMultiCast에서 전체 GameplayEffectSpec을 (하지만 FGameplayEffectSpecForRPC로 변환하여) 전송합니다. GameplayEffectSpec 에 무엇이 있느냐에 따라 대역폭이 많이 소모될 수 있습니다. cvar AbilitySystem.AlwaysConvertGESpecToGCParams 1을 설정하여 이를 최적화할 수 있습니다. 그러면 GameplayEffectSpec을 FGameplayEffectSpec 전체가 아닌 FGameplayCueParameter 구조체로 변환하고 이를 RPC 합니다. 이렇게 하면 잠재적으로 대역폭을 절약할 수 있지만, GESpec이 GameplayCueParameters로 변환되는 방식과 GC가 알아야 하는 정보에 따라 정보가 적을 수도 있습니다.

 

GameplayCue 이벤트

GameplayCue는 특정 EGameplayCueEvent에 반응합니다:

EGameplayCueEvent Description
OnActive Called when a GameplayCue is activated (added).
WhileActive Called when GameplayCue is active, even if it wasn't actually just applied (Join in progress, etc). This is not Tick! It's called once just like OnActive when a GameplayCueNotify_Actor is added or becomes relevant. If you need Tick(), just use the GameplayCueNotify_Actor's Tick(). It's an AActor after all.
Removed Called when a GameplayCue is removed. The Blueprint GameplayCue function that responds to this event is OnRemove.
Executed Called when a GameplayCue is executed: instant effects or periodic Tick(). The Blueprint GameplayCue function that responds to this event is OnExecute.

 

GameplayCue 시작 시 발생하는 GameplayCue의 모든 이펙트에 OnActive를 사용하되, 늦게 참여하는 사람이 놓쳐도 괜찮습니다. GameplayCue 의 진행 중인 이펙트 중 뒤늦게 조인하는 사람이 보길 원하는 이펙트에는 WhileActive를 사용합니다. 예를 들어 MOBA에서 타워 구조물이 폭발하는 GameplayCue가 있다면, 초기 폭발 파티클 시스템과 폭발 사운드는 OnActive에 넣고 나머지 진행 중인 불 파티클이나 사운드는 WhileActive에 넣을 수 있습니다. 이 시나리오에서는 뒤늦게 합류한 사람이 초기 폭발을 OnActive에서 재생하는 것은 의미가 없지만, 폭발이 발생한 후 지면에 지속적이고 반복되는 불 이펙트를 WhileActive에서 볼 수 있게 하려는 것입니다. OnRemove는 OnActive와 WhileActive에 추가된 모든 것을 정리해야 합니다. WhileActive는 액터가 GameplayCueNotify_Actor의 연관성 범위에 들어올 때마다 호출됩니다. OnRemove는 액터가 GameplayCueNotify_Actor의 연관성 범위를 벗어날 때마다 호출됩니다.

 

GameplayCue 안전성

일반적으로 GameplayCue는 신뢰할 수 없으므로 게임 플레이에 직접적인 영향을 미치는 모든 것에 적합하지 않은 것으로 간주해야 합니다.


실행된 GameplayCue: 이러한 GameplayCue는 신뢰할 수 없는 멀티캐스트를 통해 적용되며 항상 신뢰할 수 없습니다.


GameplayEffect에서 적용된 GameplayCue입니다:

  • Autonomous proxy는 OnActive, WhileActive를 안정적으로 수신하며, OnRemoveFActiveGameplayEffectsContainer::NetDeltaSerialize()는 UAbilitySystemComponent::HandleDeferredGameplayCues()를 호출하여 OnActive 및 WhileActive를 호출합니다. FActiveGameplayEffectsContainer::RemoveActiveGameplayEffectGrantedTagsAndModifiers()는 OnRemoved를 호출합니다.
  • Simulated proxy는 WhileActive 및 OnRemoveUAbilitySystemComponent::MinimalReplicationGameplayCues의 리플리케이션 호출인 WhileActive 및 OnRemove를 안정적으로 수신합니다. OnActive 이벤트는 신뢰할 수 없는 멀티캐스트에 의해 호출됩니다.

GameplayEffect 없이 적용된 GameplayCue입니다:

  • Autonomous proxy는 안정적이지 않은 멀티캐스트에 의해 호출되는 OnRemoveThe OnActive 및 WhileActive 이벤트를 안정적으로 수신합니다.
  • Simulated proxy는 WhileActive 및 OnRemoveUAbilitySystemComponent::MinimalReplicationGameplayCues의 리플리케이션 호출인 WhileActive 및 OnRemove를 안정적으로 수신합니다. OnActive 이벤트는 신뢰할 수 없는 멀티캐스트에 의해 호출됩니다.

GameplayCue에 '신뢰할 수 있는' 무언가가 필요한 경우, GameplayEffect에서 적용하고 WhileActive를 사용하여 FX를 추가하고 OnRemove를 사용하여 FX를 제거합니다.