Unity3D

Unity Finite State Machine | FSM | 유한상태머신

DragonTory 2023. 6. 2. 22:52
반응형

게임을 만들때 Finite State Machine (FSM)을 필수 적으로 사용을 하게 되는 것 같다.

 

지금까지 FSM을 만들어 사용 하고 있는데,

 

처음에는 일반적인 FSM 방식의 상태 클래스에 Enter / Update / Exit 등의 가상 메소드를 정의 해놓고 상태 마다 상속 해서 구현 하는 방식으로 만들어서 사용 했다.

간단한 상태도 cs파일(또는 상태 마다 클래스 정의)로 만들어야 하고 이게 상태가 많아지면 cs 파일도 많아지는 문제가 있다. 

 

두번째는 GUI FSM이나 Unity 에니메이터에서 사용 하는 Transition 상태 조건을 연결 해서 상태를 전환 해주는 방식을 사용 했었다.

이 방식 역시 상태마다 cs파일이나 클래스가 필요한것은 위와 마찬가지 이지만 Transition 조건을 추가 하는 방식으로 상태에서 상태로 전이 되는 부분을 일목요연 하게 볼 수 있는 장점이 있지만 Transition 조건을 계속 체크 해야 하는 문제와 조건이 많아지면 복잡해는 문제가 있다.

그래서 이 방식은 이제 사용 안 한다. 

 

세번째는 첫번째 FSM에 Stack 개념을 추가 해서 Stacked FSM을 만들어 사용 했는데 UI나 팝업등 관리시에 쌓여 있는 State들을 일괄적으로 처리 할 수 있는 편한 방식이라 지금은 이 방식을 사용 하고 있다. 

사용 하면서도 cs 파일이 많아 지는 것은 마찬가지로 고민이 되는 부분이다. 그래도 코드가 많아 질 수록 이렇게 분리를 해주면 나중에 수정 할 때 직관적이고 재사용 할 때 용이한 부분이 있다.

 

그래도 단순 하거나 몇개 안 되는 State를 처리 할 때는 한 파일에서 데이터와 상태를 같이 변경 하고 싶을 때가 있는데 이럴 때는 코루틴을 사용 한다.

코루틴으로 FSM을 만들어 사용 하는 방법도 있긴 한데 찾다보니 사용법이 아주 간단하고 직관적인 라이브러리?가 있어서 한 파일 내에서 상태 변경을 할 때는 주로 이 FSM을 사용 한다. 

 

MonterLove Finite State Machine

 

: "Made With Monster Love"라는 인디게임사의  Peter Cardwell-Gardner (TheFuntastic) 라는 사람이 올려 놓은 github에 프로젝트가 올라와 있는데 네임스페이가 MonsterLove라고 되어 있어서 이렇게 부르는데 딱히 라이브러리 이름은 없는 듯.

TheFuntastic's Finite State Machine 혹은 Simple Finite State Machine 이렇게 불러야 하나.

GitHub 링크: https://github.com/thefuntastic/Unity3d-Finite-State-Machine

 

사용법은 깃허브에도 나와있지만 아래와 같이 enum으로 상태를 분리 정의해주면 그 상태이름뒤에 _Enter, _Update, _Exit, _Finally 등등 내장된 메소드가 실행 되는 방식으로 Init_Enter와 같이 메소드를 구현 해서 사용 하면 된다.

 

using MonsterLove.StateMachine; //1. Remember the using statement

public class MyGameplayScript : MonoBehaviour
{
    public enum States
    {
        Init, 
        Play, 
        Win, 
        Lose
    }
    
    StateMachine<States> fsm;
    
    void Awake(){
        fsm = new StateMachine<States>(this); //2. The main bit of "magic". 

        fsm.ChangeState(States.Init); //3. Easily trigger state transitions
    }

    void Init_Enter()
    {
        Debug.Log("Ready");
    }

    void Play_Enter()
    {      
        Debug.Log("Spawning Player");    
    }

    void Play_FixedUpdate()
    {
        Debug.Log("Doing Physics stuff");
    }

    void Play_Update()
    {
        if(player.health <= 0)
        {
            fsm.ChangeState(States.Lose); //3. Easily trigger state transitions
        }
    }

    void Play_Exit()
    {
        Debug.Log("Despawning Player");    
    }

    void Win_Enter()
    {
        Debug.Log("Game Over - you won!");
    }

    void Lose_Enter()
    {
        Debug.Log("Game Over - you lost!");
    }

}

 

내장되어 있는 이벤트 외에 커스텀으로 호출 하고 싶은 이벤트를 사용 할 때는

public class Driver
{
    StateEvent Update;
    StateEvent<Collision> OnCollisionEnter; 
    StateEvent<int> OnHealthPickup;
}

와 같이 추가로 정의 해서 사용 할 수도 있다. 

void OnHealthPickup(int health)
{
    fsm.Driver.OnHealthPickup.Invoke();
}

 

또한, _Enter와 _Exit은 메소드는 코루틴도 지원 한다. 

IEnumerator Play_Enter()
{
    yield return new WaitForSeconds(1);
    
    Debug.Log("Start");    
}


IEnumerator Play_Exit()
{
    yield return new WaitForSeconds(1);
}

 

 

반응형