상태패턴
 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;
    }
}



Posted by @히테
,