WIN32API FrameWork/한단계씩 직접 구현

15. 참조 카운트로 게임오브젝트 관리

hyrule 2022. 5. 18. 16:44

https://hyrule.tistory.com/111 

 

*** 공부 방법 ***

1. 코딩을 해야 하는 부분은 첫 부분에 변수나 함수, 메소드에 대한 선언이 코드블럭으로 표시되어 있다. //ex) MakeFunction(); 2. 코드블럭 하단에는 해당 선언에 대한 구현 로직이 작성되어 있다. 처

hyrule.tistory.com

 


gameinfo.h
SAFE_RELEASE(p)

앞으로 RefCount를 사용하는 객체들은 직접 delete를 하지 않는다.

대신, 수동으로 RefCount를 하나 내려줄 수 있게, Release() 함수를 호출하는 매크로를 하나 만들어 놓자.

더보기
#define SAFE_RELEASE(p) if(p) { p->Release(); p = nullptr; }

//기존 CGameObject 클래스 변경
class CGameObject: public CRef

이제, 모든 CGameObject 관련 객체의 메모리는 Reference Count로 관리한다.


class CScene

 

앞으로 CGameObject는 모두 CScene에서 관리한다.

 

예를 들어, 메인 메뉴 -> 스테이지 -> 결과창 순으로 게임이 진행된다 치면,

메인 메뉴 씬이 CSceneManager에서 로딩되면, 메인 메뉴 씬에 필요한 게임 오브젝트만 로딩하여 생성한다.

이후 스체이지로 씬이 교체되면, 씬에서 사용되는 게임오브젝트를 로드하고, 메인 메뉴 씬을 제거한다.

-> Reference Count가 메모리를 관리하므로, 누수가 나지 않고, 효율적으로 메모리를 사용할 수 있게 된다.

 

그렇게 하기 위해서는 씬 별로 게임오브젝트의 목록을 관리해줄 자료구조가 있어야 한다.

총알같이 빠르게 생성되었다 빠르게 사라지는 것들도 모두 게임 오브젝트이므로, 삽입과 삭제를 빠르게 처리할 수 있는 자료구조가 필요하다.

모든 개별 씬의 부모인 CScene 클래스에 CGameObject의 포인터(SharedPtr)를 원소로 가지는 자료구조를 선언하자.

▼ 참고

더보기
std::List<CSharedPtr<class CGameObject>> m_ObjList;

CGameObject를 상속받는 모든 클래스들도 앞으로 폐쇄적으로 관리하도록 해준다.

모든 CGameObject 상속 클래스들의 생성/소멸자를 상속 관계에서만 사용할수 있도록 해주고,

CScene에서만 자유롭게 관리할 수 있도록 해주자.

더보기
  • CGameObject를 상속받는 모든 클래스
    • friend class CScene; 추가
    • 생성/소멸자를 public: -> protected:로 변경

CreateObject()

게임오브젝트 목록을 만들었으면, 이제 이 게임오브젝트들을 목록에 삽입/삭제해주는 메소드도 필요할 것이다.

 

CScene에서 CGameObject를 관리하는 리스트를 생성했으므로, 

마찬가지로 CScene에서 CGameObject를 추가하는 메소드를 만든다.

 

어떤 타입의 인자가 들어올지 모르므로, 템플릿을 사용해야 한다.

들어온 T의 포인터를 반환한다.
템플릿을 사용했으므로 헤더에 선언과 정의를 모두 작성해주어야 할 것이다.

 

인자로는 이름으로 등록해 줄 문자열을 받고, 값이 들어오지 않으면 기본값으로 "GameObject"를 전달한다.

 

게임오브젝트 T를 동적 할당하고, 인자로 받은 이름을 설정해준다.

 

또한 게임오브젝트의 초기화 메소드를 실행시키고,

초기화에 실패했을 경우 동적 할당을 도로 해제하고 nullptr을 반환한다.

 

초기화에 성공하면, 해당 게임오브젝트를 아까 생성했던 CScene의 리스트에 업캐스팅하여 삽입하고,

이 주소를 반환한다.

