Chapter 11. 상태 패턴(State Pattern)

Date:     Updated:

카테고리:

태그:

인프런에 있는 이재환님의 강의 게임 디자인 패턴 with Unity 를 듣고 정리한 필기입니다. 😀

Chapter 11. State Pattern

🔔 개념

상태 패턴을 사용하지 않은 경우

상태 플래그 변수 사용할 때

슈퍼마리오 같은 횡스크롤 게임을 만든다면 서있기 / 점프 / 엎드리기 / 엎드려 기모으기 / 이동 과 같은 동작을 만들 것이다. 이단 점프는 허용 안 한다고 가정.

  • 일단 스페이스바 누르면 점프하도록 만들기
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StartCoroutine("HandleJump");  // 점프를 처리하는 코루틴 함수
        }
    }
    
  • 이단 점프를 허용 안한다고 가정하므로 이단 점프를 막기 위해 다음과 같이 추가한다.
     void Update()
     {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (!is.Jumping)
            {
                isJumping = true;
                StartCoroutine("HandleJump");  // 점프를 처리하는 코루틴 함수
            }
        }
     }
    
  • 이제 캐릭터가 땅에 있을 때 아래쪽 버튼을 누르면 엎드리고, 버튼을 떼면 다시 일어서는 기능을 추가해보자.
    void Update()
     {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (!is.Jumping)
            {
                isJumping = true;
                StartCoroutine("HandleJump");  // 점프를 처리하는 코루틴 함수
            }
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow))  // ↓ 키를 누르면
        {
            if (!is.Jumping)  // 점프 상태가 아닐 때만 엎드릴 수 있어야 함
            {
                StartCoroutine("HandleDown");  // 엎드리기를 처리하는 코루틴 함수
            }
        }
        else if (Input.GetKeyUp(KeyCode.DownArrow))  // ↓ 키를 떼면
        {
            StartCoroutine("HandleStand");  // 일어나기를 처리하는 코루틴 함수
        }
     }
    
  • 그런데 이렇게 해도 또 수정해야할 버그가 있다. 예를 들어 엎드린 상태에서 점프를 하고 공중에서 아래 버튼을 뗀다면 점프 중인데도 잠시 동안 땅에 서있다가 다시 올라가는 버그가 생길 것이다.
    • 👉 상태 변경을 더 체크해야 하므로 상태 변경을 체크할 isJumping같은 플래그 변수를 더 추가 해야 한다.
      • 근데 그래도 또! 버그가 생길 것이고 플래그 변수를 더 추가해야 한다. 너무 복잡

FSM 을 사용할 때

위와 같은 문제를 FSM (유한 상태 기계) 사용으로 해결해보자.

  • 캐릭터가 할 수 있는 동작들을 모두 적고 상태도를 그림
    • image
  • switch 문으로 case : STATE_Standing , case : STATE_Jumping … 등등
    • 이런식으로 각각의 상태들 경우에 따라 따로 처리를 해주는 방식이다.
  • 그러나 FSM을 제어하기 위한 열거문 만으로도 부족할 때가 있다
    • 코드가 꼬인다.

상태 패턴 정의 및 적용하기

상태를 별도의 클래스로 캡슐화한 다음 현재 상태를 나타내는 객체에게 행동을 위임한다.

  • 따라서 내부 상태가 바뀜에 따라서 행동이 달라지게 된다.
  • 동적으로 행동을 교체할 수 있다.
  • 전략 패턴과 구조는 거의 동일하나 쓰임의 용도가 다르다.

핵심 정리

  • 상태 패턴을 사용하면 내부 상태를 바탕으로 여러가지 서로 다른 행동을 사용할 수 있다.
  • 상태 패턴을 사용하면 FSM을 쓸 때와 달리 각 상태를 클래스를 이용하여 표한하게 된다.
  • 상태를 사용하는 Context 객체에서는 현재 상태에게 행동을 위임한다.
  • 각 상태를 클래스로 캡슐화함으로써 나중에 변경시켜야 하는 내용을 국지화시킬 수 있다.
  • 상태 패턴전략 패턴 의 용도 차이점
    • 전략 패턴
      • 행동 또는 알고리즘을 Context 클래스를 만들 때 설정한다.
    • 상태 패턴
      • Context의 내부 상태가 바뀜에 따라 알아서 행동을 바꿀 수 있도록 할 수 있다.
  • 상태 전환은 State 클래스에 의햇서 제어할 수도 있고 Context 클래스에 의해서 제어할 수도 있다.
  • State클래스를 여러 Context 객체의 인스턴스에서 공유하도록 디자인 할 수도 있다.


🔔 예제 1

📜 State.cs

// State 추상클래스
public abstract class State
{
    //public virtual void Handle() { }
    public abstract void Handle();
};

/*
// 이렇게 인터페이스로 구현해도 됨
public interface State
{
    void Handle();
};
*/


📜 ConcreteStateA.cs

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

// ConcreteState1 클래스
public class ConcreteStateA : State
{
    public override void Handle()  // 오버라이딩
    {
        Debug.Log("ConcreteStateA");
    }
}


📜 ConcreteStateB.cs

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

// ConcreteState2 클래스
public class ConcreteStateB : State
{
    public override void Handle()   // 오버라이딩
    {
        Debug.Log("ConcreteStateB");
    }
}


📜 Context.cs

// Context 클래스
public class Context
{
    private State state;  // ConcreteStateA 혹은 ConcreeStateB 할당 가능

    // Constructor
    public Context(State state)  // 어떤 상태가 들어올지 모르지만 일단 상태 입력을 받고
    {
        this.state = state;
    }

    public void setState(State state)  // setter 상태를 se
    {
        this.state = state;
    }

    public void Request()  // getter 상대를 get
    {
        state.Handle();  // ⭐상태에 따른 행동을 알아서 한다.⭐
    }
};


📜 StateManager.cs

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

public class StateManager : MonoBehaviour {

	void Start () {
        // Setup context in a state
        Context c = new Context(new ConcreteStateA());  // 상태를 만들고 상태에 따른 행동을 위임
        c.Request();

        // Issue requests, which toggles state
        c.setState(new ConcreteStateB());  // 상태를 ConcreteStateB로 바꿔줌
        c.Request();

        c.setState(new ConcreteStateA());  // 상태를 ConcreteStateA로 바꿔줌
        c.Request();
    }
}



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기