DevLog/D2D11 프로젝트

D2D11HollowKnight - StatePatern을 Class화 하는 장점

뽀또치즈맛 2025. 3. 17. 22:41

 
 
FSM은 한 번에 한 가지 상태만 가질 수 있기에
상태 변화에 크게
A상태 진입 -> A상태 진행 중 -> A상태 탈출 -> B상태 진입
이런 순으로 나눌 수 있다.
즉, 하나의 상태를 3가지의 상태로 또 쪼갤 수 있는 것이다.
 
State 패턴을 가질 수 있는 클래스와
그 3가지 상태를 구분하는 Enum Class를 하나 둔다.
 

enum class EState
{
	Enter,
	Exit,
	Ing,
	None
};

class State
{
public:
	State(class StateComponent* owner, std::string key, State* val);
	virtual ~State() {};
	virtual void Update() = 0;
	virtual void LateUpdate() = 0;
	virtual void OnEnter() = 0;
	virtual void OnExit() = 0;
	virtual void OnIng() = 0;
	virtual const char* GetName() const = 0;

	EState state;
	std::string mKey;
	State* mVal;
	StateComponent* mOwner;
};

 
 
이를 상속 받은 WallState는 다음과 같이 헤더 파일에서 정의된다.

class WallState : public State
{
public:
	WallState(class StateComponent* owner, std::string key, State* val);
	virtual ~WallState();

	void Init(class BaseCharacter* character, ObRect* wallCol);
	virtual void Update() override;
	virtual void LateUpdate() override;
	virtual void OnEnter() override;
	virtual void OnExit() override;
	virtual void OnIng() override;
	virtual const char* GetName() const override;

private:
	class ObRect* mWallCol;
	BaseCharacter* mCharacter;
};

 
아래는 Cpp파일로 Wallstate의 초기화 및
진입 조건, 탈출 조건을 관리할 수 있다.

#include "Actor/Component/State/State.h"
#include "WallState.h"

WallState::WallState(StateComponent* owner, std::string key, State* val) : State(owner, key, val)
{
}

WallState::~WallState()
{
}

void WallState::Init(BaseCharacter* character, ObRect* wallCol)
{
	this->mWallCol = wallCol;
	this->mCharacter = character;
}

void WallState::Update()
{
}

void WallState::LateUpdate()
{
	OnEnter();
	OnExit();
	OnIng();
}

void WallState::OnEnter()
{
	if (state == EState::Enter) {
		state = EState::Ing;
	}
}

void WallState::OnExit()
{
	if (state == EState::Exit) {
		state = EState::None;
	}
}

void WallState::OnIng()
{
	if (state != EState::Ing) return;
}

const char* WallState::GetName() const
{
	return "State = WallState";
}

 
 
이렇게 관리하면 Wall이 첫 등장시
즉, OnEnter 지진 효과를 나타낸다거나 할 수 있다.
 
사실 구조 짜면서 해당 구조를 내 프로젝트에 적용하고자
좀 서툴게 만들었는데,
다시 세분화하고 기획해서 만들어 볼 생각이다. 

리소스나 게임 레퍼 참고는 HollowKnight를 참고할 예정이지만,
내가 구현하고 싶은 것들을 해당 프로젝트를 통해 구현하고자 한다.

해당 구조의 기반이 되었던 코드는 아래와 같다.

class AIState
{
public:
	AIState(class AIComponent* owner) : mOwner(owner) {}
	virtual void Update(float deltaTime) = 0;
	virtual void OnEnter() = 0;
	virtual void OnExit() = 0;
	virtual const char* GetName() const = 0;

protected:
	class AIComponent* mOwner;
};

class AIPatrol : public AIState
{
public:
	AIPatrol(class AIComponent* owner)
		:AIState(owner)
	{ }

	// Override with behaviors for this state
	void Update(float deltaTime) override;
	void OnEnter() override;
	void OnExit() override;

	const char* GetName() const override
	{ return "Patrol"; }
};

class AIDeath : public AIState
{
public:
	AIDeath(class AIComponent* owner)
		:AIState(owner)
	{ }

	void Update(float deltaTime) override;
	void OnEnter() override;
	void OnExit() override;

	const char* GetName() const override
	{ return "Death"; }
};

class AIAttack : public AIState
{
public:
	AIAttack(class AIComponent* owner)
		:AIState(owner)
	{ }

	void Update(float deltaTime) override;
	void OnEnter() override;
	void OnExit() override;

	const char* GetName() const override
	{ return "Attack"; }
};

 
 
나는 StateComponent를 활용한다는 점에서 좀 다르다.
아래는 StateComponent의 해더 파일 부분이다.

#include "Actor/Component/Component.h"
#include "State.h"

class StateComponent : protected Component
{
public:
	StateComponent(class Actor* owner, std::string key);
	virtual ~StateComponent();

	std::map<std::string, class State*> mStateMap;
};

 
 
 
어느정도 구현을 완성한 후 클래스 다이어그램으로 다시 정리해놓고자 한다.
결국 컴포넌트도 map으로 관리되므로,
owner를 따로 지정만 해준다면 owner에서는 컴포넌트를 추가하기 위해서


//State Component 추가
mStateComponent = new StateComponent(this, "StateComponent");

//State Component 안에 floorState 추가.
mFloorStateCom = new FloorState(mStateComponent, "floor", mFloorStateCom);

해당 코드 두 줄이면 충분하다.
 
이 코드를 만들고자 떠올린 아이디어는 언리얼에 익숙했기에,
언리얼 C++과 비슷한 방식으로 코드를 쓰고싶어서 위와 같이 만든 것도 있다.
흠...map으로 관리하는 템플릿화하면 뭔가 비슷하지 않을까 
코드 하나만 보고 어떻게 했겠거니 하고 딱 보이고 싶다.
많이 공부하고 많이 짜봐야겠다.
 
사실 간단하게 enum으로 쫙쫙 빼는 방법도 있다.
그건 이미 으래 아는 방법이고,
좀 더 객체지향적으로 쓰는 방법을 프로젝트에 적용하고 싶었다.
enum이 익숙하다면 속도면에서 이점이 있을 것이고,
위 방법으로 짜면 나중에 엔진 코드를 볼 때 구조보는 눈이 길러질 것을 기대하고 짰었다.

다시 만들 땐 변수명 규칙도 통일해야겠다.
 
----
 
토이 플젝으로 다시 취준 때 만들다가 말았던 포폴을 제작하고자 한다.
코드도 리팩토링하고자 하고,
그 코드를 리팩토링하는 과정을 블로그에 남기고,
리팩토링 끝나고 구현하는 과정도 블로그에 남기고자 한다.
 
이 프로젝트는 4달 정도 보고있다.
혼자 만드는 것이기도 하고,
회사에서 좀 남아서도 일을 하는 편이라
좀 넉넉하게 보고있다.
아마 당분간은 좀 바쁘겠다.
 
----