FSM: 유한 상태 기계(Finite-State Machine)
유한 상태 기계는 주어지는 모든 시간에서 처해 있을 수 있는 유한개의 상태를 가지고 주어진 입력에 따라 어떤 상태에서 다른 상태로 전환하거나 출력이나 액션이 일어나게 하는 장치 또는 장치를 나타낸 모델
상태(State): 게임에 정의된 여러 동작을 낸다
- Idle, Walk, Run...
- 한 상태에서 다른 상태로 전이할 수 있고 동시에 여러 상태를 실행할 수 없다.
전이(Transition): 한 상태에서 다른 상태로 변화 하는것
- 각 상태 로직 또는 외부에서 전이 조건에 의해 전이될 수 있다.
FSM을 간단하게 사용해 플레이어를 움직일 수 있게 하는 코드를 작성했습니다.
구현 코드
BaseState
모든 상태의 부모 클래스
public abstract class BaseState
{
/// <summary> 다른상태에서 이 상태로 변화될때 한번 실행 </summary>
public abstract void OnEnter();
/// <summary> Update()에 추가하여 반복 실행 시키는 함수 </summary>
public abstract void OnUpdate();
/// <summary> FixedUpdate()에 추가하여 반복 실행 시키는 함수 </summary>
public abstract void OnFixedUpdate();
/// <summary> 이 상태에서 다른 상태로 변화할때 한번 실행 </summary>
public abstract void OnExit();
}
PlayerState
BaseState를 상속받으며 플레이어의 상태 클래스의 부모 클래스
만약 Monster의상태를 만들고자 한다면 BaseState에서 상속받은 MonsterState를 아래와 같은 방식으로 작성하면된다.
/// <summary> 플레이어 상태의 부모 클래스 </summary>
public abstract class PlayerState : BaseState
{
protected Player _player;
protected PlayerStateMachine _machine;
public PlayerState(Player player, PlayerStateMachine machine)
{
_player = player;
_machine = machine;
}
}
P_IdleState
플레이어가 대기 상태일때 실행되는 함수를 정리한 클래스
/// <summary> 플레이어 대기 상태 정보를 가지고 있는 클래스 </summary>
public class P_IdleState : PlayerState
{
public P_IdleState(Player player, PlayerStateMachine machine) : base(player, machine)
{
}
/// <summary> 현재 상태로 전이시 한번 실행 </summary>
public override void OnEnter()
{
//만약 애니메이션이 있다면 이쪽에 애니메이션 전환 코드를 작성
}
/// <summary> 다음 상태로 전이시 한번 실행 </summary>
public override void OnExit()
{
//만약 애니메이션이 있다면 이쪽에 애니메이션 전환 코드를 작성
}
/// <summary> FixedUpdate()에 추가하여 반복 실행 시키는 함수 </summary>
public override void OnFixedUpdate()
{
}
/// <summary> Update()에 추가하여 반복 실행 시키는 함수 </summary>
public override void OnUpdate()
{
//플레이어를 움직이게하는 함수를 실행하고
//만약 플레이어가 키를 누르고 있으면 참, 아니면 거짓 반환
//움직이고 있는 상태이면 이동 상태로 전환 한다.
if (_player.Move())
_machine.OnStateChange(_machine.RunState);
}
}
P_RunState
플레이어가 이동 상태일때 실행되는 함수를 정리한 클래스
public class P_RunState : PlayerState
{
public P_RunState(Player player, PlayerStateMachine machine) : base(player, machine)
{
}
/// <summary> 현재 상태로 전이시 한번 실행 </summary>
public override void OnEnter()
{
//만약 애니메이션이 있다면 이쪽에 애니메이션 전환 코드를 작성
}
/// <summary> 다음 상태로 전이시 한번 실행 </summary>
public override void OnExit()
{
//만약 애니메이션이 있다면 이쪽에 애니메이션 전환 코드를 작성
}
/// <summary> FixedUpdate()에 추가하여 반복 실행 시키는 함수 </summary>
public override void OnFixedUpdate()
{
}
/// <summary> Update()에 추가하여 반복 실행 시키는 함수 </summary>
public override void OnUpdate()
{
//플레이어를 움직이게하는 함수를 실행하고
//만약 플레이어가 키를 누르고 있으면 참, 아니면 거짓 반환
//움직이지 않는 상태이면 대기 상태로 전환 한다.
if (!_player.Move())
_machine.OnStateChange(_machine.IdleState);
}
}
StateMachine
모든 상태 머신의 부모 클래스
public abstract class StateMachine
{
/// <summary> FixedUpdate()에 추가하여 반복 실행 시키는 함수 </summary>
public abstract void OnStateUpdate();
/// <summary> Update()에 추가하여 반복 실행 시키는 함수 </summary>
public abstract void OnStateFixedUpdate();
}
PlayerStateMachine
StateMachine를 상속받으며 플레이어의 상태 확인 및 전이시키는 역할을 하는 클래스
만약 Monster상태머신을 만들고자 한다면 아래와 비슷한 방식으로 코드를 작성하면 되겠다.
using UnityEngine;
public class PlayerStateMachine : StateMachine
{
protected Player _player;
//유한상태머신 변수들
//==========================================================================
/// <summary> 현재 상태를 저장 </summary>
public BaseState CurrentState { get; private set; }
/// <summary> 대기 상태 </summary>
public BaseState IdleState { get; private set; }
/// <summary> 대기 상태 </summary>
public BaseState RunState { get; private set; }
//==========================================================================
public PlayerStateMachine(Player player)
{
_player = player;
StateInit();
}
private void StateInit()
{
IdleState = new P_IdleState(_player, this);
RunState = new P_RunState(_player, this);
//아래에 추가 상태가 더 생길시 기본 세팅을 해야합니다.
CurrentState = IdleState;
}
//현재 상태의 Update함수를 주기적으로 실행(PlayerClass Update()에 추가)
public override void OnStateUpdate()
{
CurrentState.OnUpdate();
}
//현재 상태의 FixedUpdate함수를 주기적으로 실행(PlayerClass FixedUpdate()에 추가)
public override void OnStateFixedUpdate()
{
CurrentState.OnFixedUpdate();
}
/// <summary> 현재 상태를 다른 상태로 전이하는 함수 </summary>
public void OnStateChange(BaseState nextState)
{
if (CurrentState == nextState)
{
Debug.Log("현재 상태와 같은 상태로 전이할 수 없습니다.");
return;
}
CurrentState.OnExit();
CurrentState = nextState;
CurrentState.OnEnter();
}
}
Player
플레이어의 기본적인 정보를 가지고 있는 클래스
using UnityEngine;
/// <summary> 플레이어 클래스 </summary>
public class Player : MonoBehaviour
{
[Tooltip("이동 속도")]
[SerializeField] private float _moveSpeed;
/// <summary> 플레이어 상태 머신 </summary>
private PlayerStateMachine _machine;
private void Awake()
{
Init();
}
private void Init()
{
_machine = new PlayerStateMachine(this);
}
private void Update()
{
//상태 머신의 Update함수를 주기적으로 실행
_machine?.OnStateUpdate();
}
private void FixedUpdate()
{
//상태 머신의 FixedUpdate함수를 주기적으로 실행
_machine?.OnStateFixedUpdate();
}
/// <summary> 키를 입력받아 오브젝트를 움직이게하는 함수 </summary>
public bool Move()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 movePos = new Vector3(horizontal, 0, vertical);
transform.Translate(movePos * Time.deltaTime * _moveSpeed);
if (horizontal != 0 || vertical != 0)
return true;
return false;
}
}
이상으로 유한 상태 기계에 대해 간략하게 알아봤습니다.
하지만 각각 상태들을 뚜렷하게 정의할 수 없거나 여러 상태를 가져야 하는 경우, 상태의 종류가 많아지게되면 코드의 가독성이 떨어지기 때문에 이러한 특징을 고민해보고 사용해야 할 것 같습니다.
'Unity 공부' 카테고리의 다른 글
[Unity] 컷 아웃 마스크(Cutout Mask) (0) | 2024.01.24 |
---|---|
[Unity] Behavior Tree(BT) (0) | 2023.12.11 |
[Unity] 가중치 랜덤 뽑기 (0) | 2023.11.05 |
[Unity] PC에 저장된 이미지 파일 불러오기 (0) | 2023.11.02 |
[Unity] 부분 화면 캡처 기능 (0) | 2023.10.31 |