▼참고

더보기
//Scene.h

public:
	template <typename T>
	T* CreateObject(const std::string& Name = "GameObject")
	{
		T* Obj = new T;

		Obj->SetName(Name);

		if (!Obj->Init())
		{
			SAFE_DELETE(Obj);
			return nullptr;
		}

		m_ObjList.push_back((CGameObject*)Obj);

		return Obj;
	}

 


 

이제 CGameObject의 처리는 이렇게 하게 된다.

프로그램을 실행하면, CSceneManager의 Init() 메소드에서 CreateScene() 함수를 통해 CMainScene을 생성하고 있다.

가장 먼저 나오게 되는 화면인 CMainScene에 우선 CPlayer을 생성해 보자.

 

CMainScene 에서 Cplayer을 포함시킨 뒤, 초기화 때 방금 만든 메소드를 사용하면 끝이다.

더보기
#include "../GameObject/Player.h"

bool CMainScene::Init()
{
	CreateObject<CPlayer>("Player");
    
	return true;
}

class CRef

편의를 위해 CRef 클래스에 두 가지 기능을 추가한다.

 

  • m_Enable: 활성화/비활성화 -> 잠깐동안만 Update()와 Render() 메소드에서 제외
  • m_Active: 살아있는지/죽었는지 -> false로 바뀌면 다음 Update() 때 아예 제거
  • 두 변수 모두 기본값 true로

또한 두 변수의 값을 받아오거나 바꾸는 메소드도 추가한다.

▼참고

더보기
//Ref.h

protected:
	bool	m_Enable;	// 활성, 비활성
	bool	m_Active;	// 살아 있는지 죽었는지

public:
	bool GetEnable()	const
	{
		return m_Enable;
	}

	bool GetActive()	const
	{
		return m_Active;
	}
    
	void SetEnable(bool Enable)
	{
		m_Enable = Enable;
	}

	void SetActive(bool Active)
	{
		m_Active = Active;
	}
//Ref.cpp

CRef::CRef()	:
	m_RefCount(0),
	m_Enable(true),
	m_Active(true),
	m_TypeID(0)
{
}

class CScene

CScene에서는 모든 CGameObject의 리스트를 가지고 있다.

그렇다면 해당 리스트 iterator을 만들고,

처음부터 끝까지 순회하면서 Update() 메소드와 Render() 메소드를 호출하면 된다.

직접 Render() 메소드에 생성된 게임오브젝트를 일일히 추가해야 하는 수고가 없어진 셈이다.

 

* Update(), Render() 메소드를 호출하기 전에, 

Active 여부를 확인해서 false이면 리스트에서 지워버린다.

이후 Enable 여부를 확인해서 false이면 그냥 스킵해서 지나간다.

▼참고

더보기
void CScene::Update(float DeltaTime)
{
	// std::list<CSharedPtr<class CGameObject>>::iterator
	// auto : 선언과 동시에 무조건 대입을 해주어야 한다.
	// 대입된 타입으로 타입이 선언된다.
	auto	iter = m_ObjList.begin();
	auto	iterEnd = m_ObjList.end();

	for (; iter != iterEnd;)
	{
		if (!(*iter)->GetActive())
		{
			// 리스트에서 제거하는 순간 SharedPtr의 소멸자가 호출되어
			// 카운트가 감소한다.
			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();

	for (; iter != iterEnd;)
	{
		if (!(*iter)->GetActive())
		{
			// 리스트에서 제거하는 순간 SharedPtr의 소멸자가 호출되어
			// 카운트가 감소한다.
			iter = m_ObjList.erase(iter);
			iterEnd = m_ObjList.end();
			continue;
		}

		else if (!(*iter)->GetEnable())
		{
			++iter;
			continue;
		}

		(*iter)->Render(hDC, DeltaTime);

		++iter;
	}
}

 


 

씬 구조는 거의 다 완료되었다.

다음 글에서는 잡다한 설정을 한 후 다다음 글에서 화면에 출력이 가능할 듯

GameFrameworkStepbyStep_15_SceneStructure.zip
1.42MB