본문 바로가기

카테고리 없음

[Unreal 5] GAS - Gameplay Ability

본 게시글은 GAS 구조를 공부하던 중 UGameplayAbility 의 주석을 필자가 아는만큼 해석해놓은 글이니 읽으실 때 참고 부탁드립니다.

(필자의 수준은 매우 낮다는 것을 고려)

 


 

 

직접 캐릭터의 스킬 등을 GameplayAbility 로 구현할 때에는 어떻게 해야 할지 감이 안잡혀서 일단 클래스 전체를 톺아보기로 했다.

 

 

우선은 긴 주석이 나오는데, 어빌리티는 Gameplay Logig 중에서 트리거되거나 활성화될 수 있는 것들이라고 한다.

AbilitySystem 이 GameplayAbility 에게 제공하는 주요 특징들은 아래와 같은데,

  • 우선 쿨타임을 적용할 수 있다고 한다. 실제로 어빌리티를 상속받아 BP를 만들어 보면, 아래처럼 Cooldown Gameplay Effect 를 적용할 수 있도록 하고 있다.

 

  • 어빌리티 사용에 대한 비용을 적용할 수 있다. (마나라던지, 스태미나라던지 등등) 이것도 역시 Cost Gameplay Effect 를 적용할 수 있도록 하고 있다.

  • 기타등등

 

또, Replication 도 내부적으로 지원해주고 있다고 한다.

Ability 활성화에 대한 클라이언트/서버 통신이라던지, 클라이언트의 예측도 같이 지원한다고 한다.

 

 

추가로 Prediction (예측)은, 클라이언트/서버 모델에서 발생할 수 있는 네트워크에 의한 각종 지연을 해결할 수 있는 방법이다.

 

예를 들어, Replication 을 위해서는 우선 서버쪽의 검증 이후 클라이언트에게 복사가 일어나게 되는데 이렇게 되면 클라이언트가 반응을 얻기까지 서버의 연산 시간 + 네트워크를 통해 클라이언트에 도달하는 시간이 필요하다.

만약 예측을 하게 된다면, 클라이언트에 우선 적용 후 Replication 을 적용함으로써 지연이 상당 부분 사라지게 된다.

 

언리얼에서는 MovementComponent 등 여러 빌트인 클래스들이 이러한 Replication 과 Prediction 을 제공한다.

문제 아닌 문제가 있다면 어떤 것들이 어디까지 지원하는지가 처음엔 명확하지 않아서, 의도하지 않은 동작들이 일어날 수 있다. 때문에 멀티플레이 기반 프로젝트를 진행 중이라면 반드시 Listen Server 모드와 Dedicated 모드를 번갈아 확인하면서 문제점들을 파악하고, 어떤 클래스가 어디까지 지원하는지를 알아 놓는 것이 좋다고 생각한다.

 

 

또, 게임플레이 어빌리티의 인스턴스 관련 기능들을 지원한다.

  • Non-Instanced : 어빌리티는 아예 인스턴스화 되지 않을 수 있다. 만약 월드에 아무런 객체가 필요 없는 어빌리티가 있다면, Static 함수와 비슷하게 인스턴스화 할 필요가 없을 수 있다. 인스턴스가 없으므로 당연히 부하도 제일 적다. 이 예시를 위해서 GameplayAbility_Montage 를 보라고 한다. 해당 Montage 가 재생되는 동안에 Gameplay Effect 를 타겟에 적용하고, Montage 재생이 종료되면 GE를 제거하는 식으로 사용할 수 있다고 한다. 또, 이 경우 딱히 특성 인스턴스에 대한 State 가 없기 때문에 Replicate 될 요소도 없다고 하며, RPC또한 적용이 불가능하다고 한다. 결론적으로, 당연한 말이지만 Replication 되기 위해서는 반드시 인스턴스가 존재해야 한다.

 

  • Intanced per Owner : 인스턴스가 단 하나 생성되고, 이후에 삭제되거나 초기화되지 않는다. 마치 UMG의 Collapsed 와 같은 기능이다. 해당 어빌리티를 계속해서 재사용하고자 할 때 사용할 수 있는데, 이렇게 되면 처음 만들어진 뒤 계속해서 해당 데이터를 들고 있게 된다. 때문에 초기화해야 하는 멤버가 있다면 알아서 초기화를 명시적으로 해줘야 한다.

 

  • Instanced per Execution : 실행 시 마다 인스턴스가 생성된다. (기본 설정) 자원을 제일 많이 사용하는 방법이지만, 제일 일반적인 방법이기도 하다. 예를 들어 파이어볼 어빌리티가 있는 경우, 한번 활성화할 때마다 파이어볼 어빌리티를 인스턴스화하고 종료 시 없애고를 반복하게 된다.

 

또, Input Binding 이나 어빌리티 부여와 같은 기능들도 지원할 수 있다고 한다.

 

 

그 다음으로는 어빌리티 종료, 취소, 어빌리티 상태 종료 등에 대한 델리게이트가 존재한다는 것을 알 수 있다.

 

 

