TIL(2024,09,04)캐릭터 상태패턴 적용(State Pattern)

김보근·2024년 9월 4일

Unity

목록 보기
77/113
post-thumbnail

TIL: 캐릭터 상태 패턴(State Pattern) 구현하기

오늘은 Unity에서 상태 패턴(State Pattern)을 사용하여 캐릭터의 상태를 관리하는 방법을 배웠다. 상태 패턴은 게임에서 캐릭터의 다양한 상태(Idle, Run, Hit 등)를 효율적으로 관리하고, 상태 전환을 매끄럽게 처리할 수 있도록 도와주는 중요한 디자인 패턴이다.

오늘 한 작업 :
State Pattern 도입: 플레이어의 상태를 관리하기 위해 State Pattern을 도입함.
IState 인터페이스를 정의하고, 각 상태(IdleState, RunState)를 개별 클래스로 구현.
상태 전환을 관리하는 StateMachine을 구현하여 상태 간의 전환을 처리하도록 구성함.
상태에 따른 애니메이션 처리:
각 상태 진입 시 애니메이션을 전환하기 위해 Animator를 사용.
상태의 Enter() 메서드에서 적절한 애니메이션을 재생하도록 구성함.

주요 클래스:
Player: 입력을 받아 StateMachine에 전달하고, 애니메이션 및 이동 처리를 담당.
IState: 상태 패턴의 기본 인터페이스로, Enter(), Execute(), Exit() 메서드를 포함.
StateMachine: 현재 상태를 관리하며 상태 전환을 처리.
IdleState: 플레이어가 움직이지 않는 상태를 정의.
RunState: 플레이어가 이동 중일 때의 상태를 정의하고, 이동 속도에 따른 애니메이션을 재생.

스크립트

플레이어

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

public class Player : MonoBehaviour
{
    private Vector2 playerVector;    // 플레이어의 이동 방향을 저장할 변수
    [SerializeField] private float playerSpeed;   // 플레이어 이동 속도

    private Rigidbody2D rb;          // 물리 연산에 사용할 Rigidbody2D
    private SpriteRenderer sprite;   // 스프라이트 방향 변경에 사용할 SpriteRenderer
    private Animator animator;       // 애니메이션 제어에 사용할 Animator
    private StateMachine stateMachine;   // 상태를 관리할 상태 머신

    private void Awake()
    {
        // 컴포넌트 참조 초기화
        rb = GetComponent<Rigidbody2D>();
        sprite = GetComponent<SpriteRenderer>();
        animator = GetComponent<Animator>();

        // 상태 머신 초기화 및 IdleState로 시작
        stateMachine = new StateMachine();
        stateMachine.SetState(new IdleState(stateMachine, animator));
    }

    private void FixedUpdate()
    {
        // 플레이어 이동 처리 (상태에 따라 이동 로직이 변경될 수 있음)
        Vector2 movement = playerVector.normalized * playerSpeed * Time.fixedDeltaTime;
        rb.MovePosition(rb.position + movement);
    }

    public void OnMove(InputValue value)
    {
        // 이동 입력을 받아 상태 머신에 전달
        playerVector = value.Get<Vector2>();
    }

    public void OnHit()
    {
        // 플레이어가 피격되었을 때 상태를 HitState로 전환
        //stateMachine.SetState(new HitState(stateMachine, stateMachine.CurrentState, animator));
    }

    private void Update()
    {
        // 매 프레임마다 상태 머신을 업데이트
        stateMachine.Update(playerVector);
    }

    private void LateUpdate()
    {
        // 플레이어 스프라이트 방향 처리
        if (playerVector.x != 0)
            sprite.flipX = playerVector.x < 0;
    }
}

Istate 상태패턴 기본 인터페이스

using UnityEngine;

public interface Istate
{
    void Enter(); // 상태에 진입할 때 호출
    void Execute(Vector2 playerVector); // 상태가 활성화 되어 있을때 매 프레임 호출
    void Exit(); // 상태에서 벗어날때 호출
}

IdleState 대기 상태

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

public class IdleState : Istate
{
    private StateMachine stateMachine;
    private Animator animator;

    public IdleState(StateMachine machine, Animator animator)
    {
        stateMachine = machine;
        this.animator = animator;
    }
    public void Enter()
    {
        Debug.Log("대기상태");
        animator.Play("Idle");
    }

    public void Execute(Vector2 playerVector)
    {
        if(playerVector.magnitude > 0)
        {
            stateMachine.SetState(new RunState(stateMachine, animator)); // 상태전환
        }
    }

    public void Exit()
    {
        Debug.Log("대기 상태 종료");
    }
}

RunState 뛰는상태

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

public class RunState : Istate
{
    private StateMachine stateMachine;
    private Animator animator;
    public RunState(StateMachine machine, Animator animator)
    {
        stateMachine = machine;
        this.animator = animator;
    }
    public void Enter()
    {
        Debug.Log("뛰는상태");
        animator.Play("Run");
    }

    public void Execute(Vector2 playerVector)
    {
        if(playerVector.magnitude == 0)
        {
            stateMachine.SetState(new IdleState(stateMachine, animator));
        }
    }

    public void Exit()
    {
        Debug.Log("뛰는상태 종료");
    }
}

StateMachine 현재 상태를 관리하는 스크립트

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

public class StateMachine
{
    private Istate currentState;

    public void SetState(Istate newState)
    {
        if(currentState != null)
            currentState.Exit();

        currentState = newState;
        currentState.Enter();
    }
    public void Update(Vector2 PlayerVector)
    {
        if (currentState != null)
            currentState.Execute(PlayerVector);
    }
}

트러블 슈팅

문제: 애니메이션이 제대로 전환되지 않음.

원인: Animator에 미리 설정된 트랜지션 및 파라미터가 존재했기 때문에 상태 패턴에서 직접 애니메이션을 재생해도 트랜지션 조건을 충족하지 않아서 애니메이션이 변경되지 않았음.

해결 방법:

Animator의 트랜지션 설정을 제거하고, 상태 패턴에서 Animator.Play("AnimationName")을 사용하여 직접적으로 애니메이션을 제어함으로써 즉시 애니메이션이 전환되도록 처리.

결과: 상태에 맞는 애니메이션이 즉각적으로 반응하고, 트랜지션 설정에 의해 애니메이션 전환이 지연되거나 무시되는 문제가 해결됨.

profile
게임개발자꿈나무

0개의 댓글