반응형
🎮 구현 목표
복합 상태를 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 계속 추가 | 태그만 정의 |
| 가독성 | ⭐⭐ | ⭐⭐⭐⭐ |
반응형
'프로젝트 회고' 카테고리의 다른 글
| [UE5 액션] 루트 모션 기반 자연스러운 대시 구현 Motion Warping (0) | 2025.12.02 |
|---|---|
| [UE5 액션] 유연한 콤보 시스템: 이벤트 기반 Perfect/Mercy 구간을 통한 공격 후딜레이 캔슬 및 콤보 연계 (0) | 2025.12.02 |
| [DirectX 11] 다중 좌표계 간 위치 동기화 (0) | 2025.12.02 |
| [UE5 액션] 애니메이션 라이프 사이클 기반 비동기 상태 관리 (0) | 2025.12.02 |
| [DirectX 11] Enum의 한계 → FSM Component (0) | 2025.12.02 |
