이번에 최종 프로젝트를 진행하면서 플레이어 이동 구현을 맡아 이전에 강의에서 배웠던 FSM(Finite State Machine) 유한 상태 머신이라는 디자인 패턴으로 구현해보려고 한다.
FSM 개념
- FSM은 유한한 갯수의 상태들로 구성된 기계 및 패턴을 뜻한다.
- 상태와 상태 간의 전환을 기반으로 동작하는 동작 기반 시스템이다.
FSM의 구성 요소
- 상태 (State) : 시스템이 취할 수 있는 다양한 상태.
- 전환 조건 (Transition Condition) : 상태 간 전환을 결정하는 조건.
- 동작 (Action) : 상태에 따라 수행되는 동작 또는 로직
FSM의 동작 원리
- 초기 상태에서 시작하여 입력 또는 조건에 따라 상태 전환을 수행
- 상태 전환은 전환 조건을 충족할 때 발생하며, 전환 조건은 입력, 시간, 조건 등으로 결정
- 상태 전환 시 이전 상태의 종료 동작과 새로운 상태의 진입 동작이 수행
상태 기계를 구현하는 방법은 다양하다.
가장 간단한 방법은 case switch 문인데 유지보수에 어려움이 있고(상태가 추가 될 때마다 새로운 분기를 작성해야됨), 상태가 많아지고 조건이 복잡해진다면 코드가 지나치게 길어진다는 단점이 있다.
그래서 강의에서는 State Pattern을 활용한다. 기본적으로 객체 지향의 다형성을 활용하고, 상태를 명확하게 정의 및 상태 간 전환을 일관되게 관리할 수 있으며 복잡한 동작을 상태와 전환 조건으로 나누어 구현하므로 코드 유지 보수가 용이하기 때문이다.
예를 들어서 case switch 문으로 이동에 관련된 FSM을 구현했을 때 점프 요소를 추가하려면은 switch 문에 그 상태를 추가해줘야하는데, State Pattern으로 FSM을 구현했다면 새로운 Class를 만들고 연결 시켜주면 끝난다.
처음에는 State Pattern으로 구현하는게 감이 잘 안 잡혔지만 그냥 쉽게 생각하면 기존에 case switch 문으로 작성하던거를 Class로 나눠서 전환 조건이 됐을 때 옮겨간다라는 식으로 생각하고 작성해보니 감이 어느정도 잡히고 있는 것 같다.
플레이어 이동 구현을 FSM으로 선택한 이유는 우선 게임을 한 사이클 돌리는걸 빠르게 만드는게 목표인데, 그러다보니 플레이어의 관련된 기능들에 대해서 선택으로 빼둔 부분들이 어느정도 있어서 혹시라도 시간이 된다면 나중에 새로운 상태들을 더 편하게 확장시키고 싶어서 FSM으로 구현하게 됐다.
IState라는 interface를 만들어 상태 별로 사용할 함수들을 정의하고, SStateMachine을 abstract class로 만들어서 PlayerStateMachine에 상속 시켜준다. 강의에서는 추상 클래스로 만들어서 상속받는 class에서 구현할 수 있도록 한다고 했지만 음... 완성된 코드를 보면 굳이 왜 넣었는지 잘 모르겠다.
Player에서 PlayerStateMachine 클래스를 객체화시켜주면 PlayerStateMachine에서 PlayerBaseState를 상속받은 State들을 객체화 시켜줄 때 PlayerStateMachine을 변수에 넣어준다.
public interface IState
{
public void Enter();
public void Exit();
public void HandleInput();
public void Update();
public void PhysicsUpdate();
}
public abstract class StateMachine
{
protected IState currentState;
public void ChangeState(IState state)
{
currentState?.Exit();
currentState = state;
currentState?.Enter();
}
public void HandleInput()
{
currentState?.HandleInput();
}
public void Update()
{
currentState?.Update();
}
public void PhysicsUpdate()
{
currentState?.PhysicsUpdate();
}
}
이것이 기존에 사용하던 StateMachine.cs인데 내 생각에는 abstract로 class를 구현할거라면
public interface IState
{
public void Enter();
public void Exit();
public void HandleInput();
public void Update();
public void PhysicsUpdate();
}
public abstract class StateMachine
{
public abstract void ChangeState(IState state);
public abstract void HandleInput();
public abstract void Update();
public abstract void PhysicsUpdate();
}
이렇게 바꾸고 상속받는 PlayerStateMachine에서 구현하는 것이 맞는 방식이라고 생각한다.
public class PlayerStateMachine : StateMachine
{
protected IState currentState;
public Player player { get; }
public Vector2 MovementInput { get; set; }
public float MovementSpeed { get; private set; }
public float RotationDamping { get; private set; }
public float MovementSpeedModifier { get; set; } = 1f;
public float JumpForce { get; set; }
public Transform MainCamTransform { get; set; }
public PlayerIdleState IdleState { get; private set; }
public PlayerWalkState WalkState { get; private set; }
public PlayerRunState RunState { get; private set; }
public PlayerJumpState JumpState { get; private set; }
public PlayerStateMachine(Player player)
{
this.player = player;
MainCamTransform = Camera.main.transform;
IdleState = new PlayerIdleState(this);
WalkState = new PlayerWalkState(this);
RunState = new PlayerRunState(this);
JumpState = new PlayerJumpState(this);
MovementSpeed = player.Data.GroundData.BaseSpeed;
RotationDamping = player.Data.GroundData.BaseRotationDamping;
}
public override void ChangeState(IState state)
{
currentState?.Exit();
currentState = state;
currentState?.Enter();
}
public override void HandleInput()
{
currentState?.HandleInput();
}
public override void Update()
{
currentState?.Update();
}
public override void PhysicsUpdate()
{
currentState?.PhysicsUpdate();
}
}
그래서 다른 사람이 StateMachine을 상속받아서 다른 StateMachine을 만들려고 할 때 강제로 구현해야 될 함수를 만들어 놓게 하는게 좋지 않을까 싶다. 만약에 abstract class에서 사용하는 함수의 내용이 다 동일하다면 virtual로 구현하면 될 것 같다.
그래서 결론적으로 상태별로 Class를 구현해서 PlayerStateMachine 쪽에서 생성자를 만들어주고, 필요할 때 stateMachine에 기능별 함수들을 적절하게 호출해서 관리해주면 된다.
'내배켐 Unity TIL' 카테고리의 다른 글
Unity 57일차 TIL - Unity (C# Class 상속) (0) | 2024.07.02 |
---|---|
Unity 56일차 TIL - Unity FSM(디자인 패턴) 트러블슈팅 (0) | 2024.07.01 |
Unity 51일차 TIL - 팀 프로젝트 트러블 슈팅 (0) | 2024.06.24 |
Unity 47일차 TIL - Unity InputSystem (0) | 2024.06.18 |
Unity 41일차 TIL - 팀 프로젝트 2 (마무리) (0) | 2024.06.10 |