Context와 State의 차이

구분 Context State
의미 외적 상황, 입력 해석 환경 내적 상태, 현재 행동 상태
질문 지금 어떤 모드인가? 지금 무엇을 하고 있는가?
변경 주체 플레이어 선택, 환경 변화 시스템 전이
예시 전투 모드, 청소 모드, 차량 탑승 Idle, Cleaning, Attacking, Dead
지속성 명시적으로 변경될 때까지 유지 조건에 따라 자동 전환

Context는 입력의 의미를 결정한다.
State는 현재 행동 가능 여부를 결정한다.


Context만 사용할 때의 문제

void OnInteract()
{
    if (CurrentContext == FloorCleaning)
    {
        CleanFloor();
    }
}

이 구조에서는 청소 애니메이션이 재생 중이어도 OnInteract가 반복 호출될 수 있다.
즉, 현재 행동 상태를 막아줄 장치가 없다.

State를 함께 사용하면 다음처럼 막을 수 있다.

void OnInteract()
{
    if (CurrentContext == FloorCleaning && CurrentState == Idle)
    {
        CleanFloor();
        CurrentState = Cleaning;
    }
}

State는 행동을 실행해도 되는지 판단하는 “허가증” 역할을 한다.


Finite State Machine

Finite State Machine, 즉 FSM은 제한된 상태와 상태 간 전이 규칙으로 동작을 관리하는 패턴이다.

FSM은 보통 다음 요소로 구성된다.

요소 설명
State 가능한 상태 집합
Transition 상태 간 이동 규칙
Guard 전환 허용 조건

State 정의

enum class ECleaningState : uint8
{
    Idle,
    Cleaning
};

전이 구조

Idle
  ↓ StartCleaning()
Cleaning
  ↓ Animation Finished / Interrupt
Idle

Guard 함수

bool CanStartCleaning() const
{
    return CurrentState == ECleaningState::Idle;
}

bool CanMove() const
{
    return CurrentState != ECleaningState::Cleaning;
}

bool CanJump() const
{
    return CurrentState != ECleaningState::Cleaning;
}

State Machine Component 구조

class UCleaningStateMachine : public UActorComponent
{
    GENERATED_BODY()

private:
    ECleaningState CurrentState = ECleaningState::Idle;

    float StateStartTime = 0.f;
    float AnimationDuration = 0.f;

    UPROPERTY()
    class AEnhancedChallengeCharacter* OwnerCharacter;

public:
    void StartCleaning(float Duration);
    void InterruptCleaning();

    bool CanStartCleaning() const;
    bool CanMove() const;
    bool CanJump() const;

    ECleaningState GetCurrentState() const
    {
        return CurrentState;
    }

protected:
    virtual void BeginPlay() override;
    virtual void TickComponent(
        float DeltaTime,
        ELevelTick TickType,
        FActorComponentTickFunction* ThisTickFunction
    ) override;

private:
    void SetState(ECleaningState NewState);
    void StopCurrentMontage();
};

State 전환 처리

StartCleaning

void UCleaningStateMachine::StartCleaning(float Duration)
{
    if (!CanStartCleaning())
    {
        return;
    }

    ECleaningState OldState = CurrentState;

    CurrentState = ECleaningState::Cleaning;

    StateStartTime = GetWorld()->GetTimeSeconds();
    AnimationDuration = Duration;

    OnStateChanged.Broadcast(OldState, CurrentState);
}

Tick에서 자동 복귀

void UCleaningStateMachine::TickComponent(
    float DeltaTime,
    ELevelTick TickType,
    FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    if (CurrentState == ECleaningState::Cleaning)
    {
        float CurrentTime = GetWorld()->GetTimeSeconds();
        float ElapsedTime = CurrentTime - StateStartTime;

        if (ElapsedTime >= AnimationDuration)
        {
            SetState(ECleaningState::Idle);
        }
    }
}

InterruptCleaning

void UCleaningStateMachine::InterruptCleaning()
{
    if (CurrentState == ECleaningState::Cleaning)
    {
        StopCurrentMontage();
        SetState(ECleaningState::Idle);
    }
}

Enter / Update / Exit 패턴

State Machine은 다음 흐름으로 구성할 수 있다.

Exit Old State
   ↓
Transition
   ↓
Enter New State
   ↓
Update Current State
void UCleaningStateMachine::SetState(ECleaningState NewState)
{
    if (CurrentState == NewState)
    {
        return;
    }

    ECleaningState OldState = CurrentState;

    switch (OldState)
    {
    case ECleaningState::Cleaning:
        StopCurrentMontage();
        break;

    case ECleaningState::Idle:
        break;
    }

    CurrentState = NewState;

    switch (NewState)
    {
    case ECleaningState::Idle:
        break;

    case ECleaningState::Cleaning:
        break;
    }

    OnStateChanged.Broadcast(OldState, NewState);
}

Animation과 State의 분리

애니메이션만으로 상태를 관리하면 안 된다.

잘못된 방식

void OnInteract()
{
    PlayAnimMontage(CleaningMontage);
}

이 방식은 애니메이션이 중단되거나 스킵되었을 때 게임 로직과 실제 표현이 불일치할 수 있다.

올바른 방식

void OnInteract()
{
    if (!StateMachine->CanStartCleaning())
    {
        return;
    }

    float Duration = PlayAnimMontage(CleaningMontage);

    StateMachine->StartCleaning(Duration);
}
반응형

'Unreal Engine' 카테고리의 다른 글

DataTable, DataAsset  (0) 2026.06.26
UE Input 3  (0) 2026.06.25
UE Input 1  (0) 2026.06.23
GameplayTag Static vs GameplayTag Extern  (0) 2026.06.22
AssetManager  (0) 2026.06.19