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 |