뇌절금지 뇌절금지
뇌절금지 뇌절금지
슬슬 끝이 다가와서 그런 걸까? 스스로를 통제 못해가고 있는 느낌이 든다...
마음이 급한건지 이게 맞는 건지 잘 모르겠네...
StateMachine & IState - 2
어제 큰 틀로 작성한 것들을 세부적으로 봐보자.
- InputSystem (InputAction)
이전에 했던 것들과 똑같지만 하나 다른 게 있다면 우측 인스펙터 창에서 C# Class File을 Generate 해야 한다는 것이다.
Apply를 눌러 생성하면 다음과 같이 C# Class가 생성된다.
- PlayerInput (C# Class) : Monobehavior
Player 오브젝트에 부착되는 PlayerInput 클래스는 방금 만들어준 InputAction의 C# 클래스 정보를 가지고 있다.
public class PlayerInput : MonoBehaviour
{
public PlayerInputActions InputActions { get; private set; }
public PlayerInputActions.PlayerActions PlayerActions { get; private set; }
private void Awake()
{
InputActions = new PlayerInputActions();
PlayerActions = InputActions.Player;
}
private void OnEnable()
{
InputActions.Enable();
}
private void OnDisable()
{
InputActions.Disable();
}
}
Awake 시 새로운 PlayerInputActions를 생성하고
실제 Action들이 담겨있는 PlayerActions 구조체 PlayerActions에 방금 생성한 InputActions.Player를 담는다.
오브젝트 활성화 여부에 따라 InputAction 도 OnOff 가능하도록 OnEnable과 OnDisable을 이용해 해당 기능을 넣는다.
- Player (C# Class) : MonoBehavior
플레이어의 동작을 수행할 Update 및 FixedUpdate 가 있고,
동작에 필요한 로직을 제어할 PlayerStateMachine을 필드로 가지고 있다.
private PlayerStateMachine stateMachine;
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
stateMachine.ChangeState(stateMachine.IdleState);
}
private void Update()
{
stateMachine.HandleInput();
stateMachine.Update();
}
private void FixedUpdate()
{
stateMachine.PhysicsUpdate();
}
이 외에도 Player 클래스에는 플레이어에 대한 정보들이 담겨있다.
[field: Header("Reference")]
[field: SerializeField] public PlayerSO Data { get; private set; }
[field: Header("Animations")]
[field: SerializeField] public PlayerAnimationData AnimationData { get; private set; }
public Animator Animator { get; private set; }
public PlayerInput Input { get; private set; }
public CharacterController Controller { get; private set; }
public ForceReceiver ForceReceiver { get; private set; }
private PlayerStateMachine stateMachine;
private void Awake()
{
AnimationData.Initialize();
Animator = GetComponentInChildren<Animator>();
Input = GetComponent<PlayerInput>();
Controller = GetComponent<CharacterController>();
ForceReceiver = GetComponent<ForceReceiver>();
stateMachine = new PlayerStateMachine(this);
}
여기서부터 좀 중요했는데,
자세히 보면 Update 메서드에선 stateMachine에 별다른 매개변수 없이 HandleInput 및 Update 메서드만 호출하고 있다.
플레이어의 정보 없이 그저 stateMahcine의 메서드만 호출해주면 어떻게 움직이고 애니메이션이 변할까?
그 이유는 PlayerStateMachine에서 Player를 역참조 하고 있기 때문이다.
해당 부분은 조금 있다 보도록 하자.
- PlayerSO (ScriptableObject)
플레이어가 사용할 파라미터들을 가지고 있는 ScriptableObject.
[CreateAssetMenu(fileName = "Player", menuName = "Characters/Player")]
public class PlayerSO : ScriptableObject
{
[field: SerializeField] public PlayerGroundData GroundedData { get; private set; }
[field: SerializeField] public PlayerAirData AirData { get; private set; }
}
지상(Ground)과, 공중(Air)에 대한 파라미터를 두 개로 분리시켰다.
또한 ScriptableObject 내에서만 수치를 조정하게 하고 인스펙터 상에 상태에 따른 파라미터들이 보이도록
[field: SerializeField] 어트리뷰트와 get; private set; 프로퍼티를 사용했다.
- PlayerAnimationData (C# Class)
Animator 들에 등록된 파라미터들을 Hash로 변환해주는 클래스이다.
Player.Awake 에서 Initialize 메서드를 호출해 사용할 파라미터들을 Hash 로 변환해 준다.
public class PlayerAnimationData
{
[SerializeField] private string groundParameterName = "@Ground";
[SerializeField] private string idleParameterName = "Idle";
[SerializeField] private string walkParameterName = "Walk";
[SerializeField] private string runParameterName = "Run";
[SerializeField] private string airParameterName = "@Air";
[SerializeField] private string jumpParameterName = "Jump";
[SerializeField] private string fallParameterName = "Fall";
[SerializeField] private string attackParameterName = "@Attack";
[SerializeField] private string comboAttackParameterName = "ComboAttack";
public int GroundParameterHash { get; private set; }
public int IdleParameterHash { get; private set; }
public int WalkParameterHash { get; private set; }
public int RunParameterHash { get; private set; }
public int AirParameterHash { get; private set; }
public int JumpParameterHash { get; private set; }
public int fallParameterHash { get; private set; }
public int AttackParameterHash { get; private set; }
public int ComboAttackParameterHash { get; private set; }
public void Initialize()
{
GroundParameterHash = Animator.StringToHash(groundParameterName);
IdleParameterHash = Animator.StringToHash(idleParameterName);
WalkParameterHash = Animator.StringToHash(walkParameterName);
RunParameterHash = Animator.StringToHash(runParameterName);
AirParameterHash = Animator.StringToHash(airParameterName);
JumpParameterHash = Animator.StringToHash(jumpParameterName);
fallParameterHash = Animator.StringToHash(fallParameterName);
AttackParameterHash = Animator.StringToHash(attackParameterName);
ComboAttackParameterHash = Animator.StringToHash(comboAttackParameterName);
}
지금부터 각 상태에 대해 접근하니 많이 세부적으로 알아봐야 한다.
- StateMachine (C# Class)
강의에 따르면 StateMachine은 그 자체로 사용하는 것이 아니라 상속을 받아 사용하고
생성자를 만들지 못하게 하기 위해 추상클래스로 사용한다고 한다.
생성자 부분을 정확히 알지 못해 확인한 결과, 생성자를 가질 순 있지만
인스턴스를 생성하지는 못한다는 것을 알게 되었다.
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();
}
}
Player 클래스에서도 보았듯, 현재 State의 HandleInput과 Update 메서드들을 실행시켜 주는 메서드들이 이곳에 담겨있다.
하지만 여전히 플레이어의 정보는 보이지 않는다.
바로 다음 클래스를 보도록 하자.
- PlayerStateMachine
StateMachine을 상속받고 Player의 State에 따라 행동을 해줄 각 State 정보를 가지고 있으며
실제 로직이 담긴 State에 넘겨줄 플레이어의 이동, 카메라 등을 제어할 파라미터들을 가지고 있다.
public Player Player { get; }
앞서 말했듯 PlayerStateMachine에서 Player를 역참조 하고 있기 때문에
매개변수를 넘기지 않고 StateMachine의 메서드를 호출해도 파라미터 값을 전달할 수 있다.
public PlayerIdleState IdleState { get; }
public PlayerWalkState WalkState { get; }
public PlayerRunState RunState { get; }
플레이어의 각 상태에 따른 로직을 담당하는 IState를 상속받은 State 들이다.
한 마디로 플레이어의 상태이다.
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; }
이동, 카메라 제어 시 필요한 파라미터들이다.
여기까지만 보면 get 프로퍼티들이 있어 초기화도 못하고 무언가를 return 하지도 않는다.
또한 파라미터들의 값 또한 Modifier를 제외하면 초기화가 되어있지도 않다.
그래서 PlayerStateMachine의 생성자를 활용해 이를 할당한다.
public PlayerStateMachine(Player player)
{
Player = player;
IdleState = new PlayerIdleState(this);
WalkState = new PlayerWalkState(this);
RunState = new PlayerRunState(this);
MainCameraTransform = Camera.main.transform;
MovementSpeed = player.Data.GroundedData.BaseSpeed;
RotationDamping = player.Data.GroundedData.BaseRotationDamping;
}
생성 시, Player를 매개변수로 받아 역참조하며,
각 State 들은 인스턴스를 생성해 매개변수 this를 넘겨준다. 이 부분은 또 나중에 보도록 하자.
MainCameraTransform은 Camera.main.transform을 사용하고
Speed와 Damping은 역참조한 Player의 SO Data를 이용해 초기화하자.
MovementInput은 따로 초기화하지 않는다. 그 이유는 플레이어의 입력에 따라 계속해서 변하는 값이기 때문이다.
- IState (interface)
IState를 상속받은 각 상태들이 호출해야 할 메서드를 지정해 놓았다.
현재 상태로 변경될 때 호출하는 Enter입력에 따라 취할 행동이 바뀌는 HandleInput, Update, PhysicsUpdate현재 상태가 끝났을 때 호출하는 Exit
public interface IState
{
public void Enter();
public void Exit();
public void HandleInput();
public void Update();
public void PhysicsUpdate();
}
- PlayerBaseState (C# Class) - IState를 상속받은 BaseState. 이를 상속받은 Ground, AirState 와 또 이걸 상속받은 각종 State가 있다. 이부분은 내일 적도록 해야겠다.
'스파르타 내배캠' 카테고리의 다른 글
스파르타 내배캠 Unity 3기 42일차 (0) | 2024.02.23 |
---|---|
스파르타 내배캠 Unity 3기 41일차 (0) | 2024.02.23 |
스파르타 내배캠 Unity 3기 39일차 (0) | 2024.02.19 |
스파르타 내배캠 Unity 3기 38일차 (1) | 2024.02.18 |
스파르타 내배캠 Unity 3기 37일차 (0) | 2024.02.14 |