WinApi/Brotato 모작

[Win32 API Brotato 모작] 4. Core & Scene

Vfly 2025. 3. 11. 20:19

이번에는 CCore 클래스의 progess 함수에 대해 살펴보겠다.

void CCore::progress() {

	// ============
	//	Manager Update
	// ============
	CTimeMgr::GetInstance()->update();
	CkeyMgr::GetInstance()->update();
	CCamera::GetInstance()->update();
	CSoundMgr::GetInstance()->update();

	// ============
	//	Scene Update
	// ============
	CSceneMgr::GetInstance()->update();

	// 충돌 체크. 
	CCollisionMgr::GetInstance()->update();

	//UI 이벤트 체크
	CUIMgr::GetInstance()->update();
	ItemMgr::GetInstance()->update();

	// ============
	//	Rendering
	// ============

	// Draw시작.
	ID2D1HwndRenderTarget* pRenderTarget = Direct2DMgr::GetInstance()->GetRenderTarget();
	//한 프레임당 그림 그리기 시작. 
	pRenderTarget->BeginDraw();
	Clear();

	//Direct2D 버전 렌더링
	CSceneMgr::GetInstance()->render(pRenderTarget);
	
	//한 프레임 렌더링 끝. 
	HRESULT hr = pRenderTarget->EndDraw();
	if (FAILED(hr)) {
		MessageBox(nullptr, L"렌더링 실패!", L"오류", MB_OK);
	}
	// Draw 끝. 

	//렌더링까지 전부 다 끝난 후에 EventMgr에서 이벤트 지연 처리.
	// ============
	//	이벤트 지연 처리.
	// ============
	CEventMgr::GetInstance()->update();
}

 

wWinMain() 함수에서 메세지를 처리하지 않는 대부분의 시간은 위의 progess() 함수가 수행된다. 

progess() 함수가 한 번 호출되고 종료되는 상황이 1프레임의 작업이 된다.

 

이때 SingleTon으로 구현된 매니저들의 update()CSceneMgr에 등록된 현재 씬(장면)의 render() 함수를 호출해 현재 씬의 등록된 객체들의 update() render() 를 호출해 객체들의 상태를 변화시키거나 화면에 그려주는 작업을 진행한다.

 

이후 마지막에 지금까지 EventMgr에 등록된 이벤트들을 처리해 준다.


<CScene.h> , <CScene.cpp>

 

CScene 클래스는 직접적으로 객체를 할당할 수 없는 추상 클래스로 구현하였다.

이유는  모든 씬(장면)들은 화면에 진입했을 때와 화면을 탈출할 때의 해야 할 일들이 전부 다를것이다.  그렇기 때문에 일부 함수들은 순수 가상 함수로 표현했기 때문에 CScene클래스는 상속을 받아 사용해야하는 클래스로 제작하였다.

 

CScene 클래스를 상속받는 클래스는 다음과 같다.

  • Scene_Main : 메인 화면이 되는 씬(장면).

게임 실행시 나오는 메인화면

  • Scene_Select_Character : 캐릭터를 선택하는 씬

캐릭터 선택, 랜덤을 고를시 6개 중 랜덤하게 선택됨

  • Scene_Select_Weapon : 무기를 선택하는 씬

무기 선택, 랜덤을 고를시 3개 중 랜덤하게 선택됨

  • Scene_Start : 전투가 일어나는 씬

전투 플레이
전투 중 ESC를 눌렀을 때

  • Scene_Shop : 상점 씬

상점

  • Scene_Run_End : 게임 실패 or 성공 시 나오는 결산 화면

엔딩

 

#pragma once

#include "global.h"
#include "CMonFactory.h"
//전방선언하는 이유는 컴파일 속도에 영향을 주지 않기 위해. 
class CObject;
class CMonster;

class CScene
{
	static bool isPause;
private:
	vector<CObject*> m_arrObj[(UINT)GROUP_TYPE::END];		//벡터 안에 모든 오브젝트 집어 넣겠다. 이런 특성(요소)를 가진만큼 나눠주기.
	//달리말하면 그룹 갯수만큼 나눠주기.

	SCENE_TYPE		m_eType;
	wstring			m_strName; //Scene 이름

	CObject*		m_pPlayer;		//Player

public:
	void SetName(const wstring& _strName) { m_strName = _strName; }
	const wstring& GetName() { return m_strName; }

	SCENE_TYPE GetSceneType() { return m_eType; }
	void SetSceneType(SCENE_TYPE _eType) { m_eType = _eType; }
	CObject* GetPlayer() { return m_pPlayer; }

