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
'Unity > 디자인 패턴' 카테고리의 다른 글
싱글톤 패턴(Singleton Pattern) (0) | 2024.07.09 |
---|