언리얼 엔진에는 GAS라는 기능이 있다고 한다.

게임 내 캐릭터의 스탯, 쿨타임 등의 능력에 관한 기능을 효율적으로 구축할 수 있도록 도와주는 기능이라고 한다.

에픽게임즈에서 개발한 포트나이트에도 사용되고 있고, 이전에 개발했던 파라곤에서도 사용되었었다고 한다.

 

공식 문서에는 위와 같이 설명되어 있다.

 

이 GAS가 정확히 어떤 기능인지, 어떻게 사용하는지, 어디에 활용하는지 등에 대한 궁금증이 생겼고 이 기능을 파헤쳐보고자하는 마음이 생겼다. 그런 이유로 GAS를 분석해보고자 한다.

 

분석에 있어서 아래 깃허브와 공식 문서를 참조하였다.

(해당 깃허브는 에픽게임즈가 제공해주는 공식 예제가 아니라, 감사하게도 제 3자가 기능 설명을 위해 제공해준 예제 프로젝트이다.)

 

tranek/GASDocumentation: My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer sample project. (github.com)

 

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

 

 

먼저, 이 기능을 사용하기 위해 몇가지 프로젝트 세팅을 해야한다.

 

프로젝트 세팅


1. 에디터에서 아래의 플러그인을 활성화 해주어야 한다.

 

2. 프로젝트의 build.cs에 있는 PrivateDependencyModuleNames에 해당 모듈을 추가해주어야 한다.

 

3. 게임이 실행되기 전 UAbilitySystemGlobals::Get().InitGlobalData() 함수를 반드시 호출해주어야 한다.

언리얼엔진 5.3 이후부터는 해당 함수를 자동으로 호출해준다고 한다.

하지만, 4.24 ~ 5.2의 버전에서는 이 함수를 자동으로 호출해주지 않기 때문에 직접 호출해주어야 한다고 한다.

 

이 함수는 GAS를 초기화 하는 함수이며, GAS가 직접적으로 사용되기 전에 호출해주어야 한다고 한다.

위의 깃허브 소유자는 UAssetManager의 StartInitialLoading() 에서 해당 함수를 호출해주고 있다.

 

 

 

GAS


일단, 게시글을 일겅보니 

ASC(AbilitySystemComponent)와 Attribute, AttributeSet이라는 용어가 보였다.

 

ASC는 UActorComponent를 상속받은 컴포넌트 클래스이며, GAS를 사용하고 싶은 Actor는 반드시 이 컴포넌트를 소유해야 한다고 한다. 플레이어가 죽고 되살아나는 과정에서 Attribute의 지속성이 필요한 경우에는 ASC는 직접 소유하기보다는 PlayerState가 소유하는 것이 더 적합하다고 한다.

중간에 위와 같은 글이 있다.

 

ASC가 PlayerState에 위치한다면, NetUpdateFrequency를 증가시켜야 한다는 것 같다. 기본값이 너무 낮게 설정되어 있어서 Attribute나 GameplayTags의 업데이트가 지연될 수 있다고 한다. 또한, Adaptive NetworkUpdate Frequency 를 활성화 하라고 하는데, 찾아보니 값이 변하지 않았는데도 업데이트 하는 경우를 방지하여 CPU의 사이클을 효과적으로 운영하는 방식이라고 한다.

 

Attribute란 해당 플레이어가 갖는 속성을 의미한다고 한다. HP, MP, Level 등의 스텟정보를 의미하는 것 같다. 위의 깃허브 프로젝트의 내부 코드를 보니 Gold와 같은 부분에도 Attribute를 사용하고 있었다. 아마 수치화 될 수 있는 모든 부분에서 사용이 가능한 듯 하다. ( 그냥 예측이긴 하지만 커뮤니티에 등록된 친구의 수, 길드원의 수와 같은 것들도 Attribute로 표현할 수 있을 것 같다. )

 

