[DirectX] Event 매니저
in Study on WinAPI&DirectX
define 헤더
enum class EVENT_TYPE
{
CREATE_OBJECT,
DELETE_OBJECT,
ADD_CHILD,
DELETE_RESOURCE,
LEVEL_CHANGE,
}
이벤트 옵션을 담은 enum class를 define 헤더파일에 추가해준다.
EventMgr 클래스
// 헤더파일
#pragma once
#include "CSingleton.h"
class CEventMgr :
public CSingleton< CEventMgr>
{
SINGLE(CEventMgr);
private:
vector<tEvent> m_vecEvent;
public:
void AddEvent(const tEvent& _evn) { m_vecEvent.push_back(_evn); }
public:
void tick();
};
// cpp 파일
#include "pch.h"
#include "CEventMgr.h"
#include "CLevelMgr.h"
#include "CLevel.h"
#include "CGameObject.h"
CEventMgr::CEventMgr()
{
}
CEventMgr::~CEventMgr()
{
}
void CEventMgr::tick()
{
for (size_t i = 0; i < m_vecEvent.size(); ++i)
{
switch (m_vecEvent[i].Type)
{
// wParam : GameObject, lParam : Layer Index
case EVENT_TYPE::CREATE_OBJECT:
{
CGameObject* NewObject = (CGameObject*)m_vecEvent[i].wParam;
int iLayerIdx = (int)m_vecEvent[i].lParam;
CLevelMgr::GetInst()->GetCurLevel()->AddGameObject(NewObject, iLayerIdx, false);
}
break;
case EVENT_TYPE::DELETE_OBJECT:
break;
case EVENT_TYPE::ADD_CHILD:
break;
case EVENT_TYPE::DELETE_RESOURCE:
break;
case EVENT_TYPE::LEVEL_CHANGE:
break;
}
}
m_vecEvent.clear();
}
레이어 이름을 지정하기
한편, 레벨의 레이어 별로 이름을 지을 수 있도록 레벨매니저의 코드를 아래와 같이 작성해줄 수 있다.
// CLevelMgr.cpp 파일의 init함수 중에서
m_pCurLevel = new CLevel;
// Layer 이름설정
m_pCurLevel->GetLayer(0)->SetName(L"Default");
m_pCurLevel->GetLayer(1)->SetName(L"Player");
m_pCurLevel->GetLayer(2)->SetName(L"Monster");
m_pCurLevel->GetLayer(3)->SetName(L"PlayerProjectile");
m_pCurLevel->GetLayer(4)->SetName(L"MonsterProjectile");
그리고 오버로딩 함수를 이용해서 CLevel클래스이 AddGameObject함수를 레이어의 이름을 받을 수 있도록 코드를 작성해준다.
FindLayerByName함수와 AddGameObject함수.
// CLevel.cpp 파일 중에서
void CLevel::AddGameObject(CGameObject* _Object, int _iLayerIdx, bool _bMove)
{
m_arrLayer[_iLayerIdx]->AddGameObject(_Object, _bMove);
}
미사일 쏴보기
CMissileScript 클래스
미사일이라는 객체를 생성해주는 것이므로 미사일 스크립트 클래스를 새로 생성해준다.
// 헤더파일
#pragma once
#include "CScript.h"
class CMissileScript :
public CScript
{
private:
Vec3 m_vDir; // 이동 방향
float m_fSpeed; // 이동 속력
public:
void SetSpeed(float _Speed) { m_fSpeed = _Speed; }
public:
virtual void tick() override;
CLONE(CMissileScript);
public:
CMissileScript();
~CMissileScript();
};
// cpp 파일
#include "pch.h"
#include "CMissileScript.h"
CMissileScript::CMissileScript()
: m_vDir(Vec3(0.f, 1.f, 0.f))
, m_fSpeed(100.f)
{
}
CMissileScript::~CMissileScript()
{
}
void CMissileScript::tick()
{
Vec3 vPos = Transform()->GetRelativePos();
vPos += m_vDir * DT * m_fSpeed;
Transform()->SetRelativePos(vPos);
}
해당 방향으로 미사일을 발사하는 기능이 담겨져있다.
CPlayerScript 클래스
미사일을 쏘는 코드를 플레이어 스크립트에 아래와 같이 추가한다.
Shoot 함수.
// 헤더파일
private:
void Shoot();
// cpp파일
void CPlayerScript::Shoot()
{
// 미사일 오브젝트 생성
CGameObject* pMissile = new CGameObject;
pMissile->AddComponent(new CTransform);
pMissile->AddComponent(new CMeshRender);
pMissile->AddComponent(new CMissileScript);
pMissile->Transform()->SetRelativePos(Transform()->GetRelativePos() + Vec3(0.f, 0.5f, 0.f) * Transform()->GetRelativeScale() );
pMissile->Transform()->SetRelativeScale(Vec3(50.f, 50.f, 50.f));
pMissile->MeshRender()->SetMesh(CResMgr::GetInst()->FindRes<CMesh>(L"RectMesh"));
pMissile->MeshRender()->SetMaterial(CResMgr::GetInst()->FindRes<CMaterial>(L"Std2DMtrl"));
CMissileScript* pMissileScript = pMissile->GetScript<CMissileScript>();
if (nullptr != pMissileScript)
pMissileScript->SetSpeed(500.f);
// 레벨에 추가
CLevel* pCurLevel = CLevelMgr::GetInst()->GetCurLevel();
pCurLevel->AddGameObject(pMissile, L"PlayerProjectile", false);
}
게임 오브젝트로부터 스크립트 가져오기
CGameObject 클래스에서 아래와 같이 코드를 추가해준다.
GetScript 템플릿 함수
// 헤더 파일
template<typename T>
inline T* CGameObject::GetScript()
{
for (size_t i = 0; i < m_vecScript.size(); ++i)
{
T* pScript = dynamic_cast<T*> (m_vecScript[i]);
if (nullptr != pScript)
return pScript;
}
return nullptr;
}
동기화 처리
tick함수가 도는 도중에 즉 프레임 도중에 레벨에 오브젝트를 추가하는 행위는 매우 위험한 행위이다.
그래서 이번 프레임에서 생성을 예약하고, 다음 프레임에서 객체를 생성하고 함께 돌 수 있도록 조치를 취해야한다.
Prefab
매번 리소스를 new해주지 않고, 복사생성자를 활용하는 방법을 사용하면 된다.
그리고 객체는 각 객체마다의 Transform을 가져야 하므로 복사생성자를 사용하는 과정에서 같은 Transform을 가지지 않도록 조치를 해주어야한다.
이때 리소스 중에서 Prefab을 활용한다.
게임 오브젝트를 리소스화 시켜서 원형 프로젝트를 필요할 때마다 복사해서 사용하는 방식이다.
// 헤더파일
#pragma once
#include "CRes.h"
class CGameObject;
class CPrefab :
public CRes
{
private:
CGameObject* m_ProtoObj;
public:
CGameObject* Instantiate();
private:
virtual int Load(const wstring& _strFilePath) { return S_OK; }
public:
virtual int Save(const wstring& _strRelativePath) { return S_OK; }
private:
virtual void UpdateData() override {}
public:
void RegisterProtoObject(CGameObject* _Proto);
public:
CPrefab();
~CPrefab();
};
// cpp 파일
#include "pch.h"
#include "CPrefab.h"
#include "CGameObject.h"
CPrefab::CPrefab()
: CRes(RES_TYPE::PREFAB)
, m_ProtoObj(nullptr)
{
}
CPrefab::~CPrefab()
{
if (nullptr != m_ProtoObj)
delete m_ProtoObj;
}
CGameObject* CPrefab::Instantiate()
{
return m_ProtoObj->Clone();
}
void CPrefab::RegisterProtoObject(CGameObject* _Proto)
{
// 원본 오브젝트는 레벨 소속이 아니여야 한다.
assert(-1 == _Proto->GetLayerIndex());
m_ProtoObj = _Proto;
}
RegisterProtoObject 함수를 통해서 원본 오브젝트를 등록한다.
이때, 해당 함수가 받는 _Proto 인자는 레벨에 등록되어 있지 않은 인자여야 한다.
따라서 GetLayerIndex의 결과가 -1이 아닐 경우에는 assert 처리를 해준다.
CResMgr
한편, 미리 필요한 Prefab을 리소스 매니저에서 제작해준다.
CreateDefaultPrefab 함수가 그 역할을 한다.
// 헤더 파일 중에서
private:
void CreateDefaultPrefab();
// cpp 파일 중에서
#include "CGameObject.h"
#include "components.h"
#include "CMissileScript.h"
void CResMgr::CreateDefaultPrefab()
{
CGameObject* pMissile = new CGameObject;
pMissile->AddComponent(new CTransform);
pMissile->AddComponent(new CMeshRender);
pMissile->AddComponent(new CMissileScript);
pMissile->Transform()->SetRelativeScale(Vec3(50.f, 50.f, 50.f));
pMissile->MeshRender()->SetMesh(CResMgr::GetInst()->FindRes<CMesh>(L"RectMesh"));
pMissile->MeshRender()->SetMaterial(CResMgr::GetInst()->FindRes<CMaterial>(L"Std2DMtrl"));
CMissileScript* pMissileScript = pMissile->GetScript<CMissileScript>();
if (nullptr != pMissileScript)
pMissileScript->SetSpeed(500.f);
Ptr<CPrefab> MissilePrefab = new CPrefab;
MissilePrefab->RegisterProtoObject(pMissile);
AddRes<CPrefab>(L"MissilePrefab", MissilePrefab);
}
CPlayerScript 클래스
플레이어 스크립트 클래스에서는 이제는 Shoot 함수에서 Prefab을 복사해와서 생성하는 방식으로 사용하게 된다.
void CPlayerScript::Shoot()
{
// 미사일 프리팹 참조
Ptr<CPrefab> pMissilePrefab = CResMgr::GetInst()->FindRes<CPrefab>(L"MissilePrefab");
Vec3 vMissilePos = Transform()->GetRelativePos() + Vec3(0.f, 0.5f, 0.f) * Transform()->GetRelativeScale();
CGameObject* pCloneMissile = pMissilePrefab->Instantiate();
// 레벨에 추가
SpawnGameObject(pCloneMissile, vMissilePos, L"PlayerProjectile");
}
그래서 위와 같이 shoot 함수가 변경이 된다.