프로젝트 회고 / / 2025. 12. 2. 22:49

[UE5 액션] 상태 관리: StateComponent와 GameplayTag

반응형

🎮 구현 목표

복합 상태를 bool 변수 없이 직관적으로 표현하고, 상태 검사를 체계적으로 관리하기

UE5 프로젝트에서는 GameplayTag로 간결하게 로직을 구현하고자 했습니다.

 

🚨 문제 상황

 

처음에는 GameplayTag 단일로 상태 관리(CurrentState)를 시도했습니다.
그러나 전투 로직의 복잡도가 증가하면서 "공격 중 피격"과 같은 상황이 문제가 되었습니다.

 

예를 들어:

상태 대미지 일반 공격 상태이상 공격 잡기 공격
일반 O 공격 중단
+ 피격 재생
공격 중단
+ 피격 재생
공격 중단
+ 피격 재생
경직 면역 O 공격 유지 공격 중단
+ 피격 재생
공격 중단
+ 피격 재생
슈퍼 아머 O 공격 유지 공격 유지 공격 유지
무적 X 공격 유지 공격 유지 공격 유지

 

이런 복합적인 상황을 CurrentState 하나로 처리하는데 한계가 있었습니다.

 

예를 들어

  • 태그를 여러 개 사용할 수 있으면
    • Character.State.Attacking
    • Character.State.SuperArmour
    • Character.State.Hit
    • "캐릭터가 공격 중이고, 슈퍼아머 상태인데 피격됐구나!"
  • 태그를 하나만 써야 한다면
    • Charater.State.Attacking.Condition.HitIgnore
    • "캐릭터가 공격 중일 때, 피격은 무시한다." ← 덜 직관적이고 조건이 늘어날수록 태그 수도 급격히 증가

 

💭 해결 방안 고민

 

"상태를 여러 개 동시에 가질 수 있다면?"

 

게임플레이 태그의 계층 구조를 활용하는 방법도 고민했으나, GameplayTag Container
여러 상태(ActiveGameplayTags)를 가지고 이를 검사하는 방식이 더 유연할 것이라고 판단했습니다.

 

핵심 아이디어:

  • 상태를 태그(문자열)로 표현
  • Container에 여러 태그를 동시에 보관
  • "이 태그를 포함하고 있는가?" 쿼리로 상태 검사
// StateComponent.h - Container로 관리
class UStateComponent
{
    FGameplayTagContainer ActiveGameplayTags; // 현재 활성 상태들

    void AddGameplayTag(const FGameplayTag& Tag);
    bool IsActiveGameplayTag(const FGameplayTag& Tag);
    bool IsAnyActiveGameplayTags(const FGameplayTagContainer& Tags);
};

 

🔧 활용

 

특정 동작을 실행하기 위한 상태 검사

bool ASoulCharacterBase::CanPerformAttack(FGameplayTag& AttackTypeTag, 
        const bool bHitCanceled, const bool bPairedAnimation)
{
    // 공격을 수행할 수 없는 상태를 정의
    FGameplayTagContainer CheckTags;
    CheckTags.AddTag(SoulGameplayTag::Character_State_Rolling);
    CheckTags.AddTag(SoulGameplayTag::Character_State_GeneralAction);
    CheckTags.AddTag(SoulGameplayTag::Character_State_Blocking);
    CheckTags.AddTag(SoulGameplayTag::Character_State_Stunned);
    CheckTags.AddTag(SoulGameplayTag::Character_State_DrinkingPotion);
    CheckTags.AddTag(SoulGameplayTag::Character_State_Down);
    CheckTags.AddTag(SoulGameplayTag::Character_State_Interaction);
    CheckTags.AddTag(SoulGameplayTag::Character_State_Attacking_Recovery);

    return StateComponent->IsAnyActiveGameplayTags(CheckTags) == false && // 태그 검사
        CombatComponent->IsCombatEnabled() == true &&
        AttributeComponent->CheckHasEnoughStamina(StaminaCost) == true;
}

 

이렇게 하면:

  • "공격 중 + 경직 면역" = Attacking + Poise 두 태그 동시 보유
  • "피격 시 경직 면역인가?" = Container에 Poise 태그 있는지 검사
  • 복잡한 조합 = 필요한 태그들만 추가/제거

 

디버깅

// StateComponent.cpp - Tick에서 실시간 확인
void UStateComponent::TickComponent(float DeltaTime, ...)
{
    if (GetOwner() != GetWorld()->GetFirstPlayerController()->GetPawn()) 
        return;

    uint64 Index = 1000;
    GEngine->AddOnScreenDebugMessage(
        Index++, 10.f, FColor::Cyan, 
        TEXT("Active Gameplay Tags : ")
    );

    // 현재 활성화된 모든 태그 출력
    for (const FGameplayTag& GameplayTag : ActiveGameplayTags)
    {
        GEngine->AddOnScreenDebugMessage(
            Index++, 0.03f, FColor::Cyan, 
            GameplayTag.ToString()
        );
    }
}

 

효과:

  • 플레이 중 현재 상태 실시간 확인 가능
  • "왜 공격이 안 나가지?" → 화면 보고 Character.State.Attacking 태그 발견 → 태그 제거 로직 검사
  • 복합 상태 디버깅이 직관적으로 변함

 

 

✅ 결과

개선 효과:

항목 DirectX (Before) UE5 (After)
복합 상태 bool 변수 여러 개 사용 AddTag()
상태 검사 if (!A && !B && C) HasTagExact()
디버깅 변수 일일이 확인 Container 출력
확장성 bool 계속 추가 태그만 정의
가독성 ⭐⭐ ⭐⭐⭐⭐
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유