그래픽스/OpenGL

인공지능 - 상태 기계 설계 해시맵 이용하기

뽀또치즈맛 2024. 10. 27. 00:23

 

게임 인공지능 구현에 빠질 수 없는 것이 있다.

State Machine Behaviors 상태 기계 행위이다.

 

아주 간단한 게임에서는 AI는 항상 같은 행위를 한다.

다음과 같은 3가지 행위를 가지는 AI가 있다고 가정해보자.

 

  • 플레이어 쫒아가기
  • 흩어지기
  • 플레이어로부터 멀어지기

이러한 행위의 변화를 표현하는 한 가지 방법으로 각 행위가 하나의 상태를 가지는

상태 기게 (State Machine)이란 것이 있다.

 

상태 기계 설계하기

 

상태 그 자체는 부분적으로 하나의 상태 기계만 정의한다.

그래서 상태 기계 설계에 있어서 상태 기계를 변경하거나

상태 기계 간 전이하는 방법을 결정하는 것이 중요하다.

 

state 상태를 구분할 때는 주로 enum class를 사용한다.

enum class 혹은 enum을 사용하면

switch 문으로 상태 기계 전이 갱신 함수를 호출 할 수 있다.

 

함수로 상태 기계 전이를 다루면,

여러 갱신 함수는 각 상태를 변화해주는ChangeState 함수를 호출해서

상태 전이를 초기화한다.

 

이러한 상태 전이 함수가 많아지면 관리하기 어렵기 때문에

AIState라는 모든 상태에 대한 기본 클래스를 정의한다.

 

기본 클래스는 상태를 제어하고자 몇 가지 가상 함수를 포함한다.

OnEnter 같은 기능으로 진입 시의 전이 코드,

OnExit 같은 기능으로 종료 전이 코드를 가상 함수로 빼놓는다.

 

어느정도 코드가 커지고 상속성이 고려되면,

AIComponent에 해시 맵을 이용하여

AIState를 맵핑하여 state추가와 삭제에 사용할 수 있다.

 

이러한 방법은 enum class 보다 관리가 용이하고,

좀 더 확장적으로 사용할 수 있다.

class AIState
{
public:
	AIState(class AIComponent* owner) : mOner(owner) {}
	// 각 상태의 구체적인 행동
	virtual void Update(float deltaTime) = 0;
	virtual void OnEnter() = 0;
	virtual void OnExit() = 0;
	// 상태의 이름 얻기
	virtual const char* GetName() const = 0;

protected:
	class AIComponent* mOner;
};

 

 

기본 클래스 상태를 제어하고자 몇 가지 가상 함수를 포함한다.

Update 함수는 상태를 갱신하며,

OnEnter 함수는 상태 진입 시의 전이 코드를 구현한다.

그리고 OnExit 에서는 종료 전이 코드를 구현한다.

GetName 함수는 상태에 대해 사람이 읽을 수 있는 이름 문자열을 반환한다.

또한, AIState 클래스는 mOwner 멤버 변수를 사용해서 

AIComponent와 연관시켜야 한다.

 

그리고 다음과 같이 AIComponent 클래스를 선언한다.

 

class AIComponent : public Component
{
public:
	AIComponent(class Actor* owner);

	void Update(float detaTime) override;
	void ChangeState(const std::string& name);

	// 새 상태를 멥에 추가한다
	void RegisterState(class AIState* state);

private:
	// 상태의 이름과 AIState 인스턴스를 매핑한다
	std::unordered_map<std::string, class AIState*> mStateMap;
	// AI 현재 상태
	class AIState* mCurrentState;
};

 

AIComponent는 상태 이름과 AIState 인스턴스 포인터의 해시 맵을 가진다.

RegisterState 함수는 AIState를 인자로 받아서 맵에 해당 상태를 추가한다.

void AIComponent::RegisterState(AIState* state)
{
	mStateMap.emplace(state->GetName(), state);
}

 

Update문에서는 deltaTime을 인자로 받고,

현재 state상태에의 Update문을 호출하면 된다.

 

ChangeState 함수를 통해

현재 상태를 빠져 나오는 것,

맵에서 새로운 상태를 찾는 것,

새로운 상태로 진입하는 것을 관리할 수 있다.

void AIComponent::ChangeState(const std::string& name)
{
	// 먼저 현재 상태를 빠져 나온다
	if (mCurrentState) {
		mCurrentState->OnExit();
	}

	// 맵에서 새로운 상태를 찾는다
	auto iter = mStateMap.find(name);
	if (iter != mStateMap.end()) {
		mCurrentState = iter->second;
		// 새로운 상태로 진입한다
		mCurrentState->OnEnter();
	}
	else {
		SDL_Log("Could not find AIState %s in state map", name.c_str());
		mCurrentState = nullptr;
	}
}

 

이후 AIState를 상속받은 클래스가 override를 통해 행위를 재정의하면

위 패턴을 사용할 수 있다.

 

이후 Update문에서 일부 갱신 작업을 한뒤,

해쉬와 맵을 이용하여 현재 상태를 AI컴포넌트에게 변경할 수 있도록 

적절한 코드를 작성하면 된다.

'그래픽스 > OpenGL' 카테고리의 다른 글

인공지능 - 길찾기 BFS  (1) 2024.10.30
2D 운석 피하기 게임 속 수학  (2) 2024.10.20
게임 객체와 컴포넌트의 관계  (0) 2024.03.26
선형보간 및 폴리곤 메시  (0) 2023.11.01
컴퓨터 그래픽스의 개요  (1) 2023.10.24