또, 해당 어빌리티를 발동함으로써 내부적으로 어떤 Gameplay Tag를 어떤 트리거로 (GameplayAbilityTriggerSourceType) 활성화 시킬 것인지에 대한 구조체를 정의하고 있는데,

 

 

좀 더 자세히 보니 Type 에는 GameplayEvent 를 통해 트리거되는 타입과 어빌리티의 소유자가 특정 Tag를 얻으면 활성화 되는 타입, 그리고 동일하지만 Tag가 제거되면 없어지는 타입이 존재한다.

 


 

 

이제 Gameplay Ability 에 대한 정의를 보자.

 

우선 Gameplay Ability 는 GameplayTask 를 실행할 수 있기 때문에 GameplayTask 에 대한 인터페이스를 구현하고 있고, friend 클래스로 UAbilitySystemComponent 와 UGameplayAbilitySet, FScopedTargetListLock 을 사용하고 있다.

 

 

중요 함수들로는 아래와 같은 것들이 있다고 한다.

 

  • CanActivateAbility() : 해당 어빌리티가 활성화 가능한지 확인하는 용도라고 한다. UI에서도 호출할 수 있다고 함. 예를 들어 UI에서 쿨타임이나, 어빌리티 활성화 상태를 보는 용도로도 사용할 수 있을 것 같다.

 

  • TryActivateAbility() : 어빌리티에는 Cost, Cooldown 을 비롯해 여러 활성화 조건들이 있다. 가령, GameplayTag 가 Blocking 하고 있다던지, 아니면 활성화에 필요한 Tag 가 있다던지 없다던지 하는 것들이 그것이다. 그래서 Ability 를 바로 실행할 수 있다고 장담할 수 없다. 때문에 "TryActivateAbility" 인 점을 명심하자. 이러한 확인을 위해 CanActivateAbility()를 호출한다고 하며, Input 이벤트는 이걸 바로 호출한다고 함. 또 Instancing-Per-Execution 로직과 Replication, Prediction 호출을 핸들링한다고 한다. 아무튼 어빌리티 활성화를 시도하는 함수.

 

  • CallActivateAbility() : ActivateAbility() 를 호출하기 위해 몇몇 선행 작업들을 하는 함수라고 한다. (가상함수 아님)

 

  • ActivateAbility() : 어빌리티가 무엇을 하는지 정의하는 함수. 스킬의 몸체같은 것이다. 당연히 상속받는 클래스에서 Override 해야 한다. 결국 모든 함수들은 이것을 위해 존재한다.

 

  • CommitAbility() : ActivateAbility() 는 반드시 이 함수를 호출해야 한다. 자원이나 쿨타임 등을 커밋한다고 한다. 아마 어빌리티 사용을 위해 자원을 소모하는 함수가 아닐까 생각한다.

 

  • CancelAbility() : 어빌리티 실행을 중단한다. (외부 요인에 의해 중단되는 경우)

 

  • EndAbility() : 어빌리티가 종료되면 스스로 종료하기 위해 호출하도록 되어 있다고 한다.

 

그냥 전체적으로 어빌리티의 호출이 어떤 식으로 돌아가는지 알 수 있는 함수들인것 같다. 굿.

 

그 다음으로는 그냥 쭉 Accessor 들이 정의되어 있다.

 

이건 그냥 보면 아는것들이니 훑어보고 스킵한다.

 

 

아까 보았던 델리게이트들이 선언되어 있다.

FGenericAbilityDelegate 이건 처음보는건데, 서버에서 어빌리티가 컨펌되면 이 델리게이트 콜백이 작동한다고 한다.

기억해 놓자.

 

 

그 다음은 어빌리티 활성화에 관한 부분이다.

 

함수 시그니처를 보면 주로 FGameplayAbilitySpecHandle (GA Spec 핸들. GA 역시 Spec 을 생성하여 인스턴스화 하고 적용한다.), FGameplayAbilityActorInfo (어빌리티를 사용하는 액터에 대해 캐싱된 데이터의 모음), FGameplayTagContainer (게임플레이 태그 모음) 을 사용하여 어빌리티를 사용하고 관리하는 것 같다.

 

크게 어려운 부분은 없으니 함수 이름들만 한번씩 읽어 보았다.

 

 

그 다음은 어빌리티의 취소.

 

 

그 다음은 어빌리티 커밋에 대한 부분인데 여기가 의외로 좀 길었다.

 

자세히 살펴보니 주로 어빌리티의 쿨타임, 코스트를 커밋하고 적용하는 부분들이 많았다.

커밋을 하는 부분들이 주로 어빌리티 발동을 체크할 마지막 타이밍인 것 같다.

이 외에도 쿨타임을 체크하거나 (쿨타임이 지나, 활성화가 가능한지?), 코스트를 체크하는 (정해진 코스트만큼 소비하기에 Attribute 등이 충분한지?) 등의 함수도 존재하니, 참고해서 사용하면 될 것 같다.

 

 

또, Input 과 관련된 부분, Animation (특히 Montage) 와 관련된 부분이 존재한다.

