유한상태기계...라며 말을 어렵지만 사실 별거 아니다.
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;
}