AttributeSet이란, 저런 속성들의 집합이다. Hp, Mp, Level 등의 속성을 한 곳에 모아서 관리하는 클래스가 AtrributeSet인 것 같다.

 

만약, ASC의 OwnerActor가 AvaterActor (Pawn, Character)와 다르다면 (Player State가 소유하고 있다면)  IAbilitySystemInterface 인터페이스를 두 곳에서 모두 상속받아야 한다고 한다.

 

이 인터페이스에는 UAbilitySystemComponent* GetAbilitySystemComponent() const 라는 순수가상함수가 선언되어 있는데, 이를 정의해야만 OwnerActor와 AvaterACtor사이에서 상호작용 할 수 있다고 한다.

만약 AvaterActor가 ASC를 소유하고 있다면, 상속받을 필요가 없는 것 같다.

 

 

예제 프로젝트의 코드를 보면 PlayerState클래스에서 ASC와 AttributeSet을 모두 생성해주고 있다.

ASC의 SetReplicationMode를 보면 3가지 종류가 있었다.

 

Full은 GamePlayEffect를 모든 클라이언트에게 리플리케이트 한다.

Mixed는 gamePlayEffect를 오직 소유한 클라이언트에게만 리플리케이트 하고 GamePlayTag과 gamePlayCues는 모두에게 리플리케이트 한다.

Minimal은 GamePlayEffect를 아무한테도 리플리케이트 하지 않고, GamePlayTags와 gamePlayCues를 모두에게 리플리케이트 한다.

 

네트워크에 관한 옵션인데, 모드에 따라 정확히 어떻게 달라지는 건지 잘 모르겠다. GamePlayEffect, GamePlayTags, GamePlayCue에 대해서 좀 알아야지 정확히 어떤 기능인지 이해할 수 있을 것 같다.

 

아래는 예제 프로젝트에서 AttributeSet 상속받아 만든 클래스이다.

UAttributeSet을 보니, Attribute들을 관리하는 여러가지 함수들이 있다.

우리는 여기에 Hp, Mp, Exp등의 Attribute를 추가해야 하기 때문에, 이를 상속받아서 사용해야 한다.

 

내부를 보면 아래와 같이 멤버변수들이 선언되어 있다.

 

Health는 현재 체력이고 MaxHealth는 최대 체력인 듯 하다.

HealthRegenRate는 시간에 따라 Health가 회복되는 수치인 것 같다.

 

보면, UPROPERTY에 여러가지 인자들이 포함되어있다.

BluePrintReadOnly는 블루프린트에서 해당 변수의 데이터를 읽을 수는 있으나 수정할 수는 없다는 뜻이다.

 

Category는 에디터에서 해당 변수를 분류할 그룹의 이름을 지정하는 것이다.

 

ReplicatedUsing은 이 변수를 네트워크를 통해 리플리케이트 함과 동시에, 해당 변수의 데이터가 수정될 때 호출될 함수를 콜백 방식으로 등록하는 것 같다.

 

Replicated와 ReplicatedUsing 두 가지가 있는데, Replicated는 콜백함수없이 리플리케이트만 하는 것이고, ReplicatedUsing은 리플리케이트함과 동시에 호출될 콜백함수를 지정하는 것이다.

 

해당 함수들의 정의를 보면 아래와 같다.

GAMEPLAYATTRIBUTE_REPNOTIFY를 호출해주고 있다.

변경된 값을 네트워크를 통해 업데이트해주는 매크로라고 한다.

 

매크로 내부를 보니, Attribute의 값이 변경될 때 델리게이트에 저장된 함수를 호출해주고 있는 것을 보았다. 

이런 식으로,  델리게이트에 바인딩되어있는 함수가 있다면 어트리뷰트의 값이 변경될 때 그 함수를 호출하도록 되어있다.

예제 프로젝트에서는 그 함수를 어디서 바인딩 하나 찾아봤더니, PlayerState에서 해주고 있었다.

 

아래는 PlayerState에서 함수를 바인딩해주는 코드이다.

