콘텐츠로 이동

UE5 C++ Gameplay Tags

Gameplay Tags는 UE5에서 제공하는 계층적 이름 기반 레이블 시스템입니다. FName 비교보다 안전하고, bool 플래그 배열보다 확장성이 뛰어납니다.

Character.State.Alive
Character.State.Dead
Character.Status.Stunned
Ability.Combat.Melee.Slash
Ability.Combat.Ranged.Arrow
비교 항목bool 플래그FNameGameplay Tags
계층 쿼리불가불가MatchesTag
에디터 드롭다운없음없음자동 생성
런타임 추가가능가능제한적
GAS 연동없음없음네이티브 지원

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)
}
GameplayTags.cpp
#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")
}

#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") = true
bool bIsSubset = SlashTag.MatchesTag(MeleeTag); // true
// MatchesTagExact: 정확히 같은 태그일 때만 true
bool bExact = SlashTag.MatchesTagExact(MeleeTag); // false

3. 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());
}

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. 실전 예시 — 상태 관리 컴포넌트”
StatusComponent.h
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;
};
StatusComponent.cpp
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;
}

  • FGameplayTag::RequestGameplayTag(TEXT("...")) 대신 UE_DEFINE_GAMEPLAY_TAG로 네이티브 태그 선언 권장 (오타 방지, 컴파일 오류 조기 감지)
  • HasTag vs HasTagExact: 계층 포함 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);
}

// 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);
}
}

콘솔 명령:
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로 캐싱하세요
  • HasTag vs HasTagExact: 계층 쿼리가 필요 없다면 HasTagExact가 더 빠릅니다
  • FGameplayTagContainerTArray 기반이므로 태그 수가 많을 때 HasAll/HasAny는 O(n²)가 될 수 있습니다. 10개 이상이면 별도 캐싱 구조를 검토하세요