FSM 개념 자체는 다른 블로그 및 영상으로 너무 잘 정리되어 있기 때문에 따로 정리하거나 하진 않겠습니다.
InGame Flow FSM이란? 게임에 진입 했을 때, 게임 런타임 도중, 게임이 종료될 때를 FSM으로 구현하는 것입니다. 방법 자체는 기존에 쓰던 FSM이랑 크게 다를 것이 없지만 먼저 코드를 예시를 봅시당
InGame Flow FSM에 관한 글인데 뜬금 State Pattern이 나와서 당황할 수 있습니다. 하지만 InGame Flow FSM은 State Pattern을 전제로 구현했기 때문에 꼭 짚고 넘어가야 합니다.
일명 상태 패턴이라 불리는 정의를 내리면 아래와 같습니다.
"객체의 내부 상태가 변경될 때 해당 객체가 그의 행동을 변경할 수 있도록 하는
행동 디자인 패턴입니다."
여기서 가져갈 키워드는 '객체의 내부 상태'와 '행동'입니다. 해당 글에서는 '객체의 내부 상태'를 InGame에, '행동'은 InGame에 시작 전, 중, 후 구분됩니다.
상태 패턴의 예시는 다른 포스팅으로 대체할 생각입니다. 주제와 벗어난 얘기이지만, 어떻게 사용되는지 알아야 아래의 내용이 더 와닿기 때문입니다.
FSM은 In-Game 외에도 여러 사용될 우려가 많이 때문에 제네릭으로 구현했습니다.
IState.cs
public interface IState<T>
{
public void Begin(T owner);
public void Runtime(T owner);
public void End(T owner);
}
해당 코드를 interface로 선언하여 interface를 상속받는 클래스의 인스턴스를 만드는 것이 가능하게 하였습니다. 선언부를 보면 in-game의 Begin/End 함수가 Node을 역할을 합니다.
또한, generic을 사용하여 'in-game FSM' 외에도 활용할 수 있게 하였습니다.
Q1, 다른 곳 어디에 사용되나요?
NPC FSM, Enemy FSM 등등 사용됩니다.
Q2, 앞글자에 I가 붙는 이유가 특별히 있나요?
A2, interface 앞에 I를 붙이는 것이 C# 관례입니다.
StageBeginState.cs
public class StageBeginState : IState<UIInGameView>
{
public void Begin(UIInGameView owner)
{
owner.StartCoroutine(owner.StartStage());
}
public void End(UIInGameView owner)
{
owner.ShowQuestion();
}
public void Runtime(UIInGameView owner)
{
}
}
StageRuntimeState.cs
public class StageRuntimeState : IState<UIInGameView>
{
public void Begin(UIInGameView owner)
{
Debug.Log("StageRuntimeState Begin");
}
public void End(UIInGameView owner)
{
Debug.Log("StageRuntimeState End");
}
public void Runtime(UIInGameView owner)
{
Debug.Log("StageRuntimeState Runtime");
owner.GameTime();
}
}
StageEndState.cs
public class StageEndState : IState<UIInGameView>
{
public void Begin(UIInGameView owner)
{
if(GameManager.Instance.IsStageClear)
owner.StartCoroutine(owner.ClearStage());
else
owner.StartCoroutine(owner.GameOverStage());
}
public void End(UIInGameView owner)
{
Debug.Log("StageEndState End");
}
public void Runtime(UIInGameView owner)
{
Debug.Log("StageEndState Runtime");
}
}
위에 3개의 Code는 interface를 상속받은 코드를 구현해준 것입니다.
Q1, Delegate 방식이 아닌 FSM으로 구현한 이유가 있을까요?
게임 자체가 단순하고, 복잡하지 않다면 Delegate로 구현해도 큰 문제가 없습니다. 하지만 FSM을 활용하는 것이 코드 재활용이 좋다 생각합니다.
[Delegate 방식]
public void delegate GameStart();
public void delegate GameRunTime();
public void delegate GameEnd();
StateMachine.cs
public class StateMachine<T>
{
public IState<T> CurrentState { get; set; }
public IState<T> PreviousState { get; set; }
public T CurOwner { get; set; }
public void ChangeState(IState<T> newState, T owner)
{
CurOwner = owner;
CurrentState?.End(owner);
PreviousState = CurrentState;
CurrentState = newState;
CurrentState.Begin(owner);
}
public void UpdateRuntimeState()
{
CurrentState?.Runtime(CurOwner);
}
}
StateMachine을 만들어 현재 상태를 관리할 수 있게 합니다.
Q1, '?'의 의미는 무엇인가요?
GameManager.cs
public class GameManager : Singleton<GameManager>
{
public StageBeginState BeginState { get; set; } = new StageBeginState();
public StageRuntimeState RunningState { get; set; } = new StageRuntimeState();
public StageEndState EndState { get; set; } = new StageEndState();
}
StageState는 게임 로직의 주기이기에 GameManager가 책임지고, UI는 절대 소유하지 않습니다.
UIInGameView.cs
public class UIInGameView : UIVIew
{
public StateMachine stateMachine<UIInGameView>();
}
UI는 오직 Show / Director / Hide을 담당하기에 신호만 보내주는 역할을 합니다.
실제 프로젝트에 사용하게 될 경우 프로그래밍 이전에 현재 프로젝트가 어떤 Scene이 있고, In-Game이 어떻게 진행되는지 먼저 생각해보며 적용하였음 좋겠습니다.
Enemy, NPC AI의 경우 FSM을 대신하여 BT를 사용하는 경우도 있습니다. 저의 생각을 제시하면 프로젝트가 작을 경우 FSM으로 처리해도 유지보수엔 큰 문제없다고 생각됩니다.
BT의 경우 Tree의 자료구조 성격이 강하기 때문에 에셋도 구매해보고, 직접 구현해보는 것도 좋은 학습 방향이 될 것 같습니다.