2026-06-16 TIL (77일차)
1. FSM
“캐릭터는 한 번 단 하나의 행동만 할 수 있다.”
유한 상태 머신(Finite State Machine, FSM)은 시스템의 동작을 이해하기 쉽도록 단순화하는 수학적 모델이자 프로그래밍 기법입니다.
- 자판기: 대기 상태 ➡️ 동전 투입 이벤트 ➡️ 음료 배출 상태
- 선풍기: 정지 상태 ➡️ 1단 버튼 클릭 ➡️ 1단 회전 상태
이처럼 FSM은 시스템이 취할 수 있는 상태를 정의하고, 특정 조건에 따라 상태를 전환하며 동작을 제어합니다.
2. FSM의 3가지 핵심 요소
FSM이 굴러가는 원리는 다음 3가지 핵심 요소로 이루어져 있습니다.
- 상태 (State): 오브젝트가 현재 무엇을 하고 있는가를 나타냅니다. (예:
Idle,Walk,Attack,Dead) - 전환 (Transition): 하나의 상태에서 다른 상태로 넘어가는 다리 역할을 합니다.
- 조건 및 이벤트 (Condition/Event): 다리를 건너게 만드는 방아쇠입니다. (예:
HP가 0이 됨,공격 버튼 입력)
이 구조를 통해 개발자는 오브젝트가 어떻게 반응해야 할지 제어할 수 있습니다.
3. 언리얼 엔진에서의 FSM 활용법
실제 게임 개발, 특히 언리얼 엔진(Unreal Engine) 환경에서 FSM은 구현해야 하는 로직이 복잡하거나 오브젝트 간 상호작용이 필요할 때 널리 쓰입니다.
활용 1: 캐릭터 로직 제어 (C++ / 블루프린트)
일반적으로 프로그래밍 단계에서는 열거형과 조건문을 조합하여 FSM을 구현합니다.
- 열거형으로
ECharacterState::Idle,ECharacterState::Attack등을 정의합니다. - 매 프레임(Tick)마다 조건문의 노드를 거쳐 현재 상태에 맞는 로직만 실행시킵니다.
- 데이터 관리의 명확성이 높아져 유지보수가 매우 편리해집니다.
예시 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 상태 정의
UENUM(BlueprintType)
enum class ECharacterState : uint8
{
Idle UMETA(DisplayName = "대기"),
Walk UMETA(DisplayName = "이동"),
Attack UMETA(DisplayName = "공격"),
Dead UMETA(DisplayName = "사망")
};
UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
// 현재 상태를 저장할 변수
ECharacterState CurrentState = ECharacterState::Idle;
float HP = 100.0f;
float Speed = 0.0f;
virtual void Tick(float DeltaTime) override;
// 이벤트를 처리할 함수들
void PerformAttack();
void TakeDamage(float DamageAmount);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void AMyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 전환 & 조건 검사
switch (CurrentState)
{
case ECharacterState::Idle:
// 조건: 속도가 0보다 크면? -> Walk 상태로 전환!
if (Speed > 0.0f)
{
CurrentState = ECharacterState::Walk;
}
break;
case ECharacterState::Walk:
// 조건: 속도가 0이 되면? -> Idle 상태로 전환!
if (Speed == 0.0f)
{
CurrentState = ECharacterState::Idle;
}
break;
case ECharacterState::Attack:
// 공격이 끝나면 다시 Idle로 돌아가는 로직 수행
break;
case ECharacterState::Dead:
// 사망 상태에서는 아무것도 하지 않음
break;
}
}
// 이벤트에 의한 상태 전환
void AMyCharacter::PerformAttack()
{
// 대기나 이동 중일 때만 공격 상태로 전환 가능
if (CurrentState == ECharacterState::Idle || CurrentState == ECharacterState::Walk)
{
CurrentState = ECharacterState::Attack;
}
}
// 이벤트에 의한 상태 전환 2
void AMyCharacter::TakeDamage(float DamageAmount)
{
if (CurrentState == ECharacterState::Dead) return;
HP -= DamageAmount;
// 조건: HP가 0 이하가 되면?
if (HP <= 0.0f)
{
CurrentState = ECharacterState::Dead; // 사망 상태로 전환
}
}
활용 2: 애니메이션 블루프린트 (스테이트 머신)
언리얼 엔진의 애니메이션 시스템은 FSM 구조를 시각화한 State Machine 기능을 기본 제공합니다.
- 각 노드는 캐릭터의 애니메이션 상태(예: 서있기, 달리기)를 의미합니다.
- 노드를 잇는 화살표(Transition Rule)에 ‘속도가 0보다 큰가?’와 같은 조건을 부여합니다.
- 이를 통해 애니메이션 전환을 설계할 수 있습니다.
4. FSM의 단점과 대안
FSM은 게임의 스케일이 커지면 단점이 생기게 됩니다.
FSM의 단점: ‘스파게티 맵’
상태가 10개, 20개로 늘어나고 모든 상태가 서로 상호작용해야 한다고 가정해 봅시다. 노드를 잇는 화살표는 늘어나 복잡해집니다. 결국 개발자조차 흐름을 파악할 수 없어 유지보수가 불가능해집니다. 이를 극복하기 위해 언리얼 엔진에서 크게 AI와 애니메이션에서 새로운 방법을 사용합니다.
대안 1 : 비헤이비어 트리 (Behavior Tree)
상태(State) 중심의 FSM과 달리, 비헤이비어 트리는 우선순위와 행동 중심으로 작동합니다. 위에서 아래로, 왼쪽에서 오른쪽으로 조건을 체크하며 트리 구조를 확장합니다.
근본적인 설계 사상의 차이
BT도 겉보기에는 ‘상태’에 따라 행동을 결정하는 것 같지만 다릅니다.
1. “상태”라는 단어의 의미와 굴레
- FSM의 상태: “지금 이 상태니까 무조건 이것만 한다!” FSM에서 캐릭터는 ‘상태’라는 안에 갇혀 있습니다. 만약
추적상태에 들어갔다면, 미리 코딩해 둔 ‘조건’이 충족되기 전까지는 무조건 추적만 합니다. 정해진 스위치가 켜지면 그에 맞는 기계적인 동작을 무한 반복하는 구조입니다. - BT의 상태: 비헤이비어 트리에는 애초에 FSM과 같은 ‘상태’라는 개념이 없습니다. 대신 행동(Task)과 그것을 결정하는 데이터(Blackboard)가 분리되어 있습니다.
- 블랙보드 (메모장): 언리얼 엔진에서 AI가 참고할 변수를 적어두는 메모장입니다. (예: “적 위치=(X,Y,Z)”, “내 체력=30”, “적 발견=True”) 블랙보드 스스로는 아무런 판단도 하지 않는 데이터베이스입니다.
- 비헤이비어 트리 (판단하는 뇌): BT는 매 순간 블랙보드를 쳐다봅니다. “어? 메모장에 ‘적 발견=True’로 바뀌었네? 그럼 최우선 순위인 ‘추적’ 행동(Task)을 실행해야지!” 라고 판단합니다.
2. 제어권의 주체
- FSM: 흐름의 주체가 현재 적용되어 있는 상태에게 있습니다.
추적상태 로직 안에서 “내가 이제공격으로 넘어갈까? 아니면대기으로 돌아갈까?”를 판단하고 전환니다. - BT: 운전대는 항상 트리의 뿌리(Root, 뇌)에 있습니다. 매 틱마다 통제실(Root)이 위에서 아래로 평가를 시작합니다.
- “죽었니?” ➡️ 아니오
- “체력 없니?” ➡️ 아니오
- “메모장(블랙보드)에 적 보인다고 적혀 있니?” ➡️ 네 ➡️ “그럼 ‘공격’ 행동 시작해!” 여기서 중요한 점은, 공격 행동(Task) 스스로는 다음 행동으로 넘어갈 권한이 없다는 것입니다. 그저 통제실에 “저 공격 성공했어요!” 또는 “아직 공격 중이에요!”라고 결과만 보고할 뿐이며, 모든 결정은 통제실(Root)이 내립니다.
로직 추가 시의 차이
위에서 설명한 구조적 차이는 ‘새로운 행동을 추가할 때’ 드러납니다. 기존 로직에 도망가기 기능을 추가한다고 가정해 봅시다.
FSM:
기존의 대기, 순찰, 공격 등 모든 상태에서 ‘도망가기’로 이어지는 화살표(전환 조건)를 새로 긋고 연결해야 합니다.BT:
트리의 가장 왼쪽(최우선 순위)에 "체력이 10% 이하인가? -> 도망간다"라는 가지(Branch)를 하나 추가하면 끝납니다. 기존 로직은 건드릴 필요가 없습니다.
| 구분 | 유한 상태 머신 (FSM) | 비헤이비어 트리 (Behavior Tree) |
|---|---|---|
| 작동 패러다임 | 상태(State) 중심 (“나 지금 무슨 상태지?”) | 작업(Task) 우선순위 중심 (“지금 제일 급한 일이 뭐지?”) |
| 흐름 제어 | 상태 간의 직접적인 전환(Transition) | 노드의 성공/실패 여부에 따른 조건부 실행 |
| 기능 추가 시 | 기존 상태들에 전환 조건을 모두 엮어야 함 | 적절한 위치에 새 가지(Branch)만 추가하면 됨 |
| 적합한 용도 | 캐릭터 애니메이션, 문(열림/닫힘), 퀘스트 단계 | 복잡한 몬스터 AI, NPC의 다채로운 패턴 행동 |
대안 2: 모션 매칭 (Motion Matching)
개발자가 수동으로 화살표(전환 조건)를 잇는 기존 애니메이션 FSM 방식에서 벗어나, AI 검색 엔진처럼 작동하여 최적의 모션을 찾아내는 언리얼 엔진 5(UE5)의 기술입니다.
애니메이션 스테이트 머신 vs 모션 매칭
캐릭터가 ‘걷기 ➡️ 뛰기 ➡️ 멈추기 ➡️ 180도 턴’을 수행해야 한다는 상황이 있습니다.
- FSM: 애니메이터가 모든 걷기, 뛰기, 정지 모션 사이에 화살표를 이어주고 “속도가 300 이상일 때 전환”, “0.2초 동안 딜레이” 등의 조건을 수동으로 할당해야 합니다.
- 모션 매칭: 수백 개의 모션 캡처 데이터를 하나의 Database에 넣기만 하면 됩니다. 엔진이 매 프레임 캐릭터의 ‘현재 자세(Pose)’와 ‘미래의 이동 궤적(Trajectory)’을 계산하여 가장 알맞은 애니메이션 프레임을 실시간으로 검색해 재생합니다.
| 구분 | 애니메이션 스테이트 머신 (FSM) | 모션 매칭 (Motion Matching) |
|---|---|---|
| 작동 원리 | 수동으로 설정한 노드와 룰(Rule)에 따라 전환 | 데이터베이스에서 최적의 프레임을 실시간 검색 |
| 작업 방식 | 화살표 잇기 및 미세 블렌딩 튜닝 (수동) | 방대한 모션 캡처 데이터 통째로 삽입 (자동화) |
| 장점 | 구조가 단순하고 통제가 완벽히 가능함 | 작업량이 획기적으로 줄고, 압도적으로 사실적임 |



