본문 바로가기

Unity/디자인 패턴

FSM (Finite-State Machine)

FSM(Finite-State Machine) 이란?

- 주어지는 모든 시간에서 처해 있을 수 있는 유한 개의 상태를 가지고 주어지는 입력에 따라 어떤 상태에서 다른 상태로 전환시키거나 출력이나 액션이 일어나게 하는 장치 또는 그런 장치를 나타내는 모델입니다.

- 유한 상태 기계(FSM) 바탕에 깔린 아이디어는 객체의 행동을 쉽게 처리할 수 있는 덩어리 또는 상태들로 분해하는 것.

    ㄴ 플레이어, 몬스터, NPC와 같은 캐릭터의 행동 "Idle(대기)", "Walk(걷기)" 등.. 과 같이 쉽게 처리할 수 있는

         상태 단위로 관리하는 것입니다.

 

게임에서의 FSM

-플레이어, 몬스터, NPC와 같은 개인 단위의 행동 관리를 합니다.

    ㄴ 지정된 위치로 이동하기, 목표를 공격하기, 도망가기, 협공하기 등..

 

Simple FSM 구현

- 유한 상태 기계(FSM)를 구현하기 위한 다양한 방식들이 있습니다.

- 가장 쉽게 떠오르는 방법으로는 일련의 if-then 문장을 사용하는 방법입니다. 또는 switch-case 문장과 같이 조금 더 깔끔한 구조를 사용하는 방법이 있습니다.

- 상태들을 표현하기 위해 열거형(enum)을 사용합니다.

    ㄴ 제가 사용한 예시) public enum PlayerState { Idle = 0, Walk, Run, Attack };

 

if-then, switch-case를 이용한 FSM의 장·단점

- 장점

    ㄴ 구현하는 알고리즘이 단순하기 때문에 구현하기 쉽다는 점이 있습니다.

- 단점

    ㄴ 어떤 상태로 들어갈 때와 나갈 때의 행동 정의가 어려워집니다. (코드가 복잡해짐)

    ㄴ 상태의 개수가 늘어남에 따라 코드의 복잡도가 증가합니다. (수정이 어려워짐)

 

- 유니티에서 제공하는 Coroutine을 활용하면 Coroutine 내부에서 무한 루프를 기준으로 현재 상태로 진입할 때 1회 현재 상태에서 매 프레임 현재 상태가 종료될 때 1회 호출하는 행동을 if-then보다 쉽고 유연하게 정의할 수 있습니다.

private IEnumerator Idle()
{
    // 상태로 진입할 때 1회 호출하는 내용
    Debug.Log("비전투 모드로 변경");
    
    // 상태가 업데이트 되는 동안 매 프레임 호출하는 내용
    while (true)
    {
    	Debug.Log("플레이어가 제자리에서 대기중입니다.");
        yield return null;
    }
}

private void ChangeState(PlayerState newState)
{
	// 이전 상태의 코루틴 종료
    StopCoroutine(playerState.ToString());
    
    // 새로운 상태로 변경
    playerState = newState;
    
    // 현재 상태의 코루틴 실행
    StartCoroutine(playerState.ToString()); // 현재 상태를 문자열로 변경하여 코루틴 실행.
}

 

 

더 나은 FSM

- Agent 와 상태의 분리

    ㄴ 각 상태를 Agent 내부(클래스)에 작성하는 것이 아닌 Agent 외부에 존재하는 별개의 클래스로 작성

    ㄴ 더 명확하고 유연한 구조를 만들 수 있으며, 앞에 나왔던 if-then/switch 구조보다 스파게티화 될

         가능성이 훨씬 적습니다.

    ㄴ 동일한 상태를 이용하는 다른 Agent가 있을 경우 코드의 재사용성을 줄여줍니다.

- 내장된 규칙들

    ㄴ " 각 상태들 자신의 내부에서 상태 전환을 위한 규칙(상태)"들을 내장시키는 방법입니다.

    ㄴ 예를 들어) "적의 상태를 파악해라(상태)" 상태는 "적의 체력, 장비 등의 상태를 감지하고 이길 수 있다고 판단(규칙)"           하면 "적을 공격하라(상태)" 상태로 교체합니다.

    ㄴ 각 상태는 다른 상태도 존재하고 있음을 알고 있지만, 자신의 상태를 다른 상태로 변경할지를 외부 논리가 아닌 자신           스스로 판단하여 교체합니다.

            ㄴ 이에 따라, 상태를 추가하거나(전투 시 팀원들을 불러 팀플레이를 유도)

                 완전히 다른 규칙 세트를 위한 명령 전체를 바꾸는 것도 쉽게 할 수 있습니다.

            ㄴ Agent의 알고리즘을 수정할 필요도 없고, 명령 몇 개만 수정하면 됩니다.

    ㄴ 상태들은 객체로 캡슐화되어 있고, 상태 전환을 쉽게 하는데 요구되는 논리들이 포함하고 있습니다.

    ㄴ 또한 모든 상태 객체들은 공통의 접속(기반 클래스)를 공유합니다.