MovementSync 이건 뭔지 아직 잘 모르겠는데, 뭐.. SyncName 을 받는 것 보니 어빌리티와 움직임 싱크를 맞추는 용도인가?

 


 

 

그리고 어빌리티의 레벨, Source Object와 관련된 부분들도 존재한다.

GE와 마찬가지로 어빌리티에도 레벨이 있다. 가령 스킬 레벨이 1인 경우에 적용되는 대미지 공식과 다른 레벨에 적용되는 대미지 공식이 다를 것이고 이것도 마찬가지로 Curve Table 등으로 정의가 가능하다. 이런 부분들의 적용을 위해 어빌리티 레벨을 얻어오거나 Source Object 를 받아올 수 있는 것 같다.

 

 

또, AbilitySystemComponent 와 상호작용하여 어빌리티를 부여했거나 제거한 콜백들도 오버라이딩해서 구현할 수 있고, Avatar 에 대한 부분도 존재한다.

 

 

해당 어빌리티에 어떤 태그가 있는지 AbilityTags 로 확인할 수 있고,

Gameplay Ability 에 존재하는 설정 (bReplicateInputDirectly) 도 사용할 수 있는데,

bReplicateInputDirectly 는 발생하는 모든 Input Pressed/Release 를 서버에 Replicate 하는 옵션이다.

근데 GA는 기본적으로 Replication 이 내장되어있는 관계로 Epic Games 도 굳이 이걸 쓰지 말라고 한다. 걍 없다고 생각하자.

 

Gameplay Task Owner Interface 에 대한 부분도 존재한다.

Task 에 대한 콜백 (Initialized, Activated, Deactivated) 들과 GameplayTaskComponent 를 받아올 수 있다.

 


 

 

또, Gameplay Effect 를 타겟이나 자기 자신에게 적용하고, 제거하는 함수도 있다.

뿐만 아니라 GameplayCue 등 GAS에서 기본적으로 사용하는 시스템에 대한 제반 사항들도 갖추고 있다고 보면 된다.

당연하게도 GAS는 GE를 기반으로 돌아가기 때문에..

 

 

제일 중요한 부분은 역시 Activate Ability 일 것 같다.

  • Child Class 들은 아마 이 Activate Ability 를 Override 할 것이며
  • 반드시 Commit Ability 를 호출해야 한다.
  • 반드시 EndAbility 를 호출해야 한다.

지연이나, 비동기 액션들도 괜찮다고 한다.

다만, Commit Ability 와 End Ability 에 대한 호출 없이 바로 ActivateAbility 가 종료될 수 있는데, 이는 지연/비동기 행동들이 Pending 인 상태에서만 그렇다고 한다. 또, ActivateAbility 가 종료되는 경우 Commit/End Ability 가 호출된 것으로 간주한다.

 

근데 자꾸 여기 막 K2 라고 써있는데, 아마 이런 함수들을 지금까지 몇번 봐왔을 것이라고 생각된다.

K2는 한마디로 그냥 레거시 함수인데, 주로 블루프린트에 노출되는 함수들에 K2 Prefix 가 붙는다.

 

? Kismet 이라고 붙던데?

그렇다. K2가 바로 Kismet Version 2 를 의미한다고 한다.

매크로에 아래처럼

 

DisplayName 이 지정되어 있기 때문에 K2라고 안보이는것 뿐이다.

또, K2 제외하면 이름이 똑같은 ActivateAbility 함수가 Native 라고 생각하면 된다.

 

ActivateAbility 는 직접 호출하는 함수는 아니지만, 결국 어빌리티를 정의한다.

(실제로 직접 호출하지 말라고 되어 있다. 이미 Boilerplate 들이 정해져 있기 때문에 괜히 헛짓하지 말고 정해진 절차대로 하자!)

호출은 보통 TryActivateAbility 를 통해 하기 때문에 우리는 Ability 에서 ActivateAbility 를 오버라이드하고 정해진 흐름에 따라 호출하면 되는 것이다.!

 

 

 

아까 Animation 의 심화판으로, Montage Section 을 뛰어넘거나, 다음 섹션을 설정하거나 정지시키는 등 어빌리티 사용 시의 Montage 와 관련된 여러 함수들도 있다.

 

 


 

 

대충 다 살펴봤는데, 역시 Gameplay Ability 를 적용하고, 활성화하고, 중지하거나 사용 가능한지 검사하는 등의 기본적인 함수들부터 애니메이션 관련, Gameplay Cue, Gameplay Effect 및 여러 상황에 대한 콜백 델리게이트까지, 예상했던 부분들이 대부분이다.

 

글을 읽으면서 눈치챘는지 모르겠지만, 이 빌트인 함수들에는 Instance 되지 않은 상태에서 호출할 수 있는 함수가 있고, 그렇지 않은 함수가 있다. 이런 부분들을 생각하면서 실제로 구현할 때 주석과 시그니처들을 잘 볼수 있도록 하자.

 

이를 참고해서 Gameplay Ability 를 구현하는데 도움이 많이 될 것 같다.