유니티에서의 유한 상태 기계(FSM) 설계 패턴

기본 개념 및 목적

유니티 게임 개발에서 플레이어 제어, 비전술적 인공지능, 애니메이션 전환 등 복잡한 행동 로직을 관리하기 위해 유한 상태 기계( Finite State Machine, FSM)는 핵심적인 설계 패턴입니다. 초기에는 모든 동작을 하나의 스크립트에 집어넣는 방식이 일반적이지만, 이는 향후 유지보수와 확장성 측면에서 큰 문제를 초래합니다. 따라서 각 상태를 분리하고 책임을 명확히 하는 것이 중요합니다.

단일 책임 원칙 적용

각 상태는 독립적으로 설계되어야 하며, 상태 간의 결합도를 최소화해야 합니다. 이를 위해 상태별로 클래스를 분리하고, 공통된 구조를 추상화하는 방법을 사용합니다.

1. 상태 기본 추상 클래스 정의

모든 상태가 공유하는 세 가지 주요 메서드(Enter, Update, Exit)를 포함하는 추상 클래스를 생성합니다.

public abstract class State
{
    public virtual void Enter() => Debug.Log("State 진입");
    
    public virtual void Update() => Debug.Log("State 업데이트");
    
    public virtual void Exit() => Debug.Log("State 종료");
}

2. 구체적 상태 클래스 구현

각 상태는 State를 상속받아 자신의 동작을 정의합니다.

대기 상태

public class IdleState : State
{
    public override void Enter() => Debug.Log("대기 상태 진입");
    
    public override void Update() => Debug.Log("대기 중 처리");
    
    public override void Exit() => Debug.Log("대기 상태 종료");
}

이동 상태

public class MoveState : State
{
    public override void Enter() => Debug.Log("이동 상태 진입");
    
    public override void Update() => Debug.Log("이동 중 처리");
    
    public override void Exit() => Debug.Log("이동 상태 종료");
}

3. 상태 관리자 클래스

현재 상태를 저장하고 상태 전환을 제어하는 중심 역할을 수행합니다.

public class StateManager : MonoBehaviour
{
    private State currentState;

    public void SwitchTo(State newState)
    {
        if (currentState != null)
            currentState.Exit();

        currentState = newState;
        currentState.Enter();
    }

    private void Update()
    {
        if (currentState != null)
            currentState.Update();
    }
}

4. 플레이어 컨트롤러 연결

게임 오브젝트에 StateManager를 추가하고, 입력에 따라 상태를 전환합니다.

public class PlayerController : MonoBehaviour
{
    private StateManager stateManager;
    private IdleState idleState;
    private MoveState moveState;

    void Start()
    {
        stateManager = GetComponent<StateManager>();
        idleState = new IdleState();
        moveState = new MoveState();

        stateManager.SwitchTo(idleState);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            stateManager.SwitchTo(moveState);

        if (Input.GetKeyDown(KeyCode.Escape))
            stateManager.SwitchTo(idleState);
    }
}

확장성과 유지보수성

새로운 상태(예: 공격)를 추가하려면 기존 코드를 수정하지 않고, 단순히 새로운 클래스를 생성하고 컨트롤러에 등록만 하면 됩니다. 예를 들어, 공격 상태는 다음과 같이 정의됩니다.

public class AttackState : State
{
    public override void Enter() => Debug.Log("공격 상태 진입");
    
    public override void Update() => Debug.Log("공격 실행 중");
    
    public override void Exit() => Debug.Log("공격 상태 종료");
}

컨트롤러에 추가만 하면 됩니다:

private AttackState attackState;

void Start()
{
    // ...
    attackState = new AttackState();
}

void Update()
{
    if (Input.GetKeyDown(KeyCode.Mouse0))
        stateManager.SwitchTo(attackState);
}

이처럼 상태가 분리되어 있으므로, 한 상태의 변경이 다른 상태에 영향을 미치지 않으며, 버그 발생 가능성을 줄이고 코드의 가독성과 재사용성을 높입니다.

태그: Unity FSM 상태 패턴 C# 객체 지향 설계

8월 3일 01:18에 게시됨