지난 게시글에선 예제 프로젝트의 코드를 보면서 Attribute가 무엇인지 간단하게 알아보았다. 다른 기능에 대한 설명에서도 계속 보이는 Attribute라는 것을 먼저 알아야 다른 것들을 이해할 수 있을 것 같아서 Attribute를 먼저 알아보았다.
이번엔 먼저 GamePlayTag에 대해 알아보자.
GamePlayTag는 해당 객체의 상태를 나타내는 태그인 것 같다.
예를 들어, 플레이어가 기절상태에 걸렸다고 해보자.
일반적으로 enum으로 state를 변경하거나 bool값으로 스턴 여부를 파악하게 된다.
반면, GamePlayTag에선 문자열을 이용해서 이런 상태이상 여부를 파악한다고 한다.
공식 문서에는 위와 같이 설명되어 있다. 사실 저 글만 보면 무슨 기능인지 당최 이해할 수가 없다.
위 글은 예제 프로젝트의 깃허브에 적힌 설명이다. 아래는 번역해본 내용이다.
.
FGamePlayTags는 부모.자식.손자의 형태로 구성된 계층적인 이름이다. 그것은 GameplayTagManager에 의해 등록이 된다. 이 태그들은 오브젝트의 상태를 묘사하거나 분류하는 것에 있어 매우 유용하다. 예를 들어, 캐릭터가 기절 상태에 돌입하였다면 우리는 State.Debuff.Stun 이라는 GameplayTag를 기절시간동안 부여할 수 있다.
당신은 GameplayTag를 사용하여 enums나 booleans를 대체하고, 오브젝트가 특정 GameplayTag를 가지고 있는지 없는지를 boolean으로 체크하고 있는 모습을 발견할 수 있을 것이다.
위 코드는 예제 프로젝트에서 플레이어, 적 유닛 등 게임 내의 캐릭터들이 공통으로 상속받을 AGDCharacterBase클래스의 생성자 코드의 일부이다.
FGameplayTag라는 구조체를 사용하여 Tag를 반환받고 있다. RequestGameplayTag의 인수를 보면 A.B.C의 형태로 3개의 단어가 연결된 문자열을 사용하고 있다.
가장 앞의 단어는 가장 포괄적인 상태를 의미하고, 두 번 째 단어에서 상태를 조금 더 구체화하고, 세 번째 단어에서 완전히 구체화하여 상태를 구분하도록 하는 것 같다.
예를 들면, State.Debuff.stun 이렇게 사용하여 (상태. 디버프. 기절) 이렇게 표현할 수도 있고
State.Buff. Invincible 이렇게 사용하여 (상태. 버프. 피해면역) 이렇게 표현할 수도 있다.
그런데 위 코드에서 보면 State.Dead 이렇게 두개의 단어만 사용하기도 하는 걸 보니, 꼭 3개의 단어로 이루어져야 하는 건 아닌 것 같다. 아마 4개 이상의 단어도 될 것 같다. ( 나중에 실험해봐야지 )
저 GameplayTag를 표현하는 단어집합은 에디터에서 추가해줄 수 있다.
1. 에디터의 프로젝트 세팅에서 추가
에디터의 프로젝트 세팅을 들어가면 Project -> GameplayTag 카테고리가 있다.
내부를 보면 Add new gameplay Tag source도 있고 manage Gameplay Tags도 있다.
먼저, Add new gameplay Tag source 를 눌러보자.
위와 같은 창이 뜨는데, Name 에 GameplayTag들을 저장할 ini파일의 이름을 설정해주면 된다.
이후, Manage Gameplay Tags를 눌러보자.
이런 창이 뜬다.
왼쪽 상단의 초록색 십자표시 눌러보자.
이렇게 창이 확장된다.
source를 눌러보면 아까 만든 ini 파일의 이름이 보일 것이다.
이제 source를 저 ini파일로 바꾸고 Name칸에 원하는 GameplayTag를 입력해주면 된다.
이렇게 입력해보겠다.
아래 창에 위 사진과 같이 Tag가 상속관계로 생성이 된다.
A.B.C.D를 입력했더니 이렇게 생성되었다.
4개 이상의 상속관계로 이루어진 Tag도 만들 수 있는 것 같다.
이제 GameplayTag가 사용되고 있는 코드들을 찾아서 사용법을 간단하게 익혀보자.
아래 코드는 플레이어의 HP가 변화할 때, 플레이어가 죽었는지 체크하는 코드이다.
위에서 보았던 생성자 코드 내부에서는 DeadTag 변수를 아래와 같이 초기화해주었다.
DeadTag = FGameplayTag::RequestGameplayTag(FName("State.Dead"));
아래 코드를 보면, 이 DeadTag를 ASC의 HasmatchingGameplayTag의 인수로 사용하여 플레이어의 현재 GameplayTag가 State.Dead를 포함하고 있는 체크하고 있다.
언리얼 엔진의 공식문서를 보면 이처럼 플레이어의 현재 Tag를 확인하는 함수를 여러가지고 제공해주고 있다.
가장 상위의 Tag만 일치하는지, 모든 Tag가 일치하는지, 해당 Tag가 존재하는지 등의 방식으로 Tag를 확인할 수 있는 것이다.
보니까 플레이어는 하나의 GameplayTag만 보유할 수 있는 것이 아니라, 여러가지를 보유할 수 있는 듯 하다.
당연하겠지만, 동시에 여러개의 버프가 적용되어 있을 수도 있고, 상태이상도 여러개가 적용되어 있을 수 있기 때문이다.
아래 코드는 GameplayTag의 변화를 감지하여 Stun이 적용되거나 해제될 때, 특정 함수를 실행하도록 하는 예제 코드이다.
AbilitySystemComponent->
RegisterGameplayTagEvent(
FGameplayTag::RequestGameplayTag(FName("State.Debuff.Stun")), EGameplayTagEventType::NewOrRemoved
).AddUObject(this, &AGDPlayerState::StunTagChanged);
보면, ASC에서 RegisterGameplayTagEvent 함수를 호출하고 있다.
RegisterGameplayTagEvent 함수의 인수로 GameplayTag와 EGameplayTagEventType을 넣어주면 델리게이트를 반환해준다. 해당 델리게이트에 AGDPlayerState::StunTagChanged 함수를 bind해주고 있다.
EGameplayTagEventType은 어떤 경우에 바인딩된 콜백함수를 호출할 것인지를 지정하는 이넘타입이다.
위의 코드에선 EGameplayTagEventType::NewOrRemoved 를 사용하여 State.Debuff.Stun에 해당하는 GameplayTag가 적용되거나 해제될 때에만 델리게이트에 바인딩된 함수를 호출해주도록 해주었다.
EGameplayTagEventType에는 EGameplayTagEventType::AnyCountChange 라는 녀석도 있다.
이 녀석은 검색을 해봐도 정보가 잘 나오지 않아서 내가 찾은 정보가 정확한지는 모르겠지만 일단 찾아본 정보를 얘기해보자면 아래와 같다.
만약 플레이어가 A.B, A.C 이렇게 두개의 GameplayTag를 가지고 있다면, 현재 A에 대한 TagCount는 2가 된다.
A.B에 대해선 TagCount가 1이고, A.C에 대해서도 TagCount가 1이다.
여기서 만약 A.D라는 태그가 추가되어 A.B, A.C, A.D 이렇게 3개의 태그를 가지게 되면 A의 TagCount가 3이 된다.
만약 위에서 RegisterGameplayTagEvent의 첫 번째 인수로 A에 대한 GameplayTag를 전달하였다면 A.D가 추가될 때 TagCount가 변경되는 것이기 때문에 이 때 델리게이트에 바인딩된 함수가 호출된다
뭔가 납득이 되는 것 같은 설명이긴 한데, 이 부분에 대해서는 조금 더 정보를 찾아서 교차검증을 해보아야 할 것 같다.
직접 실험해보고 싶지만, 아직 GAS에 대해 뭐 아는게 없어서 실험하기도 애매하다..
아래는 델리게이트에 바인딩 된 StunTagChanged함수의 내부이다.
void AGDPlayerState::StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
if (NewCount > 0)
{
FGameplayTagContainer AbilityTagsToCancel;
AbilityTagsToCancel.AddTag(FGameplayTag::RequestGameplayTag(FName("Ability")));
FGameplayTagContainer AbilityTagsToIgnore;
AbilityTagsToIgnore.AddTag(FGameplayTag::RequestGameplayTag(FName("Ability.NotCanceledByStun")));
AbilitySystemComponent->CancelAbilities(&AbilityTagsToCancel, &AbilityTagsToIgnore);
}
}
보면 인자로 FGameplayTag와 int32를 받고 있다.
이는 델리게이트와 바인딩하기 위해서 반드시 선언해주어야 하는 파라미터이다.
아마 저 int32가 위에서 말했던 TagCount가 아닐까 싶다. 이 함수는 State.Debuff.Stun이 생성되거나 소멸될 때 호출되는 함수이다. 해당 GameplayTag가 소멸되는 순간이라면 TagCount가 0이되기 때문에 파라미터의 NewCount를 활용해서 태그가 생성될 때와 소멸될 때 분기를 나누어 설계하면 되는 것 같다.
내부 함수를 보면,Stun 태그가 생성될 때 Ability 태그를 Cancel하고 있다.
Ability태그는 안에 점프, 조준이 포함되어 있다. 기절 상태가 되면 해당 동작을 중지해야 하기 때문에 해당 Tag를 Cancel하는 것 같다.
코드를 보면 FGameplayTagContainer라는 구조체가 있다. 저 녀석은 여러개의 태그를 저장하여 여러가지 편의기능을 제공해주는 놈인 것 같다.
위의 코드에선 CancelAbilities함수가 FGameplayTagContainer를 인자로 받기 때문에 사용한 것 뿐이지만 실제로는 더 유용한 쓸모가 있을 듯 하다.
(아직은 모르겠다.)
일단 여기까지 해서 GameplayTag에 대해 간단하게 알아보았다.
뭔가 하나하나씩 파헤쳐보니 대충 감은 잡히는 것 같은데, 이걸 게임에 적용해보라면 나한테 Stun 태그가 생성된것마냥 멍해질 것 같다. GAS에 대해 어느정도 이론적 지식을 쌓으면 직접 적용해보면서 지식을 체화해야 할 것 같다
'언리얼 엔진 > GAS' 카테고리의 다른 글
언리얼 엔진 - GAS ( Game Ability System ) [ 5 ] (0) | 2024.04.21 |
---|---|
언리얼 엔진 - GAS ( Game Ability System ) [ 4 ] (0) | 2024.04.17 |
언리얼 엔진 - GAS ( Game Ability System ) [ 3 ] (0) | 2024.04.16 |
언리얼 엔진 - GAS ( Game Ability System ) [ 1 ] (0) | 2024.04.14 |