유니티/게임 프로젝트

2D RPG - (3) StateMachine의 구성과 완성 (2)

monstro 2024. 12. 9. 00:29
728x90
반응형

1) 자식 오브젝트에서 사용하는 PlayAnimationTrigger

기존에 PlayerController에서 소개해드린 AnimationTrigger 함수의 경우

Animator가 있어야 설정이 가능합니다.

그러나 Animator의 경우

 

위와 같이 PlayerController가 부착된 오브젝트의 자식에 존재합니다.

따라서 Animator에서 사용하는 컴포넌트를 하나 만들어 다음과 같이 사용할 수 있도록 하겠습니다.

 

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

public class PlayerAnimationTrigger : MonoBehaviour
{
    private PlayerController _controller => GetComponentInParent<PlayerController>();

    private void AnimationTrigger()
    {
        _controller.AnimationTrigger();
    }
}

 

위와 같이 로직이 구성되어 Animation의 이벤트가 설정되면

PlayerController의 AnimationTrigger 함수를 호출하여

현재 State의 AnimationFinishTrigger 함수를 호출하게 됩니다.

 

2) 현재 State에 대한 도입과 전환을 수행하는 PlayerStateMachine

public class PlayerStateMachine 
{
    public PlayerState _currentState { get; private set; }

    // 제일 처음 아무것도 아닌 상태에서 특정 State에 돌입하는 경우 호출
    public void Init(PlayerState startState)
    { 
        _currentState = startState;
        _currentState.Enter();
    }

    // 특정 State인 상태에서 다른 State에 돌입하는 경우 호출
    public void ChangeState(PlayerState newState)
    {
        _currentState.Exit();
        _currentState = newState;
        _currentState.Enter();
    }
}

 

PlayerStateMachine은 

Init 함수를 통해 _currentState를 초기화하고 현재 State의 Enter 함수를 실행하고,

ChangeState 함수를 통해

현재 State의 Exit 함수를 실행한 후 _currentState를 업데이트하고 현재 State의 Enter 함수를 실행합니다.

 

3) 모든 State의 부모인 PlayerState

public class PlayerState
{
    // Animtion StateMachine
    protected PlayerStateMachine _stateMachine;
    protected PlayerController _controller;
    private string _animatorBoolParamName;
    
    // Player Input
    protected float _xInput;
    protected float _yInput;
    protected bool _isJumping;
    protected bool _isAttacking;
    protected Rigidbody2D _rigidbody2D;

    // Animation Event
    protected bool _triggerCalled;

    // State Timer
    protected float _stateTimer;

    public PlayerState(PlayerController inController, PlayerStateMachine inStateMachine, string inParamName)
    { 
        this._controller = inController;
        this._stateMachine = inStateMachine;
        this._animatorBoolParamName = inParamName;
    }

    // State에 돌입
    public virtual void Enter()
    {
        _controller._animtor.SetBool(_animatorBoolParamName, true);
        _rigidbody2D = _controller._rigidbody2D;
        _triggerCalled = false;
    }

    // State에서 탈출
    public virtual void Exit()
    {
        _controller._animtor.SetBool(_animatorBoolParamName, false);
    }

    // State에서 매 프레임마다 진행
    public virtual void Update()
    {
        _stateTimer -= Time.deltaTime;

        _xInput = _controller._horizontalValue;
        _yInput = _controller._verticalValue;
        _isJumping = _controller._isJumpPressed;
        _isAttacking = _controller._isAttackClicked;
        _controller._animtor.SetFloat("yVelocity", _rigidbody2D.velocity.y);
    }

    public virtual void AnimationFinishTrigger()
    {
        _triggerCalled = true;
    }
}

 

1) 멤버 변수

가장 먼저 볼수 있는 3개의 멤버변수는 

각각 _stateMachine과 _controller, 그리고 _animatorBoolParamName 입니다.

_stateMachine을 통해 CurrentState를 설정하고 여기에 맞춰 Init과 ChangeState를 수행할 수 있습니다.

_controller를 통해 PlayerController에서 업데이트되는 값들State들에게 반영합니다.

마지막으로 _animatorBoolParamNameAnimator에서 사용할 bool 변수를 의미합니다.

 

