유니티/게임 프로젝트

2D RPG - (1) StateMachine의 설계

monstro 2024. 11. 25. 21:32
728x90
반응형

Unity를 이용한 2D RPG를 만드는 이번 프로젝트의 첫 시작은 StateMachine을 설계하는 것으로 시작하겠습니다.

원래 StateMachine은 인공지능의 하나로, State별로 AI가 취해야 하는 행동을 정의합니다.

 

하지만 이번에 만드는 StateMachine은 플레이어의 애니메이션을 상태기반으로 변경하는 데 사용합니다.

구성은 크게 다음의 3가지로 구성합니다.

  • 플레이어를 조작하면서 StateMachine을 통해 State를 변화시키는 PlayerController
  • State를 변화시키는 주체StateMachine
  • 플레이어의 상태 별로 나눠진 State

 

1) 플레이어의 State를 변화시키는 주체인 PlayerStateMachine

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

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

 

PlayerStaetMachine의 구성은 위와 같습니다.

기본적으로 현재의 State인 _currentState를 중심으로 동작하며

제일 처음 _currentState를 초기화하는 Init 함수

현재 State에서 다른 State로 넘어갈_currentState를 재설정하는 ChangeState 함수로 나뉘어져 있습니다.

 

2) 모든 PlayerState의 부모 클래스인 PlayerState

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

public class PlayerState
{
    protected PlayerStateMachine _stateMachine;
    protected PlayerController _controller;
    private string _animatorBoolParamName;

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

    // State에 돌입
    public virtual void Enter()
    {
        Debug.Log("Enter " + _animatorBoolParamName);
    }

    // State에서 매 프레임마다 진행
    public virtual void Update()
    {
        Debug.Log("Update " + _animatorBoolParamName);
    }

    // State에서 탈출
    public virtual void Exit()
    {
        Debug.Log("Exit " + _animatorBoolParamName);
    }
}

 

PlayerState의 멤버 변수는 3가지 입니다.

PlayerStateMachine 변수PlayerController 변수 그리고 Animator에서 사용될 불리언 변수의 이름입니다.

3가지 정보생성자의 인자로 받아 초기화하게 됩니다.

 

멤버 함수는 총 3가지이며,

State에 처음 돌입하는 Enter, 매 프레임마다 State에서 호출하는 Update, State에서 탈출하는 Exit입니다.

 

3) PlayerStateIdle과 PlayerStateMove

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

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

    }

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

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

        if (Input.GetKeyDown(KeyCode.N))
            _controller._stateMachine.ChangeState(_controller._moveState);
    }

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

 

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

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

    }

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

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

        if(Input.GetKeyDown(KeyCode.N))
            _controller._stateMachine.ChangeState(_controller._idleState);
    }

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

 

일단 StateIdle과 StateMove라는 2개의 State를 만들었습니다.

2개의 State는 N 키를 누르면 서로 State간에 변환이 이뤄지게 됩니다.

생성자에서 PlayerController를 인자로 받는 덕분에

손쉽게 PlayerController를 불러와 State를 변환하는 것을 확인할 수 있습니다.

 

4) 플레이어를 조작하는 PlayerController

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

public class PlayerController : MonoBehaviour
{
    public PlayerStateMachine _stateMachine { get; private set; }

    public PlayerStateIdle _idleState { get; private set; }
    public PlayerStateMove _moveState { get; private set; }

    private void Awake()
    {
        _stateMachine = new PlayerStateMachine();

        _idleState = new PlayerStateIdle(this, _stateMachine, "Idle");
        _moveState = new PlayerStateMove(this, _stateMachine, "Move");
    }

    private void Start()
    {
        _stateMachine.Init(_idleState);
    }

    private void Update()
    {
       _stateMachine._currentState.Update();
    }
}

 

PlayerController는 PlayerStateMachine과 각 PlayerState에 대한 변수를 갖습니다.

이 변수들을 Awake 함수에서 설정하는데,

Unity 오브젝트가 아니므로 new를 통해 생성합니다.

 

Start 함수에서는 Player의 초기 State를 StateIdle로 설정하고,

Update문에서는 각 State의 Update문을 호출합니다.

 

이제 실행 결과를 보겠습니다.

 

PlayerController 컴포넌트를 가진 Circle 오브젝트를 배치해보겠습니다.

제일 먼저 StateIdle에 대한 Enter 함수와 Update 함수가 호출되지만,

N 키를 누르면 StateMove에 대한 Enter 함수와 Update 함수가 호출될 것입니다.

 

 

위의 영상과 같이 StateIdle과 StateMove간의 전환이 잘 이뤄지는 것을 확인할 수 있습니다.

728x90
반응형