	virtual void start();
	virtual void update();
	virtual void finalupdate();

	virtual void render(ID2D1HwndRenderTarget* _pRender);

	virtual void Enter() = 0;		//해당 Scene에 진입 시 호출.
	virtual void Exit() = 0;		//해당 Scene에 탈출 시 호출.

public:
	//클래스는 헤더에 구현하면 인라인 처리가 됨. 
	//따라서 함수 호출 비용이 사라짐. 
	void AddObject(CObject* _pObj, GROUP_TYPE _eType)
	{
		m_arrObj[(UINT)_eType].push_back(_pObj);
	}

	void RegisterPlayer(CObject* _pPlayer) { m_pPlayer = _pPlayer; }

	const vector<CObject*>& GetGroupObject(GROUP_TYPE _eType) 
	{
		return m_arrObj[(UINT)_eType];
	}
	vector<CObject*>& GetUIGroup() { return m_arrObj[(UINT)GROUP_TYPE::UI]; }

	void DeleteGroup(GROUP_TYPE _eGroup);
	void DeleteAll();

	ID2D1Bitmap* CreateCompositeMapBitmap(const wstring &tag);

	static void ChangePause(bool _bPause);
	static bool GetPause() { return isPause; }
	void AllDropItemRetrieve();

public:
	CScene();

	//소멸자의 가상함수 해줘야함. 씬 매니저가 모든 Scene을 부모 포인터로 관리함.
	// CSceneMgr에서 씬을 소멸시킬때, 소멸자는 부모인 CScene만 호출됨.  
	virtual ~CScene();
    
	friend class CMonster;
};

 

#include "pch.h"
#include "CScene.h"
#include "CObject.h"
#include "func.h"
#include "CTile.h"
#include "CTimeMgr.h"
#include "CResMgr.h"
#include "CPathMgr.h"
#include "CCamera.h"
#include "CCore.h"
#include "CTexture.h"
#include "Direct2DMgr.h"
#include "CMonster.h"
#include "CGround.h"
#include "CImage.h"

bool CScene::isPause = false;

CScene::CScene()

	: m_iTileX(0)
	, m_iTileY(0)
	, m_pPlayer(nullptr)
{
}


CScene::~CScene()
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		for (size_t objIDX = 0; objIDX < m_arrObj[typeIDX].size(); objIDX++) {
			//m_arrObj[그룹][물체] 삭제. 
			delete m_arrObj[typeIDX][objIDX];
		}
		
		//씬이 사라지면, 그 씬의 벡터들도 다 사라짐. 
		//STL의 RAII가 알아서 삭제하기 때문. 
	}
}

void CScene::AllDropItemRetrieve()
{
	UINT typeIDX = (UINT)GROUP_TYPE::DROP_ITEM;
	for (size_t objIDX = 0; objIDX < m_arrObj[typeIDX].size(); objIDX++) {

		CMonster* dropItem = static_cast<CMonster*>(m_arrObj[typeIDX][objIDX]);

		if (dropItem->Getinfo().m_eMonType == MON_TYPE::DROP_ITEM) {
			dropItem->SetRecogRange(9999.f);
			dropItem->SetSpeed(500.f);
		}
	}
}

void CScene::start()
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		for (size_t objIDX = 0; objIDX < m_arrObj[typeIDX].size(); objIDX++) {
			m_arrObj[typeIDX][objIDX]->start();
		}
	}
}

void CScene::update()
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		for (size_t objIDX = 0; objIDX < m_arrObj[typeIDX].size(); objIDX++) {
			if (!m_arrObj[typeIDX][objIDX]->IsDead()) {
				m_arrObj[typeIDX][objIDX]->update();
			}
		}
	}
}
//움직이고 했던 걸, 마지막으로 업데이트 함. 
//충돌체가 플레이어 따라가게 함, 충돌 처리. 
void CScene::finalupdate()
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		for (size_t objIDX = 0; objIDX < m_arrObj[typeIDX].size(); objIDX++) {

			//Final Update는 돌려줌. 내부적으로 Component들의 마무리 단계 업데이트(충돌처리나, 참조관계등)
			m_arrObj[typeIDX][objIDX]->finalupdate();
		}
	}
}

