게임 캐릭터의 행동을 일련의 서로 다른 상태(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";
}
'대학생활 > 수업' 카테고리의 다른 글
게임기획과비주얼스크립팅 5주차 - 블루프린트 기능 추가 (0) | 2023.10.05 |
---|---|
리얼타임엔진 5주차 - 시작해요 UEFN4 (0) | 2023.09.26 |
게임밸런스및시뮬레이션 5주차 - 능력치 밸런스 (0) | 2023.09.25 |
게임그래픽프로그래밍심화 5주차 - 그래픽스 API 혼합처리와 스텐실 (0) | 2023.09.25 |
게임기획과비주얼스크립팅 4주차 - 엔진 기초용어, 언리얼 기초 (0) | 2023.09.21 |