[Unity] 로봇 상태 관리(1) : Monster, MonsterStateMachine 구현
요약
몬스터는 이동, 스킬, 정지처럼 상태를 관리하기 위해서 유한 상태 기계(Finite State Machine, FSM)를 구현하였다. 구현하기 위해 IState로 상태 인터페이스를 만들고 상태 기계를 만든 후 몬스터에게 적용하였다. 적용된 상태 기계의 상태를 바꾸기 위해 예시로 RobotKnife의 상태가 변하는 과정을 설명하였다.
IState.cs
public interface IState
{
public void Entry();
public void IStateUpdate();
public bool Exit();
}
이동, 스킬, 정지와 같은 상태는 IState를 상속받아 다음과 같이 사용한다. 참고로 Exit()가 bool인 이유는 실제로 동작이 취소되는 경우 원치 않게 Exit()를 발동하는 경우를 막기 위함이다.
public class RobotIdleState : IState
public class RobotMovingState : IState
public class RobotSkillCastingState : MonoBehaviour, IState
스킬은 MonoBeHaviour를 사용해야만이 탄을 날리고 총을 돌리는 것과 같은 역할을 하기에 상속받았다. 이는 수정이 가능하지만 미숙함에 일단 상속을 받았다.
상태를 상속받은 스크립트는 이를 교환하고 교체하는 역할을 하는 MonsterStateMachine 스크립트가 필요하다.
MonsterStateMachine.cs
public class MonsterStateMachine
{
public Monster _monster;
public IState CurrentState { get; private set; }
public MonsterStateMachine(Monster monster)
{
_monster = monster;
}
public void Initialize(IState startingState)
{
CurrentState = startingState;
startingState.Entry();
}
public void TransitionTo(IState nextState)
{
if (nextState == CurrentState)
return;
if (CurrentState.Exit())
{
_monster.Flag = true;
}
CurrentState = nextState;
nextState.Entry();
}
public void MonsterStateMachineUpdate()
{
if (CurrentState != null)
{
CurrentState.IStateUpdate();
}
}
}
MonsterStateMachine의 역할은 단순히 상태가 변하면 새로운 상태의 시작 함수를 현재 상태는 끝나는 함수를 실행해 주고 새로운 상태를 현재 상태로 만들어 유지한다.
해당 스크립트를 사용하기 위해서는 Monster 스크립트에 적용해야 한다.
Monster.cs
public abstract class Monster : MonoBehaviour
{
... 생략 ...
protected MonsterStateMachine monsterStateMachine;
protected MonsterMovement monsterMovement;
public State state { get; set; } = State.Idle;
protected IState monsterSkillCastingState;
protected IState monsterMovingState;
protected IState monsterIdleState;
protected virtual void Awake()
{
monsterMovement = GetComponent<MonsterMovement>();
monsterStateMachine = new MonsterStateMachine(this);
... 생략 ...
}
Monster 스크립트에 FSM을 적용하기 위해 각종 상태와 기계를 생성하였다.
... 생략 ...
protected void Update()
{
if (!Die)
{
// 어떠한 경우에 상태 변환이 될지 모르기에 상태 변환을 가장 먼저 해야함
if (state == State.Idle)
{
monsterStateMachine.TransitionTo(monsterIdleState);
}
else if (state == State.Move)
{
monsterStateMachine.TransitionTo(monsterMovingState);
}
else if (state == State.Skill)
{
monsterStateMachine.TransitionTo(monsterSkillCastingState);
}
}
if (Die)
{
TurnPass();
}
... 생략 ...
// 몬스터의 상태변환을 만들기 위함
UpdateMonster();
// 몬스터의 상태 클래스에 연결되어있음
// ex(MonsterStateMachine -> MonsterMovingState)
monsterStateMachine.MonsterStateMachineUpdate();
}
... 생략 ...
}
UpdateMonster: 상속된 몬스터 객체가 필요한 함수를 사용하기 위해 사용monsterStateMachine.MonsterStateMachineUpdate(): 상태 스크립트에 있는 코드를 실행하기 위해 존재
몬스터의 상태는 주로 UpdateMonster에서 이루어진다.
RobotKnife.cs
protected override void UpdateMonster()
{
... 생략 ...
// 몬스터 턴이되면 한번만 작동, Authority를 통해 권한이 있을경우 움직임
if (Instance.monsterTurn && start && Authority)
{
if (MoveCount < 1)
{
monsterMovement.MonsterPathFinding(Instance.PlayerPositionInt);
}
string returnType = monsterMovement.AttackNavigation();
// 사거리 안에 있으면 스킬 아니면 움직임
if (returnType == "AttackRange")
{
if (AttackCount == 1)
{
TurnPass();
return;
}
state = State.Skill;
AttackCount++;
}
else if (returnType == "NotFindPath")
{
TurnPass();
}
else if (returnType == "FindPath")
{
if (MoveCount == 1)
{
TurnPass();
return;
}
state = State.Move;
MoveCount++;
}
start = false;
}
... 생략 ...
}
예시로 몬스터 RobotKnife를 설명하자면 monsterMovement을 통해 길을 찾게 되면 returnType을 통해 상태가 바뀌는 것을 볼 수 있다. 이러한 상태가 바뀌면 monsterStateMachine.MonsterStateMachineUpdate()에서 설정된 상태가 실행된다.