UE5 C++ Gameplay Tags
개요 — Gameplay Tags란
섹션 제목: “개요 — Gameplay Tags란”Gameplay Tags는 UE5에서 제공하는 계층적 이름 기반 레이블 시스템입니다. FName 비교보다 안전하고, bool 플래그 배열보다 확장성이 뛰어납니다.
Character.State.AliveCharacter.State.DeadCharacter.Status.StunnedAbility.Combat.Melee.SlashAbility.Combat.Ranged.Arrow| 비교 항목 | bool 플래그 | FName | Gameplay Tags |
|---|---|---|---|
| 계층 쿼리 | 불가 | 불가 | MatchesTag |
| 에디터 드롭다운 | 없음 | 없음 | 자동 생성 |
| 런타임 추가 | 가능 | 가능 | 제한적 |
| GAS 연동 | 없음 | 없음 | 네이티브 지원 |
1. 태그 등록
섹션 제목: “1. 태그 등록”1.1 ini 파일 등록 (권장)
섹션 제목: “1.1 ini 파일 등록 (권장)”Config/DefaultGameplayTags.ini:
[/Script/GameplayTags.GameplayTagsSettings]+GameplayTagList=(Tag="Character.State.Alive",DevComment="캐릭터 생존 상태")+GameplayTagList=(Tag="Character.State.Dead",DevComment="캐릭터 사망 상태")+GameplayTagList=(Tag="Character.Status.Stunned",DevComment="기절 상태")+GameplayTagList=(Tag="Character.Status.Silenced",DevComment="침묵 상태")+GameplayTagList=(Tag="Ability.Combat.Melee",DevComment="근접 공격 카테고리")+GameplayTagList=(Tag="Ability.Combat.Melee.Slash",DevComment="베기 공격")+GameplayTagList=(Tag="Ability.Combat.Ranged.Arrow",DevComment="화살 공격")+GameplayTagList=(Tag="Effect.Damage.Fire",DevComment="화염 피해")+GameplayTagList=(Tag="Effect.Damage.Ice",DevComment="냉기 피해")1.2 C++에서 네이티브 태그 정의 (UE5.1+)
섹션 제목: “1.2 C++에서 네이티브 태그 정의 (UE5.1+)”// GameplayTags.h — 프로젝트 전역 태그 상수#pragma once
#include "NativeGameplayTags.h"
namespace GameplayTags{ // 상태 태그 UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Character_State_Alive) UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Character_State_Dead) UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Character_Status_Stunned)
// 어빌리티 태그 UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Ability_Combat_Melee) UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Ability_Combat_Melee_Slash) UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Effect_Damage_Fire)}#include "GameplayTags.h"
namespace GameplayTags{ UE_DEFINE_GAMEPLAY_TAG(TAG_Character_State_Alive, "Character.State.Alive") UE_DEFINE_GAMEPLAY_TAG(TAG_Character_State_Dead, "Character.State.Dead") UE_DEFINE_GAMEPLAY_TAG(TAG_Character_Status_Stunned, "Character.Status.Stunned") UE_DEFINE_GAMEPLAY_TAG(TAG_Ability_Combat_Melee, "Ability.Combat.Melee") UE_DEFINE_GAMEPLAY_TAG(TAG_Ability_Combat_Melee_Slash, "Ability.Combat.Melee.Slash") UE_DEFINE_GAMEPLAY_TAG(TAG_Effect_Damage_Fire, "Effect.Damage.Fire")}2. FGameplayTag — 단일 태그
섹션 제목: “2. FGameplayTag — 단일 태그”#include "GameplayTagsManager.h"#include "GameplayTags.h"
// 태그 조회 (문자열로)FGameplayTag StunnedTag = FGameplayTag::RequestGameplayTag(TEXT("Character.Status.Stunned"));
// 네이티브 태그 사용 (컴파일 타임 안전)FGameplayTag MeleeTag = GameplayTags::TAG_Ability_Combat_Melee;
// 유효성 확인if (StunnedTag.IsValid()){ UE_LOG(LogTemp, Log, TEXT("Tag: %s"), *StunnedTag.ToString());}
// 계층 비교FGameplayTag SlashTag = GameplayTags::TAG_Ability_Combat_Melee_Slash;
// MatchesTag: 자신이 다른 태그의 자식이면 true// "Ability.Combat.Melee.Slash".MatchesTag("Ability.Combat.Melee") = truebool bIsSubset = SlashTag.MatchesTag(MeleeTag); // true
// MatchesTagExact: 정확히 같은 태그일 때만 truebool bExact = SlashTag.MatchesTagExact(MeleeTag); // false3. FGameplayTagContainer — 태그 집합
섹션 제목: “3. FGameplayTagContainer — 태그 집합”FGameplayTagContainer StatusContainer;
// 태그 추가StatusContainer.AddTag(GameplayTags::TAG_Character_State_Alive);StatusContainer.AddTag(GameplayTags::TAG_Character_Status_Stunned);
// 포함 확인bool bIsAlive = StatusContainer.HasTag(GameplayTags::TAG_Character_State_Alive);
// 계층 포함: "Character.Status.Stunned"를 가지면 "Character.Status"도 HasTag 반환FGameplayTag CharStatusParent = FGameplayTag::RequestGameplayTag(TEXT("Character.Status"));bool bHasAnyStatus = StatusContainer.HasTag(CharStatusParent); // true
// 정확한 태그만 확인bool bExactMatch = StatusContainer.HasTagExact(CharStatusParent); // false
// 컨테이너 간 비교FGameplayTagContainer RequiredTags;RequiredTags.AddTag(GameplayTags::TAG_Character_State_Alive);
bool bHasAll = StatusContainer.HasAll(RequiredTags); // 전부 포함bool bHasAny = StatusContainer.HasAny(RequiredTags); // 하나라도 포함
// 태그 제거StatusContainer.RemoveTag(GameplayTags::TAG_Character_Status_Stunned);
// 순회for (const FGameplayTag& Tag : StatusContainer){ UE_LOG(LogTemp, Log, TEXT("Status: %s"), *Tag.ToString());}4. UPROPERTY 선언
섹션 제목: “4. UPROPERTY 선언”UCLASS()class MYGAME_API AMyCharacter : public ACharacter{ GENERATED_BODY()
public: // 단일 태그 (에디터 드롭다운 제공) UPROPERTY(EditDefaultsOnly, Category = "Tags") FGameplayTag CharacterRoleTag;
// 태그 컨테이너 UPROPERTY(EditDefaultsOnly, Category = "Tags") FGameplayTagContainer DefaultAbilityTags;
// 런타임 상태 태그 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Tags") FGameplayTagContainer ActiveStatusTags;
// 태그 쿼리 (에디터에서 복잡한 조건 설정) UPROPERTY(EditDefaultsOnly, Category = "Tags") FGameplayTagQuery AbilityActivationQuery;};5. FGameplayTagQuery — 복잡한 조건 쿼리
섹션 제목: “5. FGameplayTagQuery — 복잡한 조건 쿼리”// C++에서 쿼리 빌드FGameplayTagQuery Query = FGameplayTagQuery::BuildQuery( FGameplayTagQueryExpression() .AnyTagsMatch() .AddTag(GameplayTags::TAG_Character_State_Alive) .End());
FGameplayTagContainer TargetTags;TargetTags.AddTag(GameplayTags::TAG_Character_State_Alive);
bool bMatches = Query.Matches(TargetTags); // true
// 더 복잡한 쿼리: (Alive AND NOT Stunned)FGameplayTagQuery ComplexQuery = FGameplayTagQuery::BuildQuery( FGameplayTagQueryExpression() .AllTagsMatch() .AddTag(GameplayTags::TAG_Character_State_Alive) .End());6. 실전 예시 — 상태 관리 컴포넌트
섹션 제목: “6. 실전 예시 — 상태 관리 컴포넌트”UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))class MYGAME_API UStatusComponent : public UActorComponent{ GENERATED_BODY()
public: UFUNCTION(BlueprintCallable, Category = "Status") void AddStatus(FGameplayTag StatusTag);
UFUNCTION(BlueprintCallable, Category = "Status") void RemoveStatus(FGameplayTag StatusTag);
UFUNCTION(BlueprintPure, Category = "Status") bool HasStatus(FGameplayTag StatusTag) const;
UFUNCTION(BlueprintPure, Category = "Status") bool CanActivateAbility(const FGameplayTagContainer& AbilityTags) const;
private: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Status", meta=(AllowPrivateAccess="true")) FGameplayTagContainer ActiveStatuses;
// 어빌리티 차단 태그: 이 상태들이 있으면 어빌리티 사용 불가 UPROPERTY(EditDefaultsOnly, Category = "Status") FGameplayTagContainer BlockAbilityTags;};void UStatusComponent::AddStatus(FGameplayTag StatusTag){ if (StatusTag.IsValid()) { ActiveStatuses.AddTag(StatusTag); }}
void UStatusComponent::RemoveStatus(FGameplayTag StatusTag){ ActiveStatuses.RemoveTag(StatusTag);}
bool UStatusComponent::HasStatus(FGameplayTag StatusTag) const{ return ActiveStatuses.HasTag(StatusTag);}
bool UStatusComponent::CanActivateAbility(const FGameplayTagContainer& AbilityTags) const{ // BlockAbilityTags 중 하나라도 ActiveStatuses에 있으면 사용 불가 return !ActiveStatuses.HasAny(BlockAbilityTags);}// 사용 예시void AMyCharacter::OnStunned(){ if (UStatusComponent* Status = FindComponentByClass<UStatusComponent>()) { Status->AddStatus(GameplayTags::TAG_Character_Status_Stunned); }}
bool AMyCharacter::TryUseAbility(const FGameplayTagContainer& AbilityTags){ if (UStatusComponent* Status = FindComponentByClass<UStatusComponent>()) { if (!Status->CanActivateAbility(AbilityTags)) { UE_LOG(LogTemp, Warning, TEXT("Ability blocked by status")); return false; } } // 어빌리티 발동... return true;}7. 정리
섹션 제목: “7. 정리”FGameplayTag::RequestGameplayTag(TEXT("..."))대신UE_DEFINE_GAMEPLAY_TAG로 네이티브 태그 선언 권장 (오타 방지, 컴파일 오류 조기 감지)HasTagvsHasTagExact: 계층 포함 vs 정확 일치MatchesTag: 자신이 다른 태그의 자식이면 true (계층 상속)- UPROPERTY로 선언 시 에디터에서 드롭다운으로 선택 가능
- GAS와 함께 사용 시
UAbilitySystemComponent의 태그 관련 API와 자연스럽게 통합
8. FGameplayTagCountContainer — 스택 기반 태그 카운팅
섹션 제목: “8. FGameplayTagCountContainer — 스택 기반 태그 카운팅”GAS의 UAbilitySystemComponent는 내부적으로 FGameplayTagCountContainer를 사용합니다. 동일 태그가 여러 소스에서 부여될 때 카운트를 추적해 마지막 소스가 제거될 때만 태그가 실제로 사라집니다.
#include "GameplayTagContainer.h"
FGameplayTagCountContainer TagCountContainer;
// 태그 추가 (카운트 +1)TagCountContainer.UpdateTagCount(GameplayTags::TAG_Character_Status_Stunned, 1);TagCountContainer.UpdateTagCount(GameplayTags::TAG_Character_Status_Stunned, 1); // 2회
// 카운트 확인int32 StunCount = TagCountContainer.GetTagCount(GameplayTags::TAG_Character_Status_Stunned);// StunCount == 2
// 태그 존재 여부 (카운트 > 0)bool bIsStunned = TagCountContainer.HasTag(GameplayTags::TAG_Character_Status_Stunned);
// 태그 제거 (카운트 -1)TagCountContainer.UpdateTagCount(GameplayTags::TAG_Character_Status_Stunned, -1);// 카운트 1 → 아직 Stunned
TagCountContainer.UpdateTagCount(GameplayTags::TAG_Character_Status_Stunned, -1);// 카운트 0 → Stunned 해제
// ASC에서 직접 활용void AMyCharacter::ApplyStun(){ // ASC의 태그 카운팅은 GameplayEffect가 자동 처리 // 직접 추가가 필요한 경우: AbilitySystemComponent->AddLooseGameplayTag( GameplayTags::TAG_Character_Status_Stunned);}
void AMyCharacter::RemoveStun(){ AbilitySystemComponent->RemoveLooseGameplayTag( GameplayTags::TAG_Character_Status_Stunned);}9. GAS와 태그 연동 패턴
섹션 제목: “9. GAS와 태그 연동 패턴”// AbilitySystemComponent의 태그 기반 어빌리티 차단void AMyCharacter::SetupTagBlocking(){ if (!AbilitySystemComponent) return;
// 특정 태그가 있을 때 어빌리티 취소 AbilitySystemComponent->RegisterGameplayTagEvent( GameplayTags::TAG_Character_Status_Stunned, EGameplayTagEventType::NewOrRemoved) .AddUObject(this, &AMyCharacter::OnStunTagChanged);}
void AMyCharacter::OnStunTagChanged(const FGameplayTag Tag, int32 NewCount){ if (NewCount > 0) { // 기절 상태 진입 — 이동 입력 차단 GetCharacterMovement()->DisableMovement();
// 진행 중인 모든 어빌리티 취소 AbilitySystemComponent->CancelAllAbilities(); } else { // 기절 해제 — 이동 복원 GetCharacterMovement()->SetMovementMode(MOVE_Walking); }}10. 디버그 & 성능 팁
섹션 제목: “10. 디버그 & 성능 팁”콘솔 명령:showdebug abilitysystem — GAS 태그 현황 화면에 출력log LogGameplayTags All — 태그 관련 로그 활성화GameplayTags.PrintReferencers <TagName> — 해당 태그를 참조하는 에셋 목록// C++에서 태그 유효성 검증 (개발 빌드)void ValidateTags(){ const UGameplayTagsManager& Manager = UGameplayTagsManager::Get();
// 등록되지 않은 태그 사용 경고 FGameplayTag UnknownTag = FGameplayTag::RequestGameplayTag( TEXT("Unknown.Tag"), /*bErrorIfNotFound=*/true); // 없으면 로그 출력}성능 팁:
RequestGameplayTag(TEXT("..."))반복 호출을 피하고UE_DEFINE_GAMEPLAY_TAG로 캐싱하세요HasTagvsHasTagExact: 계층 쿼리가 필요 없다면HasTagExact가 더 빠릅니다FGameplayTagContainer는TArray기반이므로 태그 수가 많을 때HasAll/HasAny는 O(n²)가 될 수 있습니다. 10개 이상이면 별도 캐싱 구조를 검토하세요