기본 개념 및 목적
유니티 게임 개발에서 플레이어 제어, 비전술적 인공지능, 애니메이션 전환 등 복잡한 행동 로직을 관리하기 위해 유한 상태 기계( 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);
}
이처럼 상태가 분리되어 있으므로, 한 상태의 변경이 다른 상태에 영향을 미치지 않으며, 버그 발생 가능성을 줄이고 코드의 가독성과 재사용성을 높입니다.