본문 바로가기
대학생활/수업

게임인공지능 5주차 - FSM and Game

by se.jeon 2023. 9. 26.
728x90
반응형

게임 캐릭터의 행동을 일련의 서로 다른 상태(mental state)로 모델링 할 수 있음.

상태 변화는 플레이어나 다른 캐릭터의 행동에 의해 유도됨.

FSM은 게임에서 AI를 정의하기 위한 기법임

 

영상 시청 : TinyKeep AI Demo Part 1 of 5: Roaming and Chasing

https://youtu.be/tAxfnFRB1Dg?si=LwWY08vX5DinaeZi 

 

영상 시청 : Finite State Machine "Sheeps"

https://youtu.be/4jCpNHRzTC0?si=ghkDedFMQtXzRjSb 

 

계층적 FSM

- 어떤 상태에 대해 단순한 액션이 아닌 경우

- 어떤 상태를 자신만의 또다른 FSM으로 확장하여 그 상태에서 해야 할 일을 정의

- 어떤 상태에 진입 했을 때

 

Advanced Issue : Hierarchical FSMs

FSM 안에 또다른 FSM이 존재 할 수도 있다. (사진을 통한 구조 설명) =

 

Efficiency and Optimization

AI에서 FSM은 사용 가능한 가장 효율적인 기술임.

그러나, 여전히 개선해야 할 점들이 있다.

- 세부 수준 : 조건에 따라 (예를 들면 플레이어와의 거리) 다른 FSM 또는 다른 업데이트 빈도를 사용해야 함.

 

State pattern을 이용한 FSM 구조 (사진)

 

(실습)Lab. West world project

시나리오

1. 등장인물 : 광부

2. 광부가 있는 주요 공간 : 집, 광산, 은행, 술집

3. 광부가 하는 일

- 금광에서 금을 캔다.

- 금광에서 일정량의 금을 캐면 은행에 예금한다.

- 은행에 예금한 후에 집에서 휴식을 취한다.

- 목이 마르면 술집으로 가서 술을 마신다.

 

#pragma once
#include "Miner.h"

class MinerState
{
public:
	virtual void Enter(class Miner* miner) =0;
	virtual void Excute(class Miner* miner)=0;
	virtual void Exit(class Miner* miner)=0;
};

 

#pragma once
#include "MinerState.h"
#include "WestWorldProject.h"
#include <string>
using namespace std;



class Miner
{
private:
	int m_nHP;
	int m_nThirst;
	int m_nGold;
	int m_nInventory;

	const int m_nAdd = 3;
	const int m_nMax = 20;
	string m_sName;
	class MinerState* m_currentState;

public:
	Miner(string name) : m_sName(name) 
	{
		m_nHP = m_nMax;
		m_nThirst = m_nMax;
		m_nGold = 0;
		m_nInventory = 0;
	};
	void Update();
	void ChangeState(class MinerState* pNewState);

	int GetHP() { return m_nHP; }
	int GetThrist() { return m_nThirst; }
	int GetGold() { return m_nGold; }
	int GetInventory() { return m_nInventory; }
	int GetAdd() { return m_nAdd; }
	int GetMax() { return m_nMax; }
	string GetName() { return m_sName; }

	void SetHP(int hp) { m_nHP = hp; }
	void SetThrist(int thirst) { m_nThirst = thirst; }
	void SetGold(int gold) { m_nGold = gold; }
	void SetInventory(int inven) { m_nInventory = inven; }
	void SetName(string name) { m_sName = name; }
};


#include "Miner.h"

void Miner::Update()
{
	if (!m_currentState) 
	{
		m_currentState = EnterMineAndDigForNugget::GetInstance();
	}

	// 갈증은 상시로 계산한다.
	m_nThirst++;
	m_currentState->Excute(this);
}

void Miner::ChangeState(MinerState* pNewState)
{
	m_currentState->Exit(this);
	m_currentState = pNewState;
	m_currentState->Enter(this);
}

 

#pragma once
#include <iostream>
#include "Miner.h"
#include "MinerState.h"



// Work
class EnterMineAndDigForNugget : public MinerState
{
public:
	EnterMineAndDigForNugget() {}

	static MinerState* GetInstance() {
		static MinerState* instance = new EnterMineAndDigForNugget();
		return instance;
	};

	void Enter(Miner* miner);
	void Excute(Miner* miner);
	void Exit(Miner* miner);
};


// Sell
class VisitBankAndDepositGold : MinerState
{
public:
	VisitBankAndDepositGold() {}

	static MinerState* GetInstance() {
		static MinerState* instance = new VisitBankAndDepositGold();
		return instance;
	};
	void Enter(Miner* miner);
	void Excute(Miner* miner);
	void Exit(Miner* miner);
};


// Rest
class GoHomeAndSleepTilRested : MinerState
{
public:
	GoHomeAndSleepTilRested() {}

	static MinerState* GetInstance() {
		static MinerState* instance = new GoHomeAndSleepTilRested();
		return instance;
	};
	void Enter(Miner* miner);
	void Excute(Miner* miner);
	void Exit(Miner* miner);
};


