이번 시간에는 StateMachine에 대해 알아보고 FSM에 대해 이해해보는 시간을 가지겠다.

내가 구현하고 싶었던 기능은 캐릭터가 상태에 따라 다른 동작을 취하게 하는 것이였다.
평소 같았으면 enum 타입을 사용하여 처리를 할 수 있지만, 이 프로젝트 내에서는 다양한 기믹이 존재하기 때문에
여러 상태가 필요한 케이스였다. 따라서 이런 다양한 상태에 따른 처리를 도와줄 수 있는 클래스가 필요해보였다.
1. 상태 기계 (State Machnie, FSM)
상태 기계(StateMachine)이라기도 불리고 "유한 상태 기계(Finite State Machine, FSM)"라 불리는 이 것은 유한한 상태 개수 중 오르지 하나의 상태에만 있게 하는 계산 모델이다.

FSM을 정의하는 세가지 요소는 다음과 같다.
1. 상태(State)
⦁ 개체가 현재 처해 있는 조건이나 수행하고 있는 행동을 나타냅니다.
⦁ 개체는 특정 시점에 오직 하나의 상태에만 있을 수 있습니다(배타성).
⦁ 예시 (게임 캐릭터): Idle (대기), Moving (이동), Jumping (점프), Attacking (공격).
2. 이벤트/조건(Event/Condition)
⦁ 상태 전환(Transition)을 유발하는 외부 또는 내부의 입력입니다.
⦁ 예시: 키 입력, 타이머 만료, 충돌 발생, 체력 감소.
3. 전환(Transitions)
⦁ 특정 이벤트나 조건이 충족될 때 한 상태에서 다른 상태로 넘어가는 과정입니다.
⦁ 예시: Idle 상태에서 '이동 키 입력'이라는 이벤트가 발생하면 Moving 상태로 전환합니다.
2. StateMachine 적용

내가 관리할 State들은 다음과 같다. 프로젝트를 진행하면서 더 늘어나겠지만 일단 5가지의 상태를 가지고 처리를 하고 싶었다.
public interface IState
{
// FSM에서 관리하려는 상태 인터페이스
public void Enter();
public void Do();
public void Exit();
}
우선 State 속성을 나타낼 인터페이스를 만들고 각각 상태 진입/지속/탈출 했을 때 기능을 구현할 수 있도록 설계를 하였다.
public class StateMachine : MonoBehaviour
{
// Player를 FSM을 관리하는 스크립트
private IState curState; // 현재 상태
public IState CurState { get { return curState; } set { curState = value; } }
public void Init(IState initState)
{
// 초기 상태 결정
curState = initState;
curState.Enter();
}
public void ChangeState(IState newState)
{
// 상태 변경
if(curState != null)
{
// 이전 상태가 있다면 Exit 실행
curState.Exit();
}
// 새로운 상태 변경 및 Enter 호출
curState = newState;
curState.Enter();
}
public void Update()
{
// Update에서 Do 실행
curState.Do();
}
}
StateMachine에서는 curState 변수를 통해 하나의 상태에만 동작할 수 있게 설정을 해주고 ChangeState 변경 시 이전 State의 Exit를 호출하고 새로운 State의 Enter를 호출하여 FSM 클래스를 만들게 되었다.
3. 상태 전환 시 규약? FSM의 관리 스케일

하지만 2가지 문제가 있었다.
첫번째는 나중에 Buff 같은 독립적으로 수행하는 것들도 FSM으로 관리를 해야하는가에 대한 고민이였고
두번째는 무분별하게 전환(Transition)이 이뤄지면 (점프 -> 달리기) 상태로 아무런 규약없이 처리 되는 거에 대한 고민이였다.
#1. State에 대한 정의
StateMachine을 사용할 때에는 State의 정의가 가장 중요하였다.
Player를 예를 들면 기본(Idle) / 이동(Move) / 죽음(Die) 같은 상태가 직관적이지만, 이 외에 Buff 걸린 상태를 정의하려면 이전 3가지 상태와 어떤 상관관계가 있는지 판별을 해야 한다. 왜냐하면 Buff 걸린 상태로 이동할 수 있기 때문에 하나의 상태로만 존재할 수 없는 경우가 있기 때문이다.
따라서 기본 5가지의 상태 (Idle, Run, Jump, JumpingPad, Climb) 모두 플레이어 움직임에 관련된 상태로 상관관계를 묶고 버프는 플레이의 Condition이나 State에 관계하기 때문에 독립적 요소라고 판단하고 State에서 배제하였다.
#2. 전환(Transition) 규약
Shift 누르면 Run 상태, Space 누르면 Jump 상태가 되게 전환한다고 할때 Space->Shift로 누르면 공중에서 Run 상태가 되버리게 된다. 따라서 Jump 상태 중일 때는 Run 상태가 되면 안된다. 따라서 이런 상태 전환에 대한 규약이 필요하였다.
// 전환 규약을 나타내는 Dictionary
private Dictionary<IState, List<IState>> allowedTransitions;
public void MakeTransitionRule(IState fromState, IState toState)
{
if (!allowedTransitions.ContainsKey(fromState))
{
// 새로운 Transition이면 새로 생성
allowedTransitions[fromState] = new List<IState>();
}
allowedTransitions[fromState].Add(toState);
}
따라서 Animation BlenTree처럼 Transition 규약을 만들 수 있는 메서드를 다음과 같이 작성하였다.

private void SetUpTransition()
{
stateMachine.MakeTransitionRule(IdleState, MoveState);
stateMachine.MakeTransitionRule(IdleState, RunState);
stateMachine.MakeTransitionRule(IdleState, JumpState);
stateMachine.MakeTransitionRule(IdleState, JumpingPadState);
stateMachine.MakeTransitionRule(RunState, JumpState);
stateMachine.MakeTransitionRule(RunState, JumpingPadState);
stateMachine.MakeTransitionRule(JumpState, IdleState);
stateMachine.MakeTransitionRule(JumpState, JumpingPadState);
stateMachine.MakeTransitionRule(JumpingPadState, IdleState);
}
위 그림과 같이 Jump 상태에서는 Run 상태로 전환하는 Transition 줄이 없게 구현하였다.
public void ChangeState(IState newState)
{
// 상태 변경
if (curState != null &&
allowedTransitions.ContainsKey(curState) &&
!allowedTransitions[curState].Contains(newState))
{
Debug.Log($"규약 위반: {curState}에서 {newState}으로 전환 불가");
return;
}
if (curState != null)
{
// 이전 상태가 있다면 Exit 실행
curState.Exit();
}
// 새로운 상태 변경 및 Enter 호출
curState = newState;
curState.Enter();
}
마지막으로 ChangeState에서 규약 검사만 하면 상태 전환 시 불필요한 예외처리를 할 수 있게 된다.
'프로그래밍 > Unity' 카테고리의 다른 글
| LineRenderer를 사용하여 Laser 만들기 (0) | 2025.11.12 |
|---|---|
| Unity Rendering Material 호환 오류 (보라색상) (0) | 2025.11.12 |
| Unity AI Navigation (0) | 2025.11.07 |
| Unity Input System (0) | 2025.11.06 |
| Unity 내장 메서드 비용(Cost) 문제 (0) | 2025.10.28 |