UE5 게임플레이 상태 머신 구현
왜 상태 머신이 필요한가
섹션 제목: “왜 상태 머신이 필요한가”캐릭터의 행동(Idle, Run, Attack, Stun, Dead)은 서로 충돌하는 전환 규칙이 있습니다. 조건문을 직접 관리하면 상태가 늘어날수록 복잡도가 기하급수적으로 증가합니다. 상태 머신은 전환 규칙을 명시적으로 정의해 이 복잡도를 제어합니다.
방법 1: Enum 기반 C++ FSM
섹션 제목: “방법 1: Enum 기반 C++ FSM”소규모 프로젝트에 적합한 직접 구현 방식입니다.
UENUM(BlueprintType)enum class ECharacterState : uint8 { Idle, Run, Jump, Attack, Stun, Dead};
UCLASS()class AMyCharacter : public ACharacter { GENERATED_BODY()
ECharacterState CurrentState = ECharacterState::Idle;
public: bool TryTransitionTo(ECharacterState NewState); void OnStateEnter(ECharacterState State); void OnStateExit(ECharacterState State);};
bool AMyCharacter::TryTransitionTo(ECharacterState NewState) { // 전환 가능 여부 검사 테이블 static const TMap<ECharacterState, TSet<ECharacterState>> ValidTransitions = { { ECharacterState::Idle, { ECharacterState::Run, ECharacterState::Attack, ECharacterState::Jump } }, { ECharacterState::Run, { ECharacterState::Idle, ECharacterState::Jump, ECharacterState::Attack } }, { ECharacterState::Attack, { ECharacterState::Idle } }, { ECharacterState::Jump, { ECharacterState::Idle } }, { ECharacterState::Stun, { ECharacterState::Idle } }, { ECharacterState::Dead, { } }, };
const auto* Allowed = ValidTransitions.Find(CurrentState); if (!Allowed || !Allowed->Contains(NewState)) return false;
OnStateExit(CurrentState); CurrentState = NewState; OnStateEnter(NewState); return true;}
void AMyCharacter::OnStateEnter(ECharacterState State) { switch (State) { case ECharacterState::Attack: BeginAttack(); break; case ECharacterState::Dead: Die(); break; default: break; }}방법 2: GAS Gameplay Tag 기반 상태
섹션 제목: “방법 2: GAS Gameplay Tag 기반 상태”GAS를 사용하는 프로젝트에서 권장합니다. 태그로 상태를 표현해 시스템 간 결합도를 낮춥니다.
// 상태 태그 정의// State.Movement.Run// State.Combat.Attacking// State.Status.Stunned
// 상태 전환void UMyCombatAbility::ActivateAbility(...) { // 스턴 상태면 활성화 불가 (Activation Required/Blocked Tags로 설정) // AbilityTags: Ability.Combat.Attack // ActivationBlockedTags: State.Status.Stunned, State.Movement.Dead
AbilitySystemComponent->AddLooseGameplayTag( FGameplayTag::RequestGameplayTag("State.Combat.Attacking"));}
void UMyCombatAbility::EndAbility(...) { AbilitySystemComponent->RemoveLooseGameplayTag( FGameplayTag::RequestGameplayTag("State.Combat.Attacking"));}
// 상태 조회bool IsAttacking() const { return AbilitySystemComponent->HasMatchingGameplayTag( FGameplayTag::RequestGameplayTag("State.Combat.Attacking"));}방법 3: StateTree (UE5.1+)
섹션 제목: “방법 3: StateTree (UE5.1+)”UE5.1에서 도입된 고수준 상태 머신으로, 에디터에서 시각적으로 구성합니다.
// StateTree 태스크 구현USTRUCT()struct FAttackStateTask : public FStateTreeTaskBase { GENERATED_BODY()
using FInstanceDataType = FAttackStateTaskData;
EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const { auto& Data = Context.GetInstanceData(*this); Data.Character->BeginAttack(); return EStateTreeRunStatus::Running; }
EStateTreeRunStatus Tick(FStateTreeExecutionContext& Context, float DeltaTime) const { auto& Data = Context.GetInstanceData(*this); return Data.Character->IsAttackComplete() ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Running; }
void ExitState(FStateTreeExecutionContext& Context, ...) const { Context.GetInstanceData(*this).Character->EndAttack(); }};방식 비교
섹션 제목: “방식 비교”| 방식 | 적합 규모 | 장점 | 단점 |
|---|---|---|---|
| Enum FSM | 소규모 | 단순, 빠른 구현 | 상태 증가 시 복잡도 폭발 |
| GAS Tag | 중~대규모 | 시스템 분리, 확장성 | GAS 셋업 필요 |
| StateTree | 중~대규모 | 시각적 편집, 조건 내장 | UE5.1+, 학습 곡선 |
상태 전환 시 애니메이션 동기화
섹션 제목: “상태 전환 시 애니메이션 동기화”void AMyCharacter::OnStateEnter(ECharacterState State) { static const TMap<ECharacterState, FName> StateMontages = { { ECharacterState::Attack, "Attack_Montage" }, { ECharacterState::Stun, "Stun_Montage" }, };
if (const FName* Montage = StateMontages.Find(State)) GetMesh()->GetAnimInstance()->Montage_Play( GetMontageByName(*Montage));}- 소규모: Enum + 전환 테이블 방식으로 빠르게 구현
- GAS 사용 시: Gameplay Tag로 상태 표현, Activation Blocked Tags로 충돌 방지
- UE5.1+: StateTree로 에디터 기반 복잡한 AI/캐릭터 상태 관리
- 상태 진입/이탈 이벤트에서 애니메이션, 사운드, 물리 처리를 중앙화