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간의 전환이 잘 이뤄지는 것을 확인할 수 있습니다.
'유니티 > 게임 프로젝트' 카테고리의 다른 글
2D RPG - (3) StateMachine의 구성과 완성 (2) (0) | 2024.12.09 |
---|---|
2D RPG - (2) StateMachine의 구성과 완성 (1) (0) | 2024.12.08 |
간단한 2D RPG 프로젝트 - (3) MonsterController (0) | 2024.11.20 |
간단한 2D RPG 프로젝트 - (2) PlayerController (0) | 2024.11.20 |
간단한 2D RPG 프로젝트 - (1) BaseController (0) | 2024.11.20 |