너무 길어서 캡쳐하지 않고, 코드블록을 사용하였다.

// Attribute change callbacks
HealthChangedDelegateHandle = 
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSetBase->GetHealthAttribute()).AddUObject(this, &AGDPlayerState::HealthChanged);

MaxHealthChangedDelegateHandle = 
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSetBase->GetMaxHealthAttribute()).AddUObject(this, &AGDPlayerState::MaxHealthChanged);

HealthRegenRateChangedDelegateHandle = 
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSetBase->GetHealthRegenRateAttribute()).AddUObject(this, &AGDPlayerState::HealthRegenRateChanged);

 

각 Attribute에 바인딩된 함수 내부를 보니, HP가 0이하라면 사망 처리를 하거나, UI WIdget을 업데이트 하는 등의 기능을 하고있었다. 

 

즉, 변수의 값이 업데이트 되면, UPROPERTY에서 설정해둔 콜백함수인 OnRep_(속성) 을 호출하게 되고 OnRep_(속성) 함수 내부에선 GAMEPLAYATTRIBUTE_REPNOTIFY 매크로를 호출하여 네트워크를 통해 업데이트된 값을 리플리케이트 해주게 된다. 리플리케이트 함과 동시에, 각 Attribute의 델리게이트에 바인딩된 함수가 있다면 해당 함수를 호출해주고 있는데,  UI를 업데이트 하거나 플레이어의 사망처리를 하거나 특정 이펙트를 띄우는 등 원하는 함수를 바인딩하면 자동으로 호출되는 것이다.

 

또한, AttributeSet에서는 위의 함수도 오버라이딩하여 해당 매크로를 실행해주어야 한다고 한다.

 

해당 매크로에 대해 찾아보았는데, GAS에서는 Attribute를 예측을 통해 미리 업데이트하는 것 같다

그래서, 서버를 통해 Attribute가 변경되었다는 알림을 받았는데 클라이언트에서는 이미 변경된 Attribute의 값이 저장되어있을 수 있다고 한다. 

 

그래서 클라이언트에 저장된 Attribute의 값이 변경되지 않을 수 있는데, 이 때도 NOTIFY를 실행할 것인가 말 것인가를 설정하는 것이라고 한다. 

실제로 값이 변경될 때만 NOTIFY를 실행할 것인가, 아니면 값이 변경되었다는 신호가 올 때마다 NOTIFY를 실행할 것인가 를 결정하는 것이다.

멤버변수의 선언을 볼 때, 보면 ATTRIBUTE_ACCESSORS라는 매크로도 있다.

보니까 이 매크로를 등록하면 해당 Attribute에 대한 여러가지 함수가 생기는 것 같다.

 

실험을 해보니, 해당 매크로가 작성되어 있을 땐, GetHealth() SetHealth() InitHealth() 등의 Attribute를 관리하는 함수가 정상적으로 호출되었지만, 해당 매크로에 주석을 쳐보니 인텔리센스에서 해당 함수를 감지하지 못하는 상황이 발생하였다.

 

GAS에 의해 관리될 Attribute라면 해당 매크로를 반드시 작성해주어야 하는 듯 하다.

 

GAS의 기초가 되는 Attribute, AttributeSet이 무엇인지 이해하기 위해 이것저것 둘러보았는데, 사실 아직도 정확히 어떤 구조로 돌아가는지는 잘 이해가 안된다. 기본적으로 네트워크를 고려하여 설계된 프레임워크인듯 한데, 본인이 언리얼엔진의 네트워크 구조 자체에 이해가 아직 부족하기 때문에 더욱 이해가 힘든 것도 있는 것 같다. 꾸준히 네트워크에 대한 공부를 하면서 분석해야 GAS를 더 완벽히 이해할 수 있을 것 같다.

 

지금 본인의 지식 상태에선 하나하나 이해하려고 하기보다는 전체적인 구조를 파악해보면서 GAS의 흐름을 먼저 파악해야 할 것 같다.

+ Recent posts