Unity

[FSM] 플레이어 상태머신 (2)

박도치 2023. 12. 27. 19:12

이전 포스트에서 Cinemachtine, ProBuilder, Input Action까지 설치하고 세팅까지 완료했다.

 

그렇다면 이제 FSM 디자인 패턴에 따라 상태들을 스크립트로 나눌 차례이다.

 

스크립트 생성

 

스크립트의 생성은 아래와 같이 생성하였다.

 

 

StateMachine 준비하기

 

PlayerInput.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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();
    }

}

 

여기서는 InputAction에서 입력받는 값들을 받아준다. InputAction의 값들을 get해주며 InputAction변수에 우리가 만들어준 InputSystem의 Player항몫을 넣어주고 활성화되면 Enable을, 비활성화 되면 Disable 처리를 해준다. 즉, 쉽게말하자면 우리가 설정해준 어떠한 입력값을 받았을 때 활성화해주고 아니면 비활성화 해주는 것을 처리하는 스크립트이다.

 

PlayerAnimationData.cs

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
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);
    }
}

 

굉장히 복잡해보이지만 이는 애니메이션을 처리할 때 사용되는 스크립트이다. 각 파라미터의 이름을 정해주고, 동작에 따라 애니메이션을 나눠주는 스크립트이다. 이전에는 플레이어의 움직임이라던지 점프라던지 그런 스크립트에서 애니메이션을 선언하고 처리해줬다면, 여기서는 애니메이션을 따로 처리해주는 스크립트를 생성에서 한번에 처리해준다고 생각하면 된다.

 

Player.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [field: Header("Animations")]
    [field: SerializeField] public PlayerAnimationData AnimationData { get; private set; }

    public Rigidbody Rigidbody { get; private set; }
    public Animator Animator { get; private set; }
    public PlayerInput Input { get; private set; }
    public CharacterController Controller { get; private set; }

    private void Awake()
    {
        AnimationData.Initialize();

        Rigidbody = GetComponent<Rigidbody>();
        Animator = GetComponentInChildren<Animator>();
        Input = GetComponent<PlayerInput>();
        Controller = GetComponent<CharacterController>();
    }

    private void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;
    }

}

 

Player가 가지고있을 Script이다. 플레이어에서 사용할 Component인 Rigidbody, PlayerInput, CharacterController 를 가져오면서 Player 오브젝트 자식에 있는 캐릭터의 Animator도 가져오는 역할을 한다.

 

그리고 이전 PlayerAnimationData 스크립트에서 설정한 Parameter들을 Awake에서 호출하며는 역할이며, 커서는 lock을 걸어주는 역할을 한다.

 

그러고 Player가 가지고 있을 Script이기 때문에 플레이어 오브젝트에 넣어주도록 하자.

 

 

 

인터페이스 IState

public interface IState
{
    public void Enter();
    public void Exit();
    public void HandleInput();
    public void Update();
    public void PhysicsUpdate();

}

 

다음은 인터페이스이다. 어떠한 상태가 시작되거나 끝날 때, Update 되거나 FixedUpdate시 일어날 것들 그리고 input을 다루는 내용들을 담아두고 플레이어 스테이트에서 필요한 부분에서 가져다 사용할 생각이다.

 

StateMachine.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.AI;

public abstract class StateMachine
{
    protected IState currentState;

    public void ChangeState(IState newState)
    {
        currentState?.Exit();

        currentState = newState;

        currentState?.Enter();
    }

    public void HandleInput()
    {
        currentState?.HandleInput();
    }

    public void Update()
    {
        currentState?.Update();
    }

    public void PhysicsUpdate()
    {
        currentState?.PhysicsUpdate();
    }
}

 

 

추상클래스로 인터페이스에 따라 작동을 하는 역할을 한다. ChangeState에서 newState를 받아와서 현재 상태는 Exit해주고, 새로운 State를 담아 이를 Enter해주는 역할을 한다. 그리고 각각 HandleInput, Update, PhysicUpdate 까지 현재 상태에 따라 입력, 업데이트, 물리 업데이트를 처리해주는 역할을 한다.

 

이렇게 해두면 플레이어 상태머신의 준비는 끝난다. 이후에 땅에서 공격, 움직임, 점프 등등을 그리고 공중에서 공격, 추락 등등을 처리해주기 위해 이렇게 기반을 준비한 것이다.

'Unity' 카테고리의 다른 글

유니티 수학 Mathf.Sin()과 Quaternion.Lerp()  (0) 2024.01.02
로딩 구현하기  (0) 2023.12.28
[FSM] 플레이어 StateMachine 만들어보기  (1) 2023.12.26
[Unity] FSM (유한 상태 기계)  (2) 2023.12.22
깃허브 cherry-pick  (3) 2023.12.20