25. GameObject의 주/종관계 형성
*** 공부 방법 ***
1. 코딩을 해야 하는 부분은 첫 부분에 변수나 함수, 메소드에 대한 선언이 코드블럭으로 표시되어 있다. //ex) MakeFunction(); 2. 코드블럭 하단에는 해당 선언에 대한 구현 로직이 작성되어 있다. 처
hyrule.tistory.com
- 조만간 만들어볼 스킬에 쓰일 기능.
- 모든 CGameObject는 자신을 생성한 객체에 대한
m_MasterObject 포인터와, 자신이 생성한 객체에 대한 m_SlaveObject 포인터를 가진다.
-- Master 포인터는 하나이고(자신을 생성한 객체는 하나일수밖에 없으므로)
-- Slave 포인터는 여러개일 수 있으므로 데이터를 모아둘 자료구조를 사용해야 한다.
(Hint - 데이터의 입출력이 잦다)
//CGameObject.h
protected:
CGameObject* m_MasterObject;
std::list<CSharedPtr<CGameObject>> m_SlaveObject;
- 굳이 사용하지 않아도 된다. 입력하지 않아도 사용 가능하게 만들어야 한다.
-- 이렇게 해놓으면, 예를 들어 만약 플레이어가 죽을 때 같이 사라져야 하는 객체들이면 주종관계를 형성하여 전부 제거가 되고,
같이 사라질 필요가 없는 객체면 주종관계를 형성하지 말고 그냥 두면 된다.
- 만약 CScene::CreateObject() 메소드로 void 포인터로 CGameObject 포인터가 전달된다면, 이 때 처리해준다.
//Scene.h
public:
template <typename T>
T* CreateObject(const std::string& Name = "GameObject",
void* Master = nullptr)
{
//들어온 T타입의 게임오브젝트를 생성한다.
T* Obj = new T;
//초기화 함수를 작동시키고 실패하면 다시 제거하고 nullptr을 반환한다.
if (!Obj->Init((CGameObject*)Master))
{
//방금 만들어진 변수여서 굳이 딴데 참조받는 곳이 없으므로 SAFE_DELETE
SAFE_DELETE(Obj);
return nullptr;
}
//자신을 생성한 CScene을 등록한다.
Obj->SetOwnerScene(this);
//이름을 설정해준다.
Obj->SetName(Name);
//생성된 게임오브젝트를 관리하는 m_ObjList에 업캐스팅하여 삽입한다.
m_ObjList.push_back((CGameObject*)Obj);
if (Master)
{
AddSlave<CGameObject>((CGameObject*)Master, (CGameObject*)Obj);
}
return Obj;
}
//오류가 발생하여 Slave를 등록하는 메소드는 따로 만든 뒤
//CreateObject 메소드에서 호출하였다.
template <typename T>
void AddSlave(T* Master, T* Obj)
{
Master->AddObj((CGameObject*)Obj);
}
-- 초기화 메소드인 Init()에 만약 생성한 객체 주소가 들어온다면,
생성된 객체는 자신을 생성한 CGameObject의 주소를 m_MasterObject에 등록한다.
-- Init() 메소드의 인자를 전부 바꿔주어야 한다!
-- CGameObject의 자식 클래스라면, 부모 클래스의 Init() 메소드를 호출하여 최종적으로 CGameObject의 Init() 함수에 도달하게 한 뒤, 여기에서 m_MasterObject를 추가해주는 방법으로 구현해보자
--- 이렇게 해주면 나중에 초기화할 때 상위오브젝트에서 초기화해주는 변수들도 싹 초기화되므로 오류 발생확률이 적어진다.
- CF) 인자의 기본값 설정은 '선언'에서만 해주면 된다. '정의' 부분에서는 빼줘야 에러가 발생하지 않음!!
//Bullet.h
public:
bool Init(CGameObject* Obj = nullptr);
//bullet.cpp
bool CBullet::Init(CGameObject* Obj)
{
//CBullet의 부모 클래스의 Init() 메소드를 호출
CGameObject::Init(Obj);
//..이외의 코드들..
return true;
}
▲CGameObject로부터 상속을 받는 모든 객체들의 초기화 메소드를 이렇게 변경하였다.
//CGameObject.cpp
bool CGameObject::Init(CGameObject* Obj)
{
//그럼 최종적으로 CGameObject::Init() 메소드로 타고 들어와서
//Obj 주소가 존재하면 해당 주소를 등록해준다.
if (Obj)
m_MasterObject = Obj;
return true;
}
- 이제 마지막으로 주인 오브젝트 / 종속 오브젝트가 삭제되었을 떄의 처리를 해주어야 한다.
-- 자신이 제거되어야 할 때 주인 오브젝트가 존재한다면, 주인 오브젝트의 종속 오브젝트 목록에 자신이 있을 것이므로 목록에서 자신을 삭제해주어야 한다.
-- 이후 종속 오브젝트를 확인한다. 종속 오브젝트가 남아있을 경우는, 종속 오브젝트의 m_Active를 false로 변경하여 삭제 대기를 시켜놓는다.
- 게임오브젝트는 현재 m_Active 변수를 통해 생성/삭제가 되고 있으므로, 해당 변수가 false가 되었을 때 처리를 해주면 된다.
-> 해당 로직은 CScene::Update() 메소드에 구현되어 있다. 이부분을 수정해주자.
//Scene.cpp
void CScene::Update(float DeltaTime)
{
auto iter = m_ObjList.begin();
auto iterEnd = m_ObjList.end();
while (iter != iterEnd)
{
if (!(*iter)->GetActive())
{
//주인 오브젝트의 종속 오브젝트 목록에서 자신을 삭제
if ((*iter)->m_MasterObject)
{
CGameObject* master = (*iter)->m_MasterObject;
auto Masteriter = master->m_SlaveObject.begin();
auto MasteriterEnd = master->m_SlaveObject.end();
while (Masteriter != MasteriterEnd)
{
if ((*iter) == (*Masteriter))
{
master->m_SlaveObject.erase(Masteriter);
break;
}
++Masteriter;
}
}
//종속 오브젝트를 순회하면서 자신과의 연결을 끊고 SetActive를 false로 전환
auto SlaveIter = (*iter)->m_SlaveObject.begin();
auto SlaveIterEnd = (*iter)->m_SlaveObject.end();
while (SlaveIter != SlaveIterEnd)
{
(*SlaveIter)->SetActive(false);
(*SlaveIter)->m_MasterObject = nullptr;
++SlaveIter;
}
//삭제 대기를 시켜놓았으므로 리스트를 비워준다.
(*iter)->m_SlaveObject.clear();
iter = m_ObjList.erase(iter);
iterEnd = m_ObjList.end();
continue;
}
else if (!(*iter)->GetEnable())
{
++iter;
continue;
}
(*iter)->Update(DeltaTime);
++iter;
}
}
void CScene::Render(HDC hDC, float DeltaTime)
{
auto iter = m_ObjList.begin();
auto iterEnd = m_ObjList.end();
while (iter != iterEnd)
{
//제거는 모두 Update()에서 관리함. 여긴 그냥 냅둔다.
if (!(*iter)->GetActive())
{
++iter;
continue;
}
else if (!(*iter)->GetEnable())
{
++iter;
continue;
}
(*iter)->Render(hDC, DeltaTime);
++iter;
}
}
- 이제 잘 작동하는지 테스트를 위해, 주종 관계가 가장 많이 쓰일 CGameObject인 CBullet을 이 방식으로 생성해보자.
//Player.cpp
void CPlayer::GunFire()
{
CBullet* Bullet = m_OwnerScene->CreateObject<CBullet>("PlayerBullet", this);
Bullet->SetSpeedDir(1000.f, m_Dir);
Bullet->SetPos(m_GunTipPos);
}
그리고 정상 작동하는지 확인하기 위해서 CPlayer::Render() 메소드에 해당 코드를 잠깐 추가한다.
char Text[64] = {};
sprintf_s(Text, "Number Of Slave Array: %d", (int)m_SlaveObject.size());
TextOutA(hDC, 1, 30, Text, (int)strlen(Text));