WIN32API FrameWork/한단계씩 직접 구현

25. GameObject의 주/종관계 형성

hyrule 2022. 5. 22. 18:07

http://hyrule.tistory.com/111 

 

*** 공부 방법 ***

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));

정상적으로 작동하는것을 확인
GameFrameworkStepbyStep_25_MasterSlave.zip
0.04MB