상태 패턴하면 가장 먼저 떠오르는게 FSM 유한 상태 기계가 가장 유명하고
또 다른 종류로 계층형 상태 기계와 푸시다운 오토마타 등등 존재 한다.
나는 이패턴을 처음 배우고 한동안 이걸 이용해서 플레이어와 몬스터 등등
다 FSM 패턴을 이용해서 구현할 정도로 너무 좋고 이해가 편한 패턴 이였다.
코딩을 하다 생각나는 예시를 하나 가지고 왔다.
캐릭터가 점프를 한다고 가정해보자.
update문 안에 점프키 눌렀을때 점프를 하게 if문을 하나 둘것이다
그런데 버그가 하나 있다 계속 누르면 계속 점프한다.
그러면 보통 bool 변수를 하나 둬서 isJump 같은 확인값을 확인해서
점프중인지 체크 할것이다.
그러고 땅에서 아래키 누르면 엎드리기 기능을 추가 해보자.
점프중이 아니라면 엎드리기 if 를 또 추가 한다.
이래도 버그가 하나 있다 엎드리기 한상태에서 점프를 하고 아래키 때면 점프인데
그냥 서있는 상태가 될꺼다.
그러면 또 플래그 변수 bool isGround를 추가 한다.
그러고 내려찍기 하는 공중공격을 만들어보자
또 플래그 변수를 넣을까..?
점점 뭔가 산으로 가는거같다.
다시 구조를 짜보자
일단 플레이어가 취해야할 상태를 정의 해보자.
이런 구조가 될꺼다 이거 FSM 유한 상태 기계 패턴이다.
이 FSM은 오토마타 이론에서 나왔다.
예시로 서있는동안 아래 버튼 누르면 엎드리기 상태로 전이한다.
점프하는동안 아래 버튼 누르면 내려찍기로 전이 한다. 전이가 없으면 무시한다.
보통 여러 플래그 변수 중에서 하나만 참일경우가 많다면 enum 열거형을 많이 쓴다.
그래서 이 상태들을 전부 State로 표현할수있다.
enum State
{
STATE_STANDING,
STATE_JUMP,
STATE_DUCKING,
STATE_DIVING
}
이런 방향으로 만들어질것이다.
그래서 이제 플래그 변수를 State로 하나 두고 값을 교체하고 필요시 비교 할땐 State값으로
비교하면 된다.
분기 순서도 바꿀수있다.
if로하면 입력에 따라 먼저 발동 하는순서도있는데
이젠 상태관련 코드를 한곳으로 모아둬서 해보자.
void HandleInput()
{
switch (state_)
{
case State.STATE_STANDING:
if (Input.GetKeyDown(KeyCode.B))
{
state_ = State.STATE_JUMP;
// ...
}
else if (Input.GetKeyDown(KeyCode.DownArrow))
{
state_ = State.STATE_DUCKING;
// ...
}
break;
case State.STATE_JUMP:
if (Input.GetKeyDown(KeyCode.DownArrow))
{
state_ = State.STATE_DIVING;
// ...
}
break;
case State.STATE_DUCKING:
if (Input.GetKeyUp(KeyCode.DownArrow))
{
state_ = State.STATE_STANDING;
// ...
}
break;
case State.STATE_DIVING:
//...
break;
}
}
이런 방향으로 조절 할수가 있다.
이런 패턴은 단점이 하나 있는데
상태를 점점 늘려가면 해야할 상태들이 너무 많아 진다.
그래도 구조로 짜서 추가하면 대응이 가능하지만 여기서 변형을 약간 준다면 개선이 가능하다.
만약에 캐릭터가 총을 들수있고 저런 행동 중에 총을 쏠수 있어야한다 라는 구조를 가지게 된다면?
모든 상태에 + 총든 버전, 총안든 버전 으로 * 2가 상태가 증가해버린다.
무기가 많아 지면 점점 증가 수치가 커지고 사실 무기 들었다 아니다 빼면 캐릭터 행동은 다 똑같다.
즉 코드에 중복이 늘어나는것이다.
이런 경우에는 무었을 하는가에 상태는 그대로 두고 뭘 들고있는지 상태를 따로 정의해서
2가지에 상태 기계를 두는것이다.
그리고 그 2가지를 각각 참조해서 엎드린 상태에 무기는 총을 장착 등등 두면 된다.
입력을 받을때도 양쪽다 입력값을 전달해주면 된다.
public class TestP : MonoBehaviour
{
enum State
{
STATE_STANDING,
STATE_JUMP,
STATE_DUCKING,
STATE_DIVING
}
State state_;
enum WeaponState
{
STATE_HAND,
STATE_GUN,
STATE_SWORD
}
WeaponState weaponState_;
}
이런 방향으로 설계하고 State를 따라서 하면 된다.
각각 상태에 따라서 입력값으로 각자 행동하고 상태를 바꿀수있다 서로가 전혀 상관 없으면 이방법이 좋다.
하지만 점프중에 총을 못쏘거나 등등 서로가 서로를 상호작용 해야할때는 서로 코드에서 다른 기계에 상태를 물어보는 코드를 점점 추가할것이다.
조금 지저분해질수도 있는데 해결은 가능한 문제이다.
캐릭터의 동작을 계속해서 구현을 하다보면 비슷한 동작을 구현할때가 많다 걷기, 달리기, 미끄러지기 등등...
이런 상태에서 모두 점프키를 누르면 점프 하고 아래키 누르면 엎드린다고 한다면
단순 상태 기계 구현에서는 중복코드로 넣어야한다.
객체지향 코드라고 생각해보면 상속으로 여러 상태가 공유해서 사용할수있을까 고민 하게된다.
점프와 엎드리기는 무조건 땅위에있는 상태 클래스를 만들어서 상속 받아서 구현할수있을까 고민하게된다.
이런 구조를 계층형 상태 기계라고 한다.
상위 상태 또는 하위 상태로 나뉘고 이벤트가 들어올때 하위에서 구현을 안했으면 상위 상태로 넘어가서 발동한다.
메서드를 오버라이드 한것과 같다.
최상위 계층으로 PlayerState를 상속받아서 GraoundState로 상속받고 거기서 이게 입력값을 따라서
각각 행동에 맞게 구현 하면 된다.
상태 스택을 이용해서 FSM을 확장 하는 방식도 있다. 계층형에서 보는거랑은 조금 다르다.
기본적으로 FSM 패턴에는 이력의 개념이 따로 없다.
현재 내 상태는 알수있지만 이전에 무슨 상태였는지는 따로 저장하지 않는 이상 알수없다.
예시로 플레이어가 스킬로 모든 무기를 난사하고 다시 이전에 무기로 돌아간다는 스킬이있다.
State 를 하나 만들어서 구현후에 이제 돌아가야하는 부분에서 구현이 하는게 고민된다.
일반적인 FSM에서는 이전 상태를 알수가 없다.
이럴떄 사용할만한게 푸시다운 오토마타이다.
FSM은 한 개의 상태를 포인터롤 관리 했다면 이번에는 상태를 스택으로 관리한다.
스택으로 관리 하게 된다면 부가적인 명령 2가지 있다.
새로운 상태를 스택에 넣으면 최상이 스택이 현재 상태가 된다.
그리고 최상위 상태를 빼면 이전에 있던 스택 최상위가 현재 상태 즉 돌아갈수있다.
FSM에 몇가지 확장 버전도 알아봤다.
하지만 개발하면서 FSM 에도 한계가 있다.
요즘은 간단한 구조라면 상관 없지만 ai들이 고도화 된 상태라서 행동트리나 계획시스템을 사용하기도한다
그래도 내부 상태에 따라서 객체 동작이 바뀔때
상태가 적고 분명하게 구분 가능할때
객체가 입력이나 이벤트에 따라서 반응할때
이때는 FSM도 충분히 사용하면 좋다.
물론 AI 뿐만 아니라 메뉴전환, 네트워크, 비동기 동작을 구현 하는데도 사용한다.
한번 Fsm패턴 구현해보는것도 좋을꺼같다.