
저번 글에서는 Input Action을 스크립트로 접근하는 법과 Player Animation을 FSM으로 구현하기 위한 기본 세팅을 마쳤었다.

이제 본격적으로 State Machine을 만들어보자.
State Machine 만들기
제일 먼저
전체적인 State를 관리해줄 기능을 위해
PlayerStateMachine.cs
스크립트를 작성하고,
모든 State들의 형태가 비슷하기 때문에
상속을 받아 만들 기본 State인
PlayerBaseState.cs
를 작성하도록 하겠다.
PlayerStateMachine.cs
using UnityEngine; public class PlayerStateMachine : StateMachine { public Player Player { get; } // States public PlayerIdleState IdleState { 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 MainCameraTransform { get; set; } public PlayerStateMachine(Player player) { this.Player = player; IdleState = new PlayerIdleState(this); MainCameraTransform = Camera.main.transform; MovementSpeed = player.Data.GroundedData.BaseSpeed; RotationDamping = player.Data.GroundedData.BaseRotationDamping; } }
해당 코드에는 이동이 이루어질 때 필요한 데이터들이 들어가게 되고, State Machine을 이루기 위한 정보와 기능들이 들어가 있다.
Player.cs
에도 PlayerStateMachine
을
필드와 상태 업데이트 부분을 추가해주자.
Player.cs
public class Player : MonoBehaviour { private PlayerStateMachine stateMachine; private void Awake() { stateMachine = new PlayerStateMachine(this); } private void Start() { Cursor.lockState = CursorLockMode.Locked; stateMachine.ChangeState(stateMachine.IdleState); } private void Update() { stateMachine.HandleInput(); stateMachine.Update(); } private void FixedUpdate() { stateMachine.PhysicsUpdate(); } }
PlayerBaseState.cs
using UnityEngine; public class PlayerBaseState : IState { protected PlayerStateMachine stateMachine; protected readonly PlayerGroundData groundData; public PlayerBaseState(PlayerStateMachine playerStateMachine) { stateMachine = playerStateMachine; groundData = stateMachine.Player.Data.GroundedData; } public virtual void Enter() { AddInputActionsCallbacks(); } public virtual void Exit() { RemoveInputActionsCallbacks(); } public virtual void HandleInput() { ReadMovementInput(); } public virtual void PhysicsUpdate() { } public virtual void Update() { Move(); } protected virtual void AddInputActionsCallbacks() { } protected virtual void RemoveInputActionsCallbacks() { } private void ReadMovementInput() { stateMachine.MovementInput = stateMachine.Player.Input.PlayerActions.Movement.ReadValue<Vector2>(); } private void Move() { Vector3 movementDirection = GetMovementDirection(); Rotate(movementDirection); Move(movementDirection); } private Vector3 GetMovementDirection() { Vector3 forward = stateMachine.MainCameraTransform.forward; Vector3 right = stateMachine.MainCameraTransform.right; forward.y = 0; right.y = 0; forward.Normalize(); right.Normalize(); return forward * stateMachine.MovementInput.y + right * stateMachine.MovementInput.x; } private void Move(Vector3 movementDirection) { float movementSpeed = GetMovemenetSpeed(); stateMachine.Player.Controller.Move( (movementDirection * movementSpeed) * Time.deltaTime ); } private void Rotate(Vector3 movementDirection) { if(movementDirection != Vector3.zero) { Transform playerTransform = stateMachine.Player.transform; Quaternion targetRotation = Quaternion.LookRotation(movementDirection); playerTransform.rotation = Quaternion.Slerp(playerTransform.rotation, targetRotation, stateMachine.RotationDamping * Time.deltaTime); } } private float GetMovemenetSpeed() { float movementSpeed = stateMachine.MovementSpeed * stateMachine.MovementSpeedModifier; return movementSpeed; } protected void StartAnimation(int animationHash) { stateMachine.Player.Animator.SetBool(animationHash, true); } protected void StopAnimation(int animationHash) { stateMachine.Player.Animator.SetBool(animationHash, false); } }
기본적으로 모든 State는 State Machine과 역참조를 할 것이다.
생성된 State들이 속한 StateMachine을 항상 참조하고 있다는 뜻이다.
나머지 virtual로 되어 있는 부분들은 이제 각각의 State를 만들 때 구현해야하는 공통적인 기능들이다.
PlayerBaseState
를 상속받는
각각의 State들을 만들어보자.
땅에 붙어 있을 때 상태 구현하기
먼저 플레이어가 땅에 닿아있는 상태인
PlayerGroundedState
이다.
PlayerGroundedState.cs
public class PlayerGroundedState : PlayerBaseState { public PlayerGroundedState(PlayerStateMachine playerStateMachine) : base(playerStateMachine) { } public override void Enter() { base.Enter(); StartAnimation(stateMachine.Player.AnimationData.GroundParameterHash); } public override void Exit() { base.Exit(); StopAnimation(stateMachine.Player.AnimationData.GroundParameterHash); } public override void Update() { base.Update(); } public override void PhysicsUpdate() { base.PhysicsUpdate(); } }
이제 땅에 있을 때 가장 기본 상태인 PlayerIdleState를 작성하자.
PlayerIdleState.cs
public class PlayerIdleState : PlayerGroundedState { public PlayerIdleState(PlayerStateMachine playerStateMachine) : base(playerStateMachine) { } public override void Enter() { stateMachine.MovementSpeedModifier = 0f; base.Enter(); StartAnimation(stateMachine.Player.AnimationData.IdleParameterHash); } public override void Exit() { base.Exit(); StopAnimation(stateMachine.Player.AnimationData.IdleParameterHash); } public override void Update() { base.Update(); } }
PlayerSO 만들기
이제 Player에 정보를 넣어주자.
이전 게시글에서 만들었던 PlayerSO 스크립트를 기반으로 스크립터블 오브젝트를 하나 생성해서 다음과 같이 값을 설정해주자.

Player 오브젝트 설정
만들어놓은 Player Object에
Player Input
스크립트와
Character Controller
를 추가해주자.
주의할 것은
Player Input
이 두 개 뜰 것인데,
Input Actions를 넣어주는 것이 아니라
우리가 스크립트화 시켜준 것을 넣어야한다.

Character Controller
라는 것이 뭐냐면
보통 Rigidbody
를 사용하지 않고 움직이는 오브젝트들을 구현할 때 많이 사용하는 것이다.
Animator 설정
이제 모델에 Animation을 걸어주자.
오브젝트 하위에 있는 모델을 클릭해서
Animator
를 추가해주자.
Animator Controller
도 만들어서
Animator
에 넣어주자.

이제 Animation Controller
를 더블클릭해서
Animator
창에 들어가주자.
배경에 우클릭을 해서
Create Sub-State Machine
을 해줄 것이다.
그러고 이름을 Ground로 바꿔주자.
Sub-State Machine은 하나의 State들을 담는 것이 아닌 여러 스테이트로 구성된 집합이라고 생각하면 된다.
Ground를 클릭해서 들어가보면 다음과 같이 또다른 State Diagram들이 나온다.

다음과 같이 추가하고, 애니메이션들을 세팅해주자.

여기까지 구현을 마치고 실행해보면 다음과 같이 IDLE 상태의 애니메이션이 적용된 것을 볼 수 있다.

지금까지 상속을 받을 수 있는 기본적인 형태의 State와 그 State를 받는 State Machine을 만드는 과정에 대해 알아보았다.
추가적인 State들을 만드는 것은 다음 글에서 다루도록 하겠다.

Uploaded by N2T
'개발 > Unity 내일배움캠프 TIL' 카테고리의 다른 글
Unity 포톤 (Photon) (0) | 2023.10.11 |
---|---|
Unity3D FSM으로 플레이어 캐릭터 조작 구현하기 (3) (0) | 2023.10.10 |
Unity3D FSM으로 플레이어 캐릭터 조작 구현하기 (1) (0) | 2023.10.05 |
Unity Universal Render Pipeline (URP) (0) | 2023.10.04 |
Unity3D 프로빌더 사용해보기 (0) | 2023.10.02 |