[Unity] FSM Component 만들기

Min·2024년 8월 11일

Project일지

목록 보기
7/8

FSM

유한상태기계...라며 말을 어렵지만 사실 별거 아니다.
FSM의 가장 핵심적인 개념은 동시에 두 가지 이상의 상태를 가질 수 없다는 것이다. FSM을 사용함에 가장 큰 이점은, 게임 오브젝트가 가질 수 있는 상태들을 각각 독립적으로 구현하고, 강제적으로 한번에 한가지 상태만 업데이트 하도록 구현하기 때문에, 상태의 충돌로 인한 오류가 일어나지 않는다.

구현

  • State
    State는 객체가 가질 수 있는 상태의 단위이다.
    해당 State의 이름, State가 시작할때 해야 할 일(public Action Start),
    State상태일 때 매 프레임 해야 하는 일(public Action Update), State가 끝날 때 해야할 일(public Action End)로 구성되어있다.
  • 상태를 관리할 자료구조
    State는 Dictionary로 관리하고, State의 이름은 Enum으로 정해줄 생각이기 때문에, Key Value는 int로 한다. 어떤 오브젝트의 현재 상태는 State CurState로 표현하며, CurState의 Update는 SC_FSM의 Update에서 실행한다.

주요 인터페이스

  • CreateState
    오브젝트의 스테이트를 정의하는 메서드. State의 이름이 서로 다른 Enum으로 정의될 수 있기 때문에 제너릭 문법을 사용
  • ChangeState
    오브젝트의 상태를 바꿔주는 메서드, State의 이름이 서로 다른 Enum으로 정의될 수 있기 때문에 제너릭 문법을 사용. 이전 상태의 End함수 호출 -> 현재 상태의 Start함수 호출 순서로 일어난다.

결과

FSM을 모듈화 하였다. FSM을 사용할 오브젝트는 SC_FSM을 컴포넌트로 가지기만 한다면 해당 기능을 사용할 수 있다. 앞으로 Fighter나 Monster같은 오브젝트를 만들 때 사용하게 될 예정이다.

코드

using System;
using System.Collections.Generic;
using UnityEngine;

public class SC_FSM : MonoBehaviour
{
    public class State
    {
        public int EnumValue;
        public System.Action Start;
        public System.Action Update;
        public System.Action End;
    };

    // Update is called once per frame
    public void Update()
    {
        if (CurState == null)
        {
            Debug.LogAssertion("CurState Is null. Use ChangeState Before Update FSM.");
            return;
        }

        CurState.Update();
    }

    public void CreateState(int Value, System.Action StateStart, System.Action StateUpdate, System.Action StateEnd)
    {
        if (FindState(Value) != null)
        {
            return;
        }

        AllState.Add(Value, new SC_FSM.State());
        SC_FSM.State Temp = AllState[Value];
        Temp.EnumValue = Value;
        Temp.Start = StateStart;
        Temp.Update = StateUpdate;
        Temp.End = StateEnd;
    }
    public void CreateState<EnumT>(EnumT Value, System.Action StateStart, System.Action StateUpdate, System.Action StateEnd)
    {
        CreateState(Convert.ToInt32(Value), StateStart, StateUpdate, StateEnd);
    }

    public void ChangeState(int Value)
    {
        if (null != CurState)
        {
            if (null != CurState.End)
            {
                CurState.End();
            }
        }

        CurState = FindState(Value);

        if (CurState == null)
        {
            return;
        }

        CurState.Start();
    }
    public void ChangeState<EnumT>(EnumT Value)
    {
        ChangeState(Convert.ToInt32(Value));
    }


    private State FindState(int Value)
    {
        if (AllState.ContainsKey(Value))
        {
            return AllState[Value];
        }

        return null;
    }

    public int GetCurState()
    {
        return CurState.EnumValue;
    }
    public EnumT GetCurState<EnumT>()
    {
        if (CurState.EnumValue is EnumT variable)
        {
            return variable;
        }

        throw new InvalidCastException($"Cannot cast {CurState.EnumValue} to {typeof(EnumT)}");
    }

    private Dictionary<int, State> AllState = new Dictionary<int, State>();
    private State CurState;
}
profile
티내는 청년

0개의 댓글