그 아래의 Player Input 변수들은 State들이 플레이어를 조작하기 위해 사용하게 됩니다.

 

_triggerCalled 변수는 AnimationEvent에 사용될 불리언 변수입니다.

 

마지막 변수인 _stateTimer는 State들이 공용적으로 사용할 타이머를 의미합니다.

 

2) 멤버 함수

생성자에서는 가장 중요한 3가지 변수를 초기화하게 됩니다.

각각 _stateMachine, _controller 그리고 _animatorBoolParamName 입니다.

 

State에 돌입하는 Enter 함수에서는

현재 State의 _animatorBoolParamNametrue로 설정하고

_rigidBody2D를 초기화, _triggerCalled 변수를 false로 초기화하여 AnimationEvent의 발생을 막겠습니다.

 

State에서 탈출하는 Exit 함수에서는 

현재 State의 _animatorBoolParamName false로 설정합니다.

 

State에서 매 프레임마다 수행하는 Update 함수에서는 

_stateTimer를 감소시키고,

PlayerController로부터 데이터를 가져 PlayerInput 변수들을 초기화합니다.

 

마지막인 AnimationFinishTrigger 함수에서는

_triggerCalled 변수를 true로 설정하여 AnimationEvent를 발생시키겠습니다.

 

4) 발을 딛고 있는 상태의 PlayerStateGrounded

public class PlayerStateGrounded : PlayerState
{
    public PlayerStateGrounded(PlayerController inController, PlayerStateMachine inStateMachine, string inParamName) 
        : base(inController, inStateMachine, inParamName)
    {

    }

    public override void Enter()
    {
        base.Enter();
    }

    public override void Exit()
    {
        base.Exit();
    }

    public override void Update()
    {
        base.Update();

        if (_isAttacking)
            _stateMachine.ChangeState(_controller._priamaryAttackState);

        if (!_controller.DoDetectIsGrounded())
            _stateMachine.ChangeState(_controller._inAirState);

        if (_isJumping && _controller.DoDetectIsGrounded())
            _stateMachine.ChangeState(_controller._jumpState);
    }
}

 

발을 딛고 있는 상태를 나타내는 PlayerStateGrounded의 경우

PlayerState로부터 상속받아 위와 같이 구성됩니다.

기본적으로 부모인 PlayerState의 구조를 실행하되

 

Update의 경우 차이점이 존재합니다.

만약, 공격입력이 들어왔다CurrentState PlayerStatePrimaryAttack으로 변경합니다.

또, 발을 딛고 있지 않은 경우 CurrentState PlayerStateInAir로 변경합니다.

마지막으로 발을 딛고 있는 상태에서 점프입력이 들어왔다

CurrentStatePlayerStateJump로 변경하게 됩니다.

 

5) 가만히 있는 상태의 PlayerStateIdle

public class PlayerStateIdle : PlayerStateGrounded
{
    public PlayerStateIdle(PlayerController inController, PlayerStateMachine inStateMachine, string inParamName) 
        : base(inController, inStateMachine, inParamName)
    {

    }

    // State에 돌입
    public override void Enter()
    {
        base.Enter();

        _controller.SetZeroVelocity();
    }

    // State에서 탈출
    public override void Exit()
    {
        base.Exit();
    }

    // State에서 매 프레임마다 진행
    public override void Update()
    {
        base.Update();

        if (_xInput == _controller._facingDir && _controller.DoDetectIsFacingWall())
            return;

        if (_xInput != 0 && !_controller._doingSomething)
            _stateMachine.ChangeState(_controller._moveState);
    }

 

PlayerStateIdle의 경우 PlayerStateGrounded로부터 상속받아

기본적으로 발을 딛고 있는 상태에서 이뤄지게 됩니다.

 

마찬가지로 Update 함수에서 차이점이 존재하는데,

만약, 세로축 입력이 플레이어가 보는 방향과 같벽을 마주하고 있는 상태에서는 

return을 걸어 아무것도 수행하지 않게끔 합니다.

하지만, 세로축 입력이 들어왔플레이어가 무언가 수행한다면

플레이어의 CurrentState를 PlayerStateMove로 변경하게 됩니다.

728x90
반응형