Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

성장하는 개발자의 학습 노트

State Pattern (FSM) 본문

디자인패턴

State Pattern (FSM)

재밌는게임~ 2024. 5. 1. 18:10

State Parttern의 대표적은 FSM (Finite State Machine) : 유한상태머신이 있다.

 

FSM의 정의는 유한한 갯수의 상태들로 구성된 기계를 말한다. 특정 객체의 상태를 관리하기 위한 패턴으로

이벤트를 받고 그에 따라 현재상태에서 다음상태로 전이된다.

 

FSM(유한상태기계) 말 그대로 유한한 상태를 가지는 기계를 말하며, 구현을 통한 그 결과물이라고 정리하는 것이 맞을 것이다. 이 유한 상태기계를 구현하기 위해 State Pattern을 사용해서 구현하거나, 또는 다양한 방법으로 구현할 수 있을 것이다.

 

게임에서의 FSM의 활용은 플레이어, 몬스터, NPC 등 상태가 계속해서 바뀔 수 있는 모든 객체에서 사용이 가능하다.

 

이렇게만 들었을 때는 이게 어떤 것인지 생각할 수 있다. 그럼 쉽게 그림으로 보여주겠다.

 

게임으로 예를 들면 몬스터라는 객체가 있다. 몬스터는 플레이어의 행동에 따라서 상태가 달라진다. 

몬스터는 어떤 상태가 있을까? 아래 그림 처럼 생성되었을 때, 이동, 죽음, 공격, 공격받음 이정도의 상태가 있을 것이다.

그러면 이러한 상태를 가지고 있는 몬스터는 어떻게 행동을 할까?

간단하게 몬스터가 생성된 이후 플레이어를 쫓아오면서 공격하는 몬스터의 상태를 만들어 보겠다.

위 그림처럼 이렇게 될 것이다.

그러면 이러한 형태들을 FSM을 사용하지 않고 코드를 작성하면 어떻게 될까?

 

public enum STATE{

     IDLE,

     MOVE,

     DIE,

     ATTACK,

     ATTACKED

}

public class Monster

{

   STATE state = new STATE;

   void Start(){

        this.state = state.IDLE;

   }

   void Update(){

        State(state);     

   }

 

   void State(STATE state){

        switch(state){

             case state.IDLE:

                     //가만히 있는다.

                     break;

             case state.MOVE:

                     //움직인다.

                     break;

               ...

               ...

               ...

        }

   }

 

간단하게 이러한 형태가 될 수 있을 것이다. 그러면 이렇게 개발을 하였는데 기획자가 상태를 더 추가해주시고, 뭐 더 해주시고, 블라블라블라. ... 이러한 상황이 나올 수 있을 것이다. 기획은 언제든지 바뀔 수 있기 때문이다. 그러면 우리는 작성한 코드를 다시 바꿔야하는 상황이 올 것이다. 그랬을 때 이러한 코드는 변경 및 추가에 대해서 되게 안좋은 코드이다. 그러면 FSM을 활용한 코드를 한번 보겠다. 

 

public class Monster{

     StateMachine stateMachine0 = new StateMachine();

     

     void Start(){

          AddState() ; 

     } 

    void AddState()
    {
        StateMachine1 = new StateMachine1<Base>(this, new Idle());
        StateMachine1.AddState(new Attack());
        StateMachine1.AddState(new Move());
    }

}

 

public abstract class State1<T>
{
    protected StateMachine1<T> StateMachine1;
    protected T context;

    public void SetMachineAndContext(StateMachine1<T> stateMachine1, T context)
    {
        this.StateMachine1 = stateMachine1;
        this.context = context;
        
        OnInitialized();
    }

    public virtual void OnInitialized()
    {
    }

    public virtual void OnEnter()
    {
    }

    public abstract void Update(float deltaTime);

    public virtual void OnExit()
    {
    }
}
public class StateMachine1<T>
{
    private T _context;

    private State1<T> _currentState1;

    private Dictionary<Type, State1<T>> _states = new();

    public StateMachine1(T context, State1<T> initState1)
    {
        this._context = _context;
        
        AddState(initState1);
        _currentState1 = initState1;
        _currentState1.OnEnter();
    }

    public void AddState(State1<T> state1)
    {
        state1.SetMachineAndContext(this, _context);
        _states[state1.GetType()] = state1;
    }

    public R ChangeState<R>() where R : State1<T>
    {
        var newType = typeof(R);
        if (_currentState1.GetType() == newType)
        {
            return _currentState1 as R;
        }

        if (_currentState1 != null)
        {
            _currentState1.OnExit();
        }

        _currentState1 = _states[newType];
        _currentState1.OnEnter();
        
        return _currentState1 as R;
    }

    public void Update(float detaTime)
    {
        _currentState1.Update(detaTime);
    }
}

 

//---------------------------------------------------------state-------------------------------------

public class Attack : State1<Base>
{
    
    public override void OnInitialized()
    {
            
    }
    
    public override void OnEnter()
    {
        Debug.Log("Attack");
    }

    public override void Update(float deltaTime)
    {
        StateMachine1.ChangeState<Idle>();
        return;
    }

    public override void OnExit()
    {
        context.IsAttack = false;
    }
}

public class Move : State1<Base>{

         //이동

}

public class Idle : State1<Base>{

         //생성직후

}

 

간단하게 이러한 느낌으로 될 것이다. 

코드상으로는 처음 접했을 때는 더 복잡해 진 것이 아닌가? 라는 생각이 들 수 있다. 하지만 어떤 것에 대한 수정이나 추가에 대해서 쉽고 빠르게 가능해졌기 때문에 훨씬 좋은 코드가 된 것이다. 

 

이렇게 FSM을 알아보았다. 

! 이것보다 더 효율적으로 작성할 수 도 있겠지만 그러한 부분에 대해서는 글쓴이도 조금 더 알아봐야 하기 때문에 여기서 마치겠다....

 

 

728x90

'디자인패턴' 카테고리의 다른 글

Observer Parttern  (0) 2024.04.30
Strategy Pattern  (0) 2024.04.27
Singleton Pattern  (0) 2024.04.27