void CScene::render(ID2D1HwndRenderTarget* _pRender)
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		auto ObjVecIter = m_arrObj[typeIDX].begin();

		for (; ObjVecIter != m_arrObj[typeIDX].end();) {
			if (!(*ObjVecIter)->IsDead()) {
				(*ObjVecIter)->render(_pRender);
				ObjVecIter++;
			}
			else {
				//Dead상태일 경우엔 렌더링에서 삭제하기. 
				ObjVecIter = m_arrObj[typeIDX].erase(ObjVecIter);
			}

		}
	}
}

void CScene::DeleteGroup(GROUP_TYPE _eGroup)
{
	Safe_Delete_Vec<CObject*>(m_arrObj[(UINT)_eGroup]);
}

void CScene::DeleteAll()
{
	for (UINT GroupIdx = 0; GroupIdx < (UINT)GROUP_TYPE::END; GroupIdx++) {
		if (GroupIdx == (UINT)GROUP_TYPE::PLAYER)
		{
			m_arrObj[(UINT)GROUP_TYPE::PLAYER].clear();
			continue;
		}
		else if (GroupIdx == (UINT)GROUP_TYPE::WEAPON) {
			m_arrObj[(UINT)GROUP_TYPE::WEAPON].clear();
		}
		DeleteGroup((GROUP_TYPE)GroupIdx);
	}
}

ID2D1Bitmap* CScene::CreateCompositeMapBitmap(const wstring &tag)
{
	Direct2DMgr* pD2DMgr = Direct2DMgr::GetInstance();
	auto splitBitmaps = Direct2DMgr::GetInstance()->GetSplitBitmaps(tag);

	const int gridCount = 36;
	// 각 타일는 원래 MakeTile에서 다음과 같이 배치됨:
	// 위치: (TILE_SIZE/4 + (TILE_SIZE/2 * tileX), TILE_SIZE/4 + (TILE_SIZE/2 * tileY))
	// 크기: TILE_SIZE/2 × TILE_SIZE/2
	// (TILE_SIZE = 64 → offset = 16, tileDrawSize = 32)
	const float offset = 16.0f;
	const float tileDrawSize = 32.0f;

	// 전체 합성 비트맵의 크기 계산: 
	// 좌측 여백 offset에서 시작해 gridCount * tileDrawSize 만큼 확장.
	const UINT compositeWidth = (UINT)(offset + tileDrawSize * gridCount);
	const UINT compositeHeight = compositeWidth; // 정사각형

	// 3. 오프스크린 렌더 타겟(비트맵 렌더 타겟) 생성
	ID2D1Bitmap* pCompositeBitmap = nullptr;
	ComPtr<ID2D1BitmapRenderTarget> pBitmapRT = nullptr;
	HRESULT hr = Direct2DMgr::GetInstance()->GetRenderTarget()->CreateCompatibleRenderTarget(
		D2D1::SizeF((FLOAT)compositeWidth, (FLOAT)compositeHeight),
		&pBitmapRT
	);
	if (FAILED(hr))
		return nullptr;

	pBitmapRT->BeginDraw();
	// 배경색 (예: 흰색)으로 클리어
	pBitmapRT->Clear(D2D1::ColorF(D2D1::ColorF::Black));

	// 4. 그리드 순회: 각 타일의 서브 비트맵 인덱스 결정 후 합성
	for (int tileY = 0; tileY < gridCount; tileY++)
	{
		for (int tileX = 0; tileX < gridCount; tileX++)
		{
			int tileIdx = 0;
			int rest = 0;
			// 모서리 타일 처리
			if (tileY == 0 && tileX == 0)	tileIdx = 0;
			else if (tileY == 0 && tileX == 35)	tileIdx = 7;
			else if (tileY == 35 && tileX == 0)	tileIdx = 56;
			else if (tileY == 35 && tileX == 35)tileIdx = 63;
			// 상단 테두리
			else if (tileY == 0 && (tileX >= 1 && tileX <= 34))
			{
				rest = tileX % 7;
				if (rest == 0) rest++;
				tileIdx = rest;
			}
			// 하단 테두리
			else if (tileY == 35 && (tileX >= 1 && tileX <= 34))
			{
				rest = tileX % 7;
				if (rest == 0) rest++;
				tileIdx = rest + 56;
			}
			// 좌측 테두리
			else if (tileX == 0 && (tileY >= 1 && tileY <= 34))
			{
				rest = tileY % 7;
				if (rest == 0) rest++;
				tileIdx = rest * 8;
			}
			// 우측 테두리
			else if (tileX == 35 && (tileY >= 1 && tileY <= 34))
			{
				rest = tileY % 7;
				if (rest == 0) rest++;
				tileIdx = rest * 8 + 7;
			}
			// 내부 타일: 테두리 제외하고 랜덤하게 (테두리 인덱스 제외)
			else
			{
				int randX = rand() % 7;
				if (randX == 0) randX = 1;
				int randY = rand() % 7;
				if (randY == 0) randY = 1;

				tileIdx = randY * 8 + randX;
			}

			// 5. 타일의 배치 위치 계산
			// MakeTile에서의 위치: X = offset + tileX * tileDrawSize, Y = offset + tileY * tileDrawSize
			float destLeft = offset + tileX * tileDrawSize;
			float destTop = offset + tileY * tileDrawSize;
			D2D1_RECT_F destRect = D2D1::RectF(destLeft, destTop, destLeft + tileDrawSize, destTop + tileDrawSize);

			// 6. 서브 비트맵의 원본 영역: 여기서는 전체 서브 이미지 (크기 TILE_SIZE×TILE_SIZE)
			D2D1_RECT_F srcRect = D2D1::RectF(0, 0, (FLOAT)TILE_SIZE, (FLOAT)TILE_SIZE);

			// 7. 타일 텍스처에서 해당 서브 이미지를 DrawBitmap (타일 이미지가 64x64에서 32x32로 축소됨)
			if (tileIdx >= 0 && tileIdx < (int)splitBitmaps.size())
			{
				pBitmapRT->DrawBitmap(
					splitBitmaps[tileIdx],   // 해당 서브 비트맵
					destRect,                // 합성될 위치 및 크기
					1.0f,                    // 불투명도
					D2D1_BITMAP_INTERPOLATION_MODE_LINEAR,
					srcRect                  // 원본 영역
				);
			}
		}
	}

	hr = pBitmapRT->EndDraw();
	if (FAILED(hr))
		return nullptr;

	// 8. 오프스크린 렌더 타겟에서 최종 커다란 비트맵 추출
	hr = pBitmapRT->GetBitmap(&pCompositeBitmap);
	if (FAILED(hr))
		return nullptr;

	return pCompositeBitmap;
}

