#06 문라이터 모작 - 상태머신
📜진행상황
현재 플레이어 상태 중 IDLE, WALK, ROLL 까지 구현이 되었다. 플레이어 입력관련 코드들은 WillController 스크립트에 있고 플레이어에 대한 데이터들은 Will 스크립트에 있다. 각각의 상태들은 StateMachineBehaviour 스크립트로 관리하고 있다.
📜목표
- 플레이어의 일반 공격을 구현한다
- 일반 공격은 총 3단 콤보로 구성되어 있다.
- 콤보 연계중 입력이 없다면 IDLE 상태로 돌아온다.
- 마지막 콤보가 끝나면 IDLE 상태로 돌아온다.
- 플레이어의 보조 공격을 구현한다.
- 일정 시간동안 차지 공격키를 누르면 차지 상태가 된다.
- 보조 공격이 끝난 후엔 IDLE 상태로 돌아온다.
📜상태머신
일단 제일 먼저 생각난 콤보 공격 구현방식은 Sub State Machine을 생성해 콤보 공격을 이어주는 방식이였다.
Sub State Machine 방식
플레이어가 IDLE 혹은 MOVE 상태일 때 콤보 공격 입력이 들어오면 트리거를 발동시켜 ComboAttack 이라는 Sub State Machine으로 전이되서 각각의 콤보 공격을 이어주려 했다. 하지만 첫 콤보 공격이 들어간후 입력을 처리하는 방식이 잘못됬는지 첫 번째 콤보 공격 이후 공격이 이어지지가 않았다. 그러던 중 유튜브를 검색하다 외국 유튜버의 2d 액션 FSM을 직접 구현하는 영상을 보게 되었다. 그리고 직접 코드를 작성해 FSM을 구현하면 제어를 확실히 할 수 있지 않을까 하는 생각이 들어 영상을 보고 직접 구현하기로 했다.
직접 구현한 FSM
처음에 든 느낌은 “새롭다”였다. 기존 상태머신은 StateMachineBehaviour만을 상속받아 작성했던 반면 이 방식은 상위 상태를 두고 상위 상태 안에 하위 상태들을 상속 받게끔 하는 구조였다. 예를 들어 Grounded State는 Idle State와 Move State의 부모 State가 된다. 따라서 공통된 데이터들을 상위 상태에 모아두고 각각의 하위 상태들에서 “동작”만 하게끔 하는 것이였다. 하지만 이러한 방식도 나에겐 무리가 있었다. 내가 직접 짠 코드가 아니라 다른 사람이 구현한 FSM을 내가 급하게 사용하려다 보니 안정적으로 코드를 작성하지 못하였다. 그래서 최종적으로 나온 답은 유니티에서 제공하는 StateMachineBehaviour와 직접 구현한 FSM에서 영감을 얻은 “상태를 상속한다” 라는 개념을 합치는 것이였다.
최종형태
최종형태는 다시 한 번 상태 다이어그램을 짜면서 확실해 졌다. 하위 상태들을 가질 상위 상태들을 먼저 정의 하고 그 하위 상태에 들어갈 상태들을 정리했다. 그리고 각각의 상태들의 전이 조건들을 차분히 정리하니까 어떻게 구현해야 할 지 감이오기 시작했다. 그리고 상태머신을 구현하면서 생각을 못한 부분이 있었는데, 그건 바로 지금 구현하고 있는 문라이터라는 게임은 플레이어 캐릭터가 한 번에 한가지 상태만을 갖는 다는 점이였다. 그러한 생각을 한 순간 상태머신을 어떻게 구성해야 할지 확실해 졌다.
PlayerState
플레이어가 가질 수 있는 모든 상태들의 최상위 상태인 PlayerState 스크립트를 생성하고 StateMachineBehaviour를 상속받게 하였다. 그리고 각각의 상태에 대한 동작을 하기 위한 참조들을 갖고 있게 하였다. 그리고 StateMachineBehaviour를 상속받았기 때문에 PlayerState를 포함한 PlayerState를 상속받은 모든 상태 스크립트들은 StateMachineBehaviour에서 쓰이는 상태 메서드 들을 호출 할 수 있었다.
PlayerGroundedState
대기, 이동 상태 추후 NPC와 대화 혹은 던전 입장과 같은 일반 적인 상태들의 최상위 클래스이다.
PlayerAbilityState
콤보 공격, 보조 공격, 구르기와 같은 플레이어 캐릭터의 기술과 연관된 상태들의 최상위 클래스이다.
상태머신
플레이어 캐릭터는 한번에 한가지 상태만을 가질 수 있으므로 상태머신은 다음과 같이 구성하였다.
각각의 상태에 대한 불리언 인자들을 생성해주고 상태에 진입할 때 true값을 주고 빠져나올 때 false값을 주는 방식이다. 이러한 구조로 상태머신을 구성하게 되면 각각의 상태들을 이어주는 transition이 존재할 필요가 없다는 것이다. 그리고 그렇게 함으로써 구조를 단순화 시키고 버그가 발생할 확률을 줄일 수 있다는 것이다. 단점으로는 2D와 같은 단순한 그래픽에서는 문제가 되지 않지만 표현이 다양한 3D에서는 다시한번 생각해봐야 할 부분인거 같다.
방향지정
다음으로 문제가 됬던 부분은 방향 지정이였다. 처음엔 구르기와 같이 상태를 체크하고 방향을 정해줘는 방법을 생각했다. 하지만 그 방법은 if문을 사용해 코드의 복잡성이 증가할 것 같았다. 그러던 중 기존 애니메이션에 사용하고 있던 블렌드 트리가 보였고 블렌드 트리를 활용하면 쉽게 방향을 제어해 줄 수 있을 것 같았다. 기존에 모든 애니메이션들을 블렌드 트리로 구성하고 있었는데 이 방법이 먼저 생각나지 않았던 건 아쉬웠지만 결과적으로 모든 애니메이션을 블렌드 트리로 구성하는 방법이 가장 깔끔했다.
📜이슈사항
InputSystem 이해도 부족
플레이어 이동 구현할 때도 알고 있었지만 인풋시스템은 키를 눌렀을 때와 땠을 때 모두 호출이 된다. 하지만 이 사실을 알고 있었지만 크게 생각하지 않고 있었다. 그러다 공격을 구현하던 중 공격키를 누르면 한번만 공격해야 할 캐릭터가 두번 공격하는 것을 알게되었다. 버그의 원인을 계속 찾던중 Input debuger를 이용해 키입력 이벤트를 보다가 땠을 때도 입력 이벤트가 호출된다는 것을 알았고 공격키를 처음 입력했을 때 조건을 바꿔주는 식으로 코드를 수정하니 정상적으로 작동되었다.
📜느낀점
게임을 만들 때 어떤 것이든 구조를 설계하는 것이 정말 어렵다고 생각이 들었다. 또한 모작을 하면서 기존에 프로그래밍을 하면서 맞닥드리지 못했던 혹은 맞닥드렸지만 크게 느끼지 못했던 문제들을 마주하면서 문제해결 능력 또한 많이 느는 느낌이다. 아직 구현해야 할 부분은 많이 남았고 앞으로도 더 많은 문제를 마주하게 될 것이다. 하지만 니체가 “나를 죽이지 못하는 고통은 나를 더 강하게 할 뿐” 이라 말한 것 처럼 나 또한 문제를 해결하며 더욱 성장할 것이기 때문에 앞으로 구현하고 맞닥드릴 문제들이 기대가 된다.
댓글남기기