public class State
{
    public virtual void Execute()
    {
    	...
    }
    
    ...
}

 

단계별 FSM

1단계. if-then / switch-case를 이용한 아주 단순한 FSM 구현

- 장점

    ㄴ 구현하는 알고리즘이 단순하기 때문에 구현이 쉽습니다.

- 단점

    ㄴ 어떤 상태로 들어갈 때와 나갈 때의 행동 정의가 어려워집니다. (코드가 복잡해짐)

    ㄴ 상태의 개수가 늘어남에 따라 코드의 복잡도가 증가합니다. (수정이 어려워짐)

 

2단계. 하나의 Agent가 사용할 수 있는 FSM 구현

- Agent와 상태의 분리, 내장된 규칙들을 적용합니다.

    ㄴ 클래스의 상속과 Up-Casting을 적용.

        ㄴ Agent의 모든 상태들이 상속받는 기반 클래스 State Class를 구현 합니다.

        ㄴ Agent는 State Class 타입의 멤버 변수를 가지고 있으며,

             해당 변수에 현재 상태를 저장하고, 업데이트하여 상태를 재생합니다.

- 장점

    ㄴ 상태의 개수가 늘어나도 해당 상태에 대해서만 정의하면 됩니다. (상태가 외부에 있기 때문)

    ㄴ 상태로 들어갈 때와 나갈 때의 행동 정의가 가능합니다. (상태가 변경될 때 메소드 호출)

- 단점

    ㄴ 현재까지는 한 종류의 Agent에 대해서만 사용 가능한 FSM이기 때문에 다른 종류의 Agent가 생기면 동일한 코드를

         또 작성해야 하는 번거로움이 있습니다. (코드의 재사용 X)

    ㄴ 코드를 이해하고 구현하는 것이 조금 어려워졌습니다.

 

3단계. 여러 에이전트가 사용할 수 있는 FSM 구현

- 재사용 가능한 State Class 구현 (일반화 클래스 적용)

    ㄴ 2단계의 단점인 한 종류의 Agent 만 사용 가능한 FSM 보완

- 상태를 관리하고 제어하는 StateMachine Class 구현

    ㄴ 상태에 관련된 내용은 에이전트들이 동일하게 사용하기 때문에 Agent 외부에서 클래스로 만들고,

          Agent가 참조 클래스로 활용하도록 합니다.

 - 전역 상태와 상태 블립 (블립(Blip) : "일시적으로 새로운 상태를 재생하다가 이전 상태로 돌아간다"는 뜻)

    ㄴ 현재 상태와 관계없이 계속해서 업데이트되어야 하는 전역 상태

    ㄴ 바로 직전에 했던 상태를 기억하고, 그 상태로 돌아갈 수 있는 상태 블립

- 장점

    ㄴ 상태의 개수가 늘어나도 해당 상태에 대해서만 정의하면 됩니다. (상태가 외부에 있기 때문)
    ㄴ 상태로 돌아갈 때와 나갈 때의 행동 정의가 가능합니다. (상태가 변경될 때 메소드 호출)

    ㄴ 서로 다른 형태의 여러 Agent가 사용할 때 상태 관련 코드 작성을 최소화 할 수 있습니다.

- 단점

    ㄴ 코드를 이해하고, 구현하는 것이 조금 어려워졌습니다.

 

4단계. Agent들 간의 소통을 위한 정보 전달 시스템

- 정보 구조체 작성

    ㄴ 메세지 전달을 위한 메세지 구조체 작성 (보낸 사람, 받는 사람, 시간, 내용 등..)

- 메세지 처리(송/수신)를 위한 에이전트 전화번호부 구현

    ㄴ 메세지를 보내거나 받는 Agent에 대한 정보를 가지고 있는 EntityManager Class 구현

- 메세지 전송 구현

    ㄴ메세지를 전송하는 MessageDispatcher Class 구현

    ㄴ 바로 보내는 메세지 처리

    ㄴ 시간이 지연되어 보내지는 메세지 처리

- 전송 받은 메세지를 처리

    ㄴ 메세지를 전달 받고, 읽어본 후 하게 될 행동 구현

        ㄴ StateMachine Classs 에서 HandleMessage() 메소드 구현

        ㄴ 메세지를 처리하는 Agent의 상태에 OnMessage() 메소드 구현

 

학생이라는 게임 엔티티의 상태들을 표현한 다이어그램입니다.

 

참고

https://www.youtube.com/watch?v=O_NJaHpbjaI&t=1s

 

참고한 영상들을 보고 FSM 대해 많은 이해가 됐습니다. 3D RPG 프로젝트에서도 FSM을 적용해서 Entity들을 수훨하게 관리할 수 있었습니다.

 

3D RPG 프로젝트 - FSM

https://herfs.tistory.com/8

 

 

 

'Unity > 디자인 패턴' 카테고리의 다른 글

싱글톤 패턴(Singleton Pattern)  (0) 2024.07.09