상태패턴
FSM을 이용해 디자인하는 패턴
FSM(Finite State Machine)
상태기계
- 유한한 개수의 상태를 갖을 수 있음
- 현재 상태에서 다음 상태로 전이 가능
- 진입, 현재, 퇴장으로 동작 구분
장점
구조가 정해져있어서 작업이 용이하고 일감 분배가 비교적 쉬움
단점
FSM틀을 벗어난 행위를 만들 수 없음
보완
FSM은 한가지 상태밖에 가질 수 없기 때문에 '걷는 동시에 공격한다' 등의 상태를 기술하려면 상태를 추가하면서 상태간 연결도 복잡해짐
이를 보완하기 위한 방법들을 설명한다
1. 병행상태기계 : 복수개의 상태머신 사용
- 독립된 개념을 상태로 두어 상태가 복잡해지는 것을 방지
- 개념이 추가될 때 마다 코드가 복잡해짐
- 상태코드 간 커플링 발생할 수 있음
ex)
캐릭터 이동관련 상태기계
캐릭터의 공격여부 상태기계
두개를 같이 사용! 하지만 엎드렸을 때 공격 불가 등의 로직 구현 시 커플링 발생
캐릭터 이동관련 상태기계
캐릭터의 공격여부 상태기계
두개를 같이 사용! 하지만 엎드렸을 때 공격 불가 등의 로직 구현 시 커플링 발생
2. 계층상태기계 : 상태를 상속하여 상태 만듬
- 큰 개념을 상위의 상태로 만들고 그것을 상속해서 사용
- 설계하기 복잡함
- 큰 개념을 상위의 상태로 만들고 그것을 상속해서 사용
- 설계하기 복잡함
ex)
필드 별로 다른 상태 만들기
이동, 정지, 엎드리기 등 기본 움직임을 갖는 상태 => A
A를 상속받아서 땅, 물, 하늘 등으로 분류
땅 위 이동, 물 속 정지 등 로직 구현가능!
3.푸쉬다운 오토마타 : FSM에 이전 상태에 대한 이력을 남김
이 방법은 FSM 단점 보완보단 확장에 가깝다
총을 쏜 후 버튼을 떼면 기본상태로 돌아와야하는 로직, 풀을 채집하다가 완료되면 원래 상태로 돌아와야하는 로직 등을 구현
상태를 스택으로 관리하면서 상태 관리
- push : 새로운 상태가 top이 되면서 그 상태가 현재상태가 됨
- pop : 현재 상태였던 최상위 상태를 빼면서 top이 되는 이전 상태가 현재 상태가 됨
샘플코드
FSMState.cs
public abstract class FSMState
{
public Type Key { get; private set; }
private List nextState;
public FSMState(Type key)
{
Key = key;
}
public abstract void OnEnter();
public abstract void OnStay();
public abstract void OnExit();
internal void Link(Type state)
{
if (nextState == null)
nextState = new List();
nextState.Add(state);
}
internal bool IsLinkable(Type state)
{
return !nextState.Contains(state);
}
internal void EnterPrco()
{
OnEnter();
}
internal void StayProc()
{
OnStay();
}
internal void ExitProc()
{
OnExit();
}
}
FSM.cs
public class FSM
{
private Dictionary> stateDic;
private FSMState currentState = null;
public State CurrentState
{
get
{
return currentState.Key;
}
private set
{
currentState = FindState(value);
}
}
public void ChangeState(State stateName)
{
if (currentState != null)
{
currentState.ExitProc();
}
CurrentState = stateName;
if (currentState != null)
{
currentState.EnterPrco();
}
}
public void UpdateState()
{
currentState.OnStay();
}
public bool AddState(FSMState state, bool isDefaultState = false)
{
if (stateDic == null)
{
stateDic = new Dictionary > ();
}
if (stateDic.ContainsKey(state.Key))
{
return false;
}
if (stateDic.ContainsValue(state))
{
return false;
}
stateDic.Add(state.Key, state);
if (currentState == null || isDefaultState)
{
currentState = state;
}
return true;
}
public bool Link(State from, State to)
{
if (from.Equals(to))
{
return false;
}
FSMState fromState = FindState(from);
if (fromState == null)
{
return false;
}
FSMState toState = FindState(to);
if (toState == null)
{
return false;
}
if (fromState == toState)
{
return false;
}
if (fromState.IsLinkable(to))
{
return false;
}
fromState.Link(to);
return true;
}
private FSMState FindState(State key)
{
FSMState result = null;
stateDic.TryGetValue(key, out result);
return result;
}
}