케릭터의 애니메이션의 동작을 위해 bool타입의 flag변수를 여러개를 사용하는 방법도 있지만 후에 많은 애니메이션이 생기고 그에 따른 많은 양의 if-else문이 작성되게 되어 그것들을 처리해야하는 상황이 생긴다면 코드가 너무 복잡해진다.
이 단점을 해결하기 위한 하나의 방법이 State 패턴을 사용하는 것이다.
객체의 상태에 따라 행동(메서드)이 달라지는 경우, 상태를 객체로 분리하여 관리하는 디자인 패턴
즉, 상태 변화를
using Unity.Mathematics;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;
public class PlayerController : MonoBehaviour
{
[SerializeField]
float _speed = 10.0f;
Vector3 _destPos;
void Start()
{
Managers.input.MouseAction -= OnMouseclicked;
Managers.input.MouseAction += OnMouseclicked;
}
float wait_run_ratio = 0;
public enum PlayerState
{
Die,
Moving,
Idle,
}
PlayerState _state = PlayerState.Idle;
void UpdateDie()
{
}
void UpdateMoving()
{
Vector3 dir = _destPos - transform.position;
if(dir.magnitude < 0.0001f)
{
_state = PlayerState.Idle;
}
else
{
float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
// 1번 방법
// if (moveDist > = dir.magnitude)
// moveDist = dir.magnitude
transform.position += dir.normalized * moveDist;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 20 * Time.deltaTime);
}
// 애니메이션
wait_run_ratio = Mathf.Lerp(wait_run_ratio, 1, 10.0f * Time.deltaTime);
Animator anim = GetComponent<Animator>();
anim.SetFloat("wait_run_ratio", wait_run_ratio);
anim.Play("WAIT_RUN");
}
void UpdateIdle()
{
// 애니메이션
wait_run_ratio = Mathf.Lerp(wait_run_ratio, 0, 10.0f * Time.deltaTime);
Animator anim = GetComponent<Animator>();
anim.SetFloat("wait_run_ratio", wait_run_ratio);
anim.Play("WAIT_RUN");
}
void Update()
{
switch(_state)
{
case PlayerState.Die:
UpdateDie();
break;
case PlayerState.Moving:
UpdateMoving();
break;
case PlayerState.Idle:
UpdateIdle();
break;
}
}
void OnMouseclicked(Define.MouseEvent evt)
{
if (_state == PlayerState.Die)
return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall"))) //768;을 사용해서 2의 8승과 2의 9승을 더한 숫자를 입력해도 된ㅏ.
{
_destPos = hit.point;
_state = PlayerState.Moving;
}
}
}
위의 코드에서 사용된 PlayerState라는 것을 사용하는 방법이다.
그러나 이렇게 코드를 작성하면 생기는 단점이 존재한다.
bool상태 플래그 변수와 다르게 변수와는 다르게 동시에 두가지 상태를 가질 수 없다.근데 이걸 공부하고 블로그를 작성하면서 State패턴에 대한 것을 확인하고 했는데.. 이건 State패턴이 아닌거 같다라는 느낌이 든다.
public interface IPlayerState
{
void EnterState(PlayerController player);
void UpdateState(PlayerController player);
void ExitState(PlayerController player);
}
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 10.0f;
public Vector3 destPos;
public Animator Animator { get; private set; }
public float wait_run_ratio = 0;
private IPlayerState currentState;
void Start()
{
Animator = GetComponent<Animator>();
ChangeState(new IdleState()); // 처음에는 Idle 상태
}
void Update()
{
currentState.UpdateState(this); // 현재 상태의 UpdateState() 호출
}
public void ChangeState(IPlayerState newState)
{
if (currentState != null)
currentState.ExitState(this);
currentState = newState;
currentState.EnterState(this);
}
void OnMouseclicked(Define.MouseEvent evt)
{
if (currentState is MovingState)
return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 100.0f, LayerMask.GetMask("Wall")))
{
destPos = hit.point;
ChangeState(new MovingState()); // 이동 상태로 변경
}
}
}
using UnityEngine;
public class IdleState : IPlayerState
{
public void EnterState(PlayerController player)
{
Debug.Log("플레이어가 대기 상태");
}
public void UpdateState(PlayerController player)
{
player.wait_run_ratio = Mathf.Lerp(player.wait_run_ratio, 0, 10.0f * Time.deltaTime);
player.Animator.SetFloat("wait_run_ratio", player.wait_run_ratio);
player.Animator.Play("WAIT_RUN");
}
public void ExitState(PlayerController player)
{
Debug.Log("대기 상태 종료");
}
}
public class MovingState : IPlayerState
{
public void EnterState(PlayerController player)
{
Debug.Log("플레이어가 이동 중");
}
public void UpdateState(PlayerController player)
{
Vector3 dir = player.destPos - player.transform.position;
if (dir.magnitude < 0.0001f)
{
player.ChangeState(new IdleState()); // 이동 완료 후 Idle로 변경
}
else
{
float moveDist = Mathf.Clamp(player.speed * Time.deltaTime, 0, dir.magnitude);
player.transform.position += dir.normalized * moveDist;
player.transform.rotation = Quaternion.Slerp(player.transform.rotation, Quaternion.LookRotation(dir), 20 * Time.deltaTime);
}
player.wait_run_ratio = Mathf.Lerp(player.wait_run_ratio, 1, 10.0f * Time.deltaTime);
player.Animator.SetFloat("wait_run_ratio", player.wait_run_ratio);
player.Animator.Play("WAIT_RUN");
}
public void ExitState(PlayerController player)
{
Debug.Log("이동 상태 종료");
}
}
public class DieState : IPlayerState
{
public void EnterState(PlayerController player)
{
Debug.Log("플레이어가 사망했습니다.");
player.Animator.Play("DIE");
}
public void UpdateState(PlayerController player)
{
// 사망 상태에서는 아무 동작도 하지 않음
}
public void ExitState(PlayerController player)
{
Debug.Log("사망 상태에서 벗어남");
}
}
GPT는 이런식으로 만드는 것이 State 패턴이라고 한다.
좀 더 공ㅂ를 해봐야겠다.