// Drink
class QuenchThirst : MinerState
{
public:
	QuenchThirst() {}

	static MinerState* GetInstance() {
		static MinerState* instance = new QuenchThirst();
		return instance;
	};

	void Enter(Miner* miner);
	void Excute(Miner* miner);
	void Exit(Miner* miner);
};

 

#include "WestWorldProject.h"


// Work
void EnterMineAndDigForNugget::Enter(Miner* miner)
{
	cout << "광부는 광산에 왔습니다.\n";
}

void EnterMineAndDigForNugget::Excute(Miner* miner)
{
	// 일을 한다
	// 인벤토리가 채워진다.
	miner->SetInventory(miner->GetInventory() + (miner->GetAdd() * 2));
	cout << "노동을 해서 인벤토리가 채워졌습니다.";
	cout << "(HP: " << miner->GetHP() << "Thirst : " << miner->GetThrist() << " Gold : " << miner->GetGold() << "Inventory: " << miner->GetInventory() << ")\n\n";

	// /체력이 감소한다.
	miner->SetHP(miner->GetHP() - miner->GetAdd());
	cout << "노동을 해서 체력이 감소했습니다.";
	cout << "(HP: " << miner->GetHP() << "Thirst : " << miner->GetThrist() << " Gold : " << miner->GetGold() << "Inventory: " << miner->GetInventory() << ")\n\n";

	// 주머니가 꽉 차면 은행에 간다.	
	if (miner->GetInventory() >= miner->GetMax())
	{
		miner->ChangeState(VisitBankAndDepositGold::GetInstance());
	}

	// 목이 마르면 주점에 간다.
	if (miner->GetThrist() == 0)
	{
		miner->ChangeState(QuenchThirst::GetInstance());
	}
}

void EnterMineAndDigForNugget::Exit(Miner* miner)
{
	cout << "광부는 일을 그만두고 나왔습니다.\n";
}


// Sell
void VisitBankAndDepositGold::Enter(Miner* miner)
{
	cout << "광부는 은행에 방문했습니다.\n";
}

void VisitBankAndDepositGold::Excute(Miner* miner)
{
	// 인벤토리의 물건들을 판다.
	cout << "광부는 물건들을 판매했습니다.\n";
	miner->SetGold(miner->GetGold() + miner->GetInventory());
	miner->SetInventory(0);
	cout << "(HP: " << miner->GetHP() << "Thirst : " << miner->GetThrist() << " Gold : " << miner->GetGold() << "Inventory: " << miner->GetInventory() << ")\n\n";

	// 부유해졌다면 집에 간다.
	if (miner->GetGold() >= miner->GetMax())
	{
		miner->ChangeState(GoHomeAndSleepTilRested::GetInstance());
	}
	else 
	{
		miner->ChangeState(EnterMineAndDigForNugget::GetInstance());
	}
}

void VisitBankAndDepositGold::Exit(Miner* miner)
{
	cout << "광부는 은행을 나왔습니다.\n";
}


// Rest
void GoHomeAndSleepTilRested::Enter(Miner* miner)
{
	cout << "광부는 집에 돌아왔습니다.\n";
}

void GoHomeAndSleepTilRested::Excute(Miner* miner)
{
	// 휴식을 취하고 체력을 회복한다
	miner->SetHP(miner->GetHP() + miner->GetAdd());
	cout << "광부는 휴식을 취합니다. " << miner->GetHP() << " \n";
	cout << "(HP: " << miner->GetHP() << "Thirst : " << miner->GetThrist() << " Gold : " << miner->GetGold() << "Inventory: " << miner->GetInventory() << ")\n\n";


	// 체력을 완전히 회복하면 일하러 간다.
	if (miner->GetHP() >= miner->GetMax())
	{
		miner->ChangeState(EnterMineAndDigForNugget::GetInstance());
	}
}

void GoHomeAndSleepTilRested::Exit(Miner* miner)
{
	cout << "광부는 집을 나섰습니다.\n";
}


// Drink
void QuenchThirst::Enter(Miner* miner)
{
	cout << "광부는 주점에 방문했습니다.\n";
}

void QuenchThirst::Excute(Miner* miner)
{
	// 술을 마신다.
	miner->SetThrist(miner->GetMax());
	cout << "광부는 술을 마십니다. " << miner->GetThrist() << " \n";
	cout << "(HP: " << miner->GetHP() << "Thirst : " << miner->GetThrist() << " Gold : " << miner->GetGold() << "Inventory: " << miner->GetInventory() << ")\n\n";


	// 갈증을 완전히 회복하면 일하러 간다.
	if (miner->GetThrist() >= miner->GetMax())
	{
		miner->ChangeState(EnterMineAndDigForNugget::GetInstance());
	}
}

void QuenchThirst::Exit(Miner* miner)
{
	cout << "광부는 주점을 나왔습니다.\n";
}

 

728x90
반응형