TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Unity / C# > 인터페이스 기반 유한 상태 머신(FSM)과 플레이어 액션 상태 전이 제어
Unity / C# Intermediate Unity 2021.3+ / Unity 6

인터페이스 기반 유한 상태 머신(FSM)과 플레이어 액션 상태 전이 제어

복잡한 Switch 분기를 제거하고 Enter/Execute/Exit 라이프사이클을 보장하는 C# 인터페이스 기반 유한 상태 머신(Finite State Machine) 아키텍처.

#Unity #C# #FSM #State Pattern #Gameplay Architecture
핵심 개념플레이어 캐릭터에 달리기, 점프, 벽타기, 피격 등의 액션이 늘어날수록 enum과 거대한 switch 문은 버그의 온상이 됩니다. 각 상태를 독립 클래스로 격리하고, 상태 진입(Enter), 갱신(Execute), 탈출(Exit)을 강제하는 FSM을 구축하면 코드 확장성과 유지보수성이 획기적으로 개선됩니다.

1. FSM 핵심 인터페이스 및 컨트롤러

StateMachine.cs
public interface IState
{
    void Enter();
    void Execute();
    void FixedExecute();
    void Exit();
}

public class StateMachine
{
    public IState CurrentState { get; private set; }

    public void ChangeState(IState newState)
    {
        if (CurrentState == newState || newState == null) return;

        CurrentState?.Exit();
        CurrentState = newState;
        CurrentState.Enter();
    }

    public void Update() => CurrentState?.Execute();
    public void FixedUpdate() => CurrentState?.FixedExecute();
}

2. 플레이어 상태 구현 예제 (Idle & Run)

PlayerStates.cs
using UnityEngine;

public class PlayerIdleState : IState
{
    private readonly PlayerController _player;

    public PlayerIdleState(PlayerController player) => _player = player;

    public void Enter()
    {
        _player.Anim.Play("Idle");
        _player.Rb.linearVelocity = new Vector2(0f, _player.Rb.linearVelocity.y);
    }

    public void Execute()
    {
        if (Mathf.Abs(_player.MoveInput) > 0.01f)
        {
            _player.SM.ChangeState(_player.RunState);
        }
    }

    public void FixedExecute() { }
    public void Exit() { }
}

public class PlayerRunState : IState
{
    private readonly PlayerController _player;

    public PlayerRunState(PlayerController player) => _player = player;

    public void Enter() => _player.Anim.Play("Run");

    public void Execute()
    {
        if (Mathf.Abs(_player.MoveInput) <= 0.01f)
        {
            _player.SM.ChangeState(_player.IdleState);
        }
    }

    public void FixedExecute()
    {
        _player.Rb.linearVelocity = new Vector2(_player.MoveInput * _player.Speed, _player.Rb.linearVelocity.y);
    }

    public void Exit() { }
}

3. 성능 및 메모리 팁 (GC 할당 방지)

  • 상태 인스턴스 재사용: ChangeState(new PlayerRunState()) 처럼 매 전이마다 new를 호출하면 GC 스파이크가 발생합니다. Awake에서 각 상태 인스턴스를 미리 캐싱해 두고 참조만 교체해야 합니다.
  • 물리 연산 분리: Update에서는 입력 및 애니메이션 플래그만 검사하고, Rigidbody 속도 제어는 반드시 FixedExecute()에 배치하여 물리 프레임 틱과 완벽히 동기화합니다.