//static 함수. 
void CScene::ChangePause(bool _bPause)
{
	if (true == _bPause) {
		CScene::isPause = _bPause;
		CTimeMgr::GetInstance()->SetTimeScale(0.f);
	}
	else if (false == _bPause) {
		CScene::isPause = _bPause;
		CTimeMgr::GetInstance()->SetTimeScale(1.f);
	}
}

 

CScene 클래스 코드 설명

CScene 클래스는 게임의 특정 씬(Scene)을 관리하는 역할을 한다. 씬은 플레이어, 몬스터, 아이템 등 다양한 오브젝트를 포함하며, 이 클래슨느 다음과 같은 작업을 수행한다.

  • 씬 내 오브젝트 관리 ( 생성, 삭제, 업데이트 등 )
  • 씬의 렌더링 처리
  • 타일 맵 생성 및 합성
  • 일시정지 ( Pause ) 기능

 

주요 메서드

  • AllDropItemRetrieve() 
    • 웨이브 시간이 모두 흐른 뒤 플레이어에게 드랍된 아이템이 모두 빨려들어가게 하기 위한 함수 
    • GROUP_TYPE::DROP_ITEM인 오브젝트를 순회하며 처리
    • DROP_ITEM의 인식범위와 속도를 설정
  • start() , update() , finalupdate()
    • start() : 씬 내의 모든 오브젝트의 start() 함수를 호출하여 초기화를 진행
    • update() : 모든 오브젝트들의 상태를 업데이트 한다. 죽은 오브젝트는 업데이트하지 않음
    • finalupdate() : 충돌 처리나 참조 관계를 정리
  • render()
    • 모든 오브젝트를 렌더링한다.
    • 죽은 오브젝트는 벡터에서 제거하여 메모리를 정리한다.
  • DeleteGroup(), DeleteAll()
    • 특정 그룹의 오브젝트를 삭제하거나, 모든 오브젝트를 삭제한다.
    • 단, PLAYER와 WEAPON 오브젝트들은 메모리를 해제하지 않고 살려둔다.
  • CreateCompositeMapBitmap()
    • 타일 맵을 합성하여 하나의 비트맵으로 생성하는 함수다.
  • ChangePause()
    • 게임 일시정지 상태를 변경
    • 일시정지 시 시간 스케일을 0으로 설정하여 모든 동작이 멈추도록 한다.

 

다음 포스트에서는 오브젝트들에 대해 알아보겠다.