유한한 상태 기계
오직 프로그래머의 프로그래밍으로 만들어지는 AI
각 조건에 맞게 상태로 움직이는 기계
state 패턴(디자인 패턴)을 활용하여 만든다
ex)몬스터가 순찰 중 순찰 범위 안에 플레이어 들어옴
-> 플레이어 쫓아가는 상태 on
->공격 범위 안에 있으면 공격 상태on
->플레이어가 몬스터의 해당 지역에서 벗어나면 다시 자기 자리 돌아가는 상태on
몬스터들은 순찰,도망,공격,평소상태등의 상태들을 갖고 있으며
조건에 따른 상태 전환들을 통해 몬스터의 행동들을 구현한다
몬스터는 자기의 AI 주소를 알고 있고
AI는 자기의 오너인 Monster 주소 와 State 주소를 알고 있으며
State는 자기의 Owner인 AI 주소를 알고 있다
각기 몬스터 마다 AI를 가지고 있고 AI가 상태들을 갖고 있으며
상태에 조건들에 따라 상태 변경이 필요하면
state에서 AI로 변경이 필요하다고 요청한다
요청된 next state를 AI에서 교체 작업
void AI::ChangeState(MON_STATE _eNextState)
{
CState* pNextState=GetState(_eNextState);
assert(m_pCurState!=pNextState);
m_pCurState = pNextState;
m_pCurState->Enter();
}
하지만 상태가 바뀌는 것도 업데이트에서 일어난다
따라서 상태가 바뀌는 것은 이벤트에서 처리해야 한다
평소상태에서 플레이어가 몬스터 인지 범위 안에 들어오면 몬스터가 플레이어 방향으로 쫓아간다
조건에 해당되면 상태 변환 이벤트 발생
void CIdleState::update()
{
//Player의 위치 체크
CPlayer* pPlayer = (CPlayer*)CSceneMgr::GetInst()->Get_pCurScene()->GetPlayer();
Vec2 vPlayerPos = pPlayer->GetPos();
//몬스터의 범위 안에 들어오면 추적상태로 전환
CMonster* pMonster = GetMonster();
Vec2 vMonPos = pMonster->GetPos();
Vec2 vDiff = vPlayerPos - vMonPos;
float fLen = vDiff.Lenght();
//플레이어가 몬스터의 인식범위 안으로 진입
if (fLen < pMonster->GetInfo().m_fRecogRange)
{
ChangeAIState(GetAI(), MON_STATE::TRACE);
}
}
case EVENT_TYPE::CHANGE_AI_STATE:
{
//lparam :AI
//wParam :Next Type
AI * pAI = (AI*)_eve.lParam;
MON_STATE eNextState = (MON_STATE)_eve.wParam;
pAI->ChangeState(eNextState);
}
void CTraceState::update()
{
//타겟팅 된 플레이어 추적
CPlayer* pPlayer = (CPlayer*)CSceneMgr::GetInst()->Get_pCurScene()->GetPlayer();
Vec2 vPlayerPos = pPlayer->GetPos();
Vec2 vMonPos = GetMonster()->GetPos();
//플레이어의 방향으로 회전하면서 다가가는 것을 생각 해보기
Vec2 vMonDir = vPlayerPos - vMonPos;
vMonDir.Normalize();
vMonPos += vMonDir*GetMonster()->GetInfo().m_fSpeed * fDT;
GetMonster()->SetPos(vMonPos);
}
디자인패턴 종류 중 하나이다
반복되는 것들을 모아서 찍어내기 편하기 위해 하는 디자인
예시
몬스터 타입 enum class로 정의한 다음 케이스로 구분하여
타입별 스탯으로 적용할 것 들을 다 적어 두었다
enum class MON_TYPE
{
NORMAL,
RANGE,
};
CMonster* CMonFactory::CreateMonster(MON_TYPE _eType,Vec2 _vPos)
{
CMonster* pMon = nullptr;
switch (_eType)
{
case MON_TYPE::NORMAL:
{ pMon = new CMonster;
pMon->SetPos(_vPos);
tMonInfo info = {};
info.m_fAtt = 10.f;
info.m_fAttRange = 50.f;
info.m_fRecogRange = 300.f;
info.m_fHP = 100.f;
info.m_fSpeed = 150.f;
pMon->SetMonInfo(info);
AI* pAI = new AI;
pAI->AddState(new CIdleState);
pAI->AddState(new CTraceState);
pAI->SetCurState(MON_STATE::IDLE);
pMon->SetAI(pAI);
}
break;
case MON_TYPE::RANGE:
break;
}
assert(pMon);
return pMon;
}