Unity 공부 (16)

도토코·2025년 3월 12일

Unity공부

목록 보기
16/22

Chapter 6

State 패턴


케릭터의 애니메이션의 동작을 위해 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패턴이 아닌거 같다라는 느낌이 든다.


IPlayerState.cs

public interface IPlayerState
{
    void EnterState(PlayerController player);
    void UpdateState(PlayerController player);
    void ExitState(PlayerController player);
}

PlayerController.cs

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()); // 이동 상태로 변경
        }
    }
}

PlayerStates.cs

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 패턴이라고 한다.
좀 더 공ㅂ를 해봐야겠다.

profile
코(딩)(꿈)나무

0개의 댓글