Chapter 11. 상태 패턴(State Pattern)
카테고리: Design Pattern
인프런에 있는 이재환님의 강의 게임 디자인 패턴 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 (유한 상태 기계)
사용으로 해결해보자.
- 캐릭터가 할 수 있는 동작들을 모두 적고 상태도를 그림
- 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();
}
}
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글남기기