WIN32 SceneManager + 중간점검

Date:     Updated:

카테고리:

태그:

8일차 - 중간 점검

  • Default
    • Client.h
    • framework.h
    • Resouce.h
    • targetver.h
  • Engine
    1. Header
      1. define.h
      2. struct.h
    2. Core
      1. CCore.cpp
      2. CCore.h
    3. Manager
      1. KeyMgr
        1. CkeyMgr.cpp
        2. CkeyMgr.h
      2. TimeMgr
        1. CTimeMgr.cpp
        2. CTimeMgr.h
      3. SceneMgr
        1. CSceneMgr.cpp
        2. CSceneMgr.h
    4. Object
      1. CObject.cpp
      2. CObject.h
    5. Scene
      1. Scene_Start
        1. CScene_Start.cpp
        2. CScene_Start.h
      2. Scene_Tool
      3. CScene.cpp
      4. CScene.h
  • 리소스 파일
  • main.cpp
  • pch.h.

  • 오늘은 중간점검을 해보자! 여느때와 같이 변화점이 있는 부분은 볼드처리 했다!!
  • 모든 소스를 볼거다! 엔진 코어의 흐름을 설명할거다.
  1. Header
    1. Define.h

       #pragma once
              
       #define SINGLE(type)public: \
       					static type* GetInstance()\
       					{\
       						static type mgr;\
       						return &mgr;\
       					}\
       					private:\
       						type();\
       						~type();
              
       #define fDT CTimeMgr::GetInstance()->GetfDT()
       #define DT	CTimeMgr::GetInstance()->GetDT()
              
       enum class GROUP_TYPE {
       	DEFAULT, 
       	PLAYER,
       	MISSILE,
       	MONSTER,
              
              	
       	END = 32,
       };
              
       enum class SCENE_TYPE {
       	TOOL,
       	START,
       	STAGE_01,
       	STAGE_02,
              	
       	END,
       };
      

      Object와 SCENE의 TYPE들을 열거형으로 넣어주었다.

      또한 전체적으로는 싱글톤 패턴을 위한 것을 매크로화, 또 DeltaTime을 매크로화, 오브젝트와 씬의 타입을 구분지어줬다고 볼 수 있다.

    2. struct.h

       #pragma once
              
       struct Vec2 {
       	float x;
       	float y;
              
       public:
       	Vec2()
       		: x(0.f)
       		, y(0.f)
       	{}
              
       	Vec2(float _x, float _y)
       		: x(_x)
       		, y(_y)
       	{}
              
       	Vec2(int _x, int _y)
       		: x((float)_x)
       		, y((float)_y)
       	{}
              
       };
      
      • 기존 POINT형은 INT만 지원해서, float형을 지원하는 커스텀 구조체이다
    3. Core

    4. CCore.cpp

       #include "pch.h"
       #include "CCore.h"
              
       #include "CTimeMgr.h"
       #include "CkeyMgr.h"
       #include "CSceneMgr.h"
              
       #include "CObject.h"
              
       //CCore* CCore::g_pInst = nullptr;
       CObject g_obj;
              
       CCore::CCore() 
       	: m_hWnd(0), m_ptResolution{}, m_hDC(0)
       	, m_hBit(0), m_memDC(0)
       {
       }
              
       CCore::~CCore() {
       	//m_hWnd에 엮여있던 m_hDC를 해제해준다. 
       	ReleaseDC(m_hWnd, m_hDC);
              
       	//Create로 만든 것은 DeleteDC로 해야한다. 
       	DeleteDC(m_memDC);
       	DeleteObject(m_hBit);
       }
              
       int CCore::init(HWND _hWnd, POINT _ptResolution) {
       	m_hWnd = _hWnd;
       	m_ptResolution = _ptResolution;
              
       	//해상도에 맞게 윈도우 크기 조정. 
              
       	RECT rt = { 0,0,m_ptResolution.x, m_ptResolution.y };
       	//테두리 포함해서, 크기 보정해주는 것. 
       	//rt 포인터에다가 다시 값을 변환해서 넣어줌. 
       	AdjustWindowRect(&rt, WS_OVERLAPPEDWINDOW, true);
              
       	//타이틀이나 그런 걸 제외한, 순수하게 물체가 그려질 수 있는 영역.
       	//첫 번째로 들어가는 건, 어떤 윈도우인지. 
       	//SetWindow는 타이틀창이나 테두리까지 포함한 크기임. 
       	SetWindowPos(m_hWnd, nullptr, 100, 100, rt.right - rt.left, rt.bottom - rt.top, 0);
              	
       	//어디다가 그려요? m_hWnd에다가 그려요. 
       	m_hDC = GetDC(m_hWnd);
              
       	//이중 버퍼링 용도의 비트맵과 DC를 만든다. 
       	//그림을 그릴 수 있는 건 윈도우에 달려있는, 해상도만큼의 픽셀(묶어서 비트맵이라고 부름)
              
       	//결국 똑같은 해상도로 비트맵을 가지면 되는 거 아닌가? 해서 똑같은 해상도의 비트맵을 만듬. 
       	//Compatible은 호환성. (호환성을 가지고 있는 비트맵)
       	//비트맵 아이디 값을 받았음. 
       	m_hBit = CreateCompatibleBitmap(m_hDC, m_ptResolution.x, m_ptResolution.y);
              
       	//새로 만든 목적지의 DC가 필요함. (어떤 구조인지에 대한 Device Context가 필요함)
       	m_memDC = CreateCompatibleDC(m_hDC);
              
       	//이번엔 그림을 그릴 타겟, 목적지를 교체해주는 것. 위에서 만든 비트맵을 전달해줌. 
       	//되돌아 리턴되는 건 원래 비트맵이지 않을까? 기본적으로 들어가있는 1픽셀짜리 Default 비트맵. 
       	HBITMAP holdBit = (HBITMAP)SelectObject(m_memDC, m_hBit);
       	DeleteObject(holdBit);
              
       	//Manager initialize
       	CTimeMgr::GetInstance()->init();
       	CkeyMgr::GetInstance()->init();
       	CSceneMgr::GetInstance()->init();
              
       	g_obj.SetPos(Vec2((float)(m_ptResolution.x / 2), (float)(m_ptResolution.y / 2)));
       	g_obj.SetScale(Vec2(100, 100));
              
       	return S_OK;
       }
              
       void CCore::progress() {
              
       	//Manager Update
       	CTimeMgr::GetInstance()->update();
       	CkeyMgr::GetInstance()->update();
              
       	CSceneMgr::GetInstance()->update();
              
       	// ============
       	//	Rendering
       	// ============
              
       	//초기화(흰 사각형으로 덧 씌움)
       	Rectangle(m_memDC, -1, -1, m_ptResolution.x + 1, m_ptResolution.y + 1);
              
       	//어디다가 그려야하는지 알려주기. 
       	CSceneMgr::GetInstance()->render(m_memDC);
              
       	BitBlt(m_hDC, 0, 0, m_ptResolution.x, m_ptResolution.y, m_memDC, 0, 0, SRCCOPY);
       }
      
      • 길어지니 변경점만 말하자면, render와 update를 각 Scene에서 하도록 바꾸었다. 이제 여기서는 총괄 매니징만 하는 느낌으로 생각하면 된다!!
    5. CCore.h

       #pragma once
              
       #include "define.h"
              
       class CCore {
       	SINGLE(CCore);
              
       private:
       	HWND	m_hWnd;				//메인 윈도우 핸들. 
       	POINT	m_ptResolution;		//메인 윈도우 해상도
              
       	HDC		m_hDC;				//메인 윈도우에 Draw 할 DC
              
       	HBITMAP m_hBit;				//사본용 . 
       	HDC		m_memDC;
              
       public:
       	int init(HWND _hWnd, POINT _ptResolution);
       	void progress();
              
       public:
       	HWND GetMainHwnd() { return m_hWnd; }
              
       };
      
      • update()와 render()를 지워줬다.
    6. Manager

      1. KeyMgr
        1. CkeyMgr.cpp

                          
           #include "pch.h"
           #include "CkeyMgr.h"
           #include "CCore.h"
                          
           int g_arrVK[(int)KEY::LAST] = {
           	VK_LEFT,	//LEFT,
           	VK_RIGHT,	//RIGHT,
           	VK_UP,		//UP,
           	VK_DOWN,	//DOWN,
                          
           	'Q',
           	'W',
           	'E',
           	'R',
           	'T',
           	'Y',
           	'U',
           	'I',
           	'O',
           	'P',
           	'A',
           	'S',
           	'D',
           	'F',
           	'G',
           	'H',
           	'J',
           	'K',
           	'L',
           	'Z',
           	'X',
           	'C',
           	'V',
           	'B',
           	'N',
           	'M',
                          
           	VK_MENU,
           	VK_CONTROL,
           	VK_LSHIFT,
           	VK_SPACE,
           	VK_RETURN,
           	VK_ESCAPE,
                          
           	//LAST,
           };
                          
           CkeyMgr::CkeyMgr() {
                          
           }
                          
           CkeyMgr::~CkeyMgr() {
                          
           }
                          
           void CkeyMgr::init()
           {
                          
           	for (int keyIDX = 0; keyIDX < (int)KEY::LAST; keyIDX++) {
           		m_vecKey.push_back(tKeyInfo{ KEY_STATE::NONE, false });
           	}
                          
           	//아래처럼 상태확인, 이전에 눌렸는지.
           	//m_vecKey[(int)KEY::LEFT].bPrev
           	//m_vecKey[(int)KEY::LEFT].eState
           }
                          
           //뭔가를 하고 있다가, 창을 바꾸면 키를 떼지는 것까지는 구현해야하지 않을까?
           //TAB상태였던 애는 바로 떼진 AWAY로 가야함 -> NONE
           //HOLD였던 애는 AWAY로 갔다가 -> NONE
                          
           void CkeyMgr::update() {
                          
           	//윈도우 포커싱 알아내기 
           	HWND hMainWnd = CCore::GetInstance()->GetMainHwnd();
           	HWND hWnd = GetFocus();//현재 포커싱 되어 있는 윈도우의 핸들값을 알려줌. 
                          
           	//지금 포커싱 중인게, 이 프로그램의 메인 윈도우라면 같은지 아닌지만 확인해주면 됨.
           	//윈도우 포커싱 중일 때, 키 이벤트 동작. 
           	if (hMainWnd == hWnd) {
                          
           		for (int keyIDX = 0; keyIDX < (int)KEY::LAST; keyIDX++) {
           			//키가 눌렸다면. 
           			if (GetAsyncKeyState(g_arrVK[keyIDX]) & 0x8000) {
           				if (m_vecKey[keyIDX].bPrevPush) {
           					//이전에 눌려있었다. 
           					m_vecKey[keyIDX].eState = KEY_STATE::HOLD;
           				}
           				else {
           					//이전에 눌려있지 않았다. 
           					m_vecKey[keyIDX].eState = KEY_STATE::TAP;
           				}
           				m_vecKey[keyIDX].bPrevPush = true;
           			}
           			else {//현재 키가 눌려있지 않다면. 
           				if (m_vecKey[keyIDX].bPrevPush) {
           					//이전에 키가 눌려있다면. 
           					m_vecKey[keyIDX].eState = KEY_STATE::AWAY;
           				}
           				else {
           					//이전에 키가 눌려있지 않았다면. 
           					m_vecKey[keyIDX].eState = KEY_STATE::NONE;
           				}
           				m_vecKey[keyIDX].bPrevPush = false;
           			}
           		}
           	}
           	else {
           		//윈도우 포커싱 해제 상태
           		for (int keyIDX = 0; keyIDX < (int)KEY::LAST; keyIDX++) {
           			m_vecKey[keyIDX].bPrevPush = false;
           			if (m_vecKey[keyIDX].eState == KEY_STATE::TAP || m_vecKey[keyIDX].eState == KEY_STATE::HOLD) {
           				m_vecKey[keyIDX].eState = KEY_STATE::AWAY;
           			}
           			else if (m_vecKey[keyIDX].eState == KEY_STATE::AWAY) {
           				m_vecKey[keyIDX].eState = KEY_STATE::NONE;
           			}
           		}
           	}
           }
                          
          
          • 키 입력을 커스텀하고, 한 프레임 안에 모두 처리하도록 한다.
          • 또한 윈도우 포커싱을 통해, 지금 포커싱 중일때만 입력을 처리하도록 한다.
        2. CkeyMgr.h

           #pragma once
                          
           //#include 거는 거 불편함. 그래서. 
                          
           //순차적이기 때문에, 한 프레임 시간이 지났을 때. DT라는 시간이 지난만큼 각각 변경점을 적용함 -> 그렇게 그린 걸 우리가 봄
           //A 물체를 이동시켰을 때, B물체보다 먼저 일어난 건가? 그건 아니지 않나? 그런데 절차지향대로라면 그렇게 됨. 
                          
           //이 모든 작업이 완료 됐을 때 모두가 동시에 일어난 사건으로 봐야만 함. 
           //만약 누구는 처리 받고, 누구는 처리 받지 못할수도 있음. 
           // 
           //따라서 KeyManager가 필요한 이유 
           //1. 프레임 동기화. 한 프레임에 모두 일어나야 함. 
                          
           //2. 윈도우에서 만들어준 함수로는 구체적인 이벤트가 적용되어 있지 않음
           //EX) 엔터키가 눌려있다만 궁금한 게 아니라, 이번에 눌렸다(Down), 이번에 뗏다(Up) <- 이런 것도 지정해줘야함. 
           #include "define.h"
                          
           //키 상태.
           enum class KEY_STATE {
           	TAP,	//막 누른 시점
           	HOLD,	//누르고 있는
           	AWAY,	//막 뗀 시점
           	NONE,	// 눌리지 않은 상태. 이전에도 눌리지 않은 상태. 
           };
                          
           //지원해줄 수 있는 키 
                          
           enum class KEY {
           	LEFT,
           	RIGHT,
           	UP,
           	DOWN,
                          
           	Q,
           	W,
           	E,
           	R,
           	T,
           	Y,
           	U,
           	I,
           	O,
           	P,
           	A,
           	S,
           	D,
           	F,
           	G,
           	H,
           	J,
           	K,
           	L,
           	Z,
           	X,
           	C,
           	V,
           	B,
           	N,
           	M,
                          
           	ALT,
           	CTRL,
           	LSHIFT,
           	SPACE,
           	ENTER,
           	ESC,
                          	
           	LAST,
           };
                          
           struct tKeyInfo{
           	KEY_STATE		eState;			//키의 상태값. 
           	bool			bPrevPush;		//이전에 이 키가 눌렸는지 안 눌렸는지
           };
           class CkeyMgr
           {
           	SINGLE(CkeyMgr);
                          
           private:
           	//벡터의 INDEX값이, 곧 KEY값. 
           	vector<tKeyInfo> m_vecKey;
                          
           public:
           	void init();
           	void update();
                          
           public:
           	KEY_STATE GetKeyState(KEY _eKey) {
           		return m_vecKey[(int)_eKey].eState;
           	};
           };
                          
          
      2. SceneMgr
        1. CSceneMgr.cpp

           #include "pch.h"
           #include "CSceneMgr.h"
           #include "CScene.h"
           #include "CScene_Start.h"
                          
           CSceneMgr::CSceneMgr()
           	: m_pCurScene(nullptr)
           	, m_arrScene{}
           {
                          
           }
                          
           CSceneMgr::~CSceneMgr() {
           	for (UINT sceneIDX = 0; sceneIDX < (UINT)SCENE_TYPE::END; sceneIDX++) {
                          
           		if (m_arrScene[sceneIDX] != nullptr) {
           			delete m_arrScene[sceneIDX];
           		}
           	}
           }
                          
           void CSceneMgr::init()
           {
           	//Scene 생성
           	m_arrScene[(UINT)SCENE_TYPE::START] = new CScene_Start;
           	m_arrScene[(UINT)SCENE_TYPE::START]->SetName(L"Start Scene");
           	//m_arrScene[(UINT)SCENE_TYPE::TOOL] = new CScene_Tool;
           	//m_arrScene[(UINT)SCENE_TYPE::STAGE_01] = new CScene_Stage01;
           	//m_arrScene[(UINT)SCENE_TYPE::STAGE_02] = new CScene_Stage02;
                          
           	//현재 씬 설정
           	m_pCurScene = m_arrScene[(UINT)SCENE_TYPE::START];
           	m_pCurScene->Enter();
                          
           	//부모 포인터라 Start()를 호출할 수가 없음. 
           }
                          
           void CSceneMgr::update()
           {
           	m_pCurScene->update();
           }
                          
           void CSceneMgr::render(HDC _dc)
           {
           	m_pCurScene->render(_dc);
           }
                          
          
          • 오늘 주요로 다룬 부분이다. SceneMgr에서는 씬(스테이지)를 다루는 부분이고, 각 씬에게 update와 render를 명령한다.
          • 또한 init으로 모든 씬을 처음에 동적할당한다.
          • 이후에 씬 전환도 추가될 예정이다.
        2. CSceneMgr.h

           #pragma once
                          
           class CScene;
                          
           class CSceneMgr
           {
           	SINGLE(CSceneMgr);
                          
           private:
           	CScene*			m_arrScene[(UINT)SCENE_TYPE::END];	//모든 씬에 대한 정보를 다 가짐
           	CScene*			m_pCurScene;						//현재 씬 
           public:
           	void init();
                          
           	void update();
           	void render(HDC _dc);
           };
                          
          
          • 역시 이것도 싱글톤 패턴으로 구현되며, 각 씬의 부모가 되는 CScene으로 포인터를 받아 관리한다.
      3. TimeMgr
        1. CTimeMgr.cpp

           #include "pch.h"
           #include "CTimeMgr.h"
           #include "CCore.h"
                          
           CTimeMgr::CTimeMgr() 
           	:m_llCurCount{}
           	, m_llPrevCount{}
           	, m_llFrequency{}
           	,m_dDT(0.)
           	,m_iCallCount(0)
           	,m_dAcc(0.)
           	,m_iFPS(0)
           {
                          
           }
                          
           CTimeMgr::~CTimeMgr() {
                          
           }
                          
           void CTimeMgr::init()
           {
                          
           	//현재 카운트
           	QueryPerformanceCounter(&m_llPrevCount);
           	//초당 카운트가 고정되어 있는것도 아님. 
                          
           	//초당 카운트 횟수(1000만)
           	QueryPerformanceFrequency(&m_llFrequency);
                          
           }
                          
           void CTimeMgr::update()
           {
           	QueryPerformanceCounter(&m_llCurCount);
                          
           	//이전 프레임의 카운팅고, 현재 프레임 카운팅 값의 차이를 구한다. 
           	m_dDT = (double)(m_llCurCount.QuadPart - m_llPrevCount.QuadPart) / (double)m_llFrequency.QuadPart;
                          
           	//이전 카운트 값을 현재값으로 갱신(다음번에 계산을 위해서)
           	m_llPrevCount = m_llCurCount;
                          
           	m_iCallCount++;
           	m_dAcc += m_dDT;	//DT 누적. 
           	if (m_dAcc >= 1.) {
           		m_iFPS = m_iCallCount;
           		m_dAcc = 0.;
           		m_iCallCount = 0;
                          
           		wchar_t szBuffer[255] = {};
           		swprintf_s(szBuffer, L"FPS : %d, DT : %f", m_iFPS, m_dDT);
           		SetWindowText(CCore::GetInstance()->GetMainHwnd(), szBuffer);
           	}
           }
                          
          
          • 초당 1000만번 세는 QueryPerformanceCounter함수를 기반으로 한 프레임당 걸린 시간을 토대로 프레임과 DeltaTime을 계산한다.
        2. CTimeMgr.h

           #pragma once
                          
           class CTimeMgr
           {
           	SINGLE(CTimeMgr);
           private:
           	//이동량을 프레임으로 쪼개서 이동을 시키면, 이번 프레임에 움직이는 양이 나옴.
                          
           	//이동량 * 1프레임에 걸리는 시간(delta Time).
           	LARGE_INTEGER m_llCurCount;
           	LARGE_INTEGER m_llPrevCount;
           	LARGE_INTEGER m_llFrequency;
                          
           	double			m_dDT;			//프레임 간의 시간값. 
           	double			m_dAcc;			//1초 체크를 위한 누적 시간. 
           	UINT			m_iCallCount;	//1초당 호출 횟수. 
           	UINT			m_iFPS;			//1초당 프레임. 
           public:
           	//GetTick은 1초당 1000이라서 정확도가 조금...
           	//게다가 오차도 있어서 훨씬 더 정밀한 함수가
           	//QueryPerformanceCounter(); -> 1000만 단위.
           	void init();
           	void update();
                          
           public:
           	double GetDT() { return m_dDT; }
           	float GetfDT() { return (float)m_dDT; }
           };
                          
          
          • GetDT와 GetfDT는 모두 Define.h에 매크로로 저장되어 있다.
      4. Object
        1. CObject.cpp

           #include "pch.h"
           #include "CObject.h"
                          
           #include "CkeyMgr.h"
           #include "CTimeMgr.h"
                          
           CObject::CObject()
           	: m_vPos{}
           	, m_vScale{}{
                          
           }
                          
           CObject::~CObject() {
                          
           }
                          
           void CObject::update()
           {
           	if (CkeyMgr::GetInstance()->GetKeyState(KEY::W) == KEY_STATE::HOLD) {
           		m_vPos.y -= 200.f * fDT;
           	}
           	if (CkeyMgr::GetInstance()->GetKeyState(KEY::S) == KEY_STATE::HOLD) {
           		m_vPos.y += 200.f * fDT;
           	}
           	if (CkeyMgr::GetInstance()->GetKeyState(KEY::A) == KEY_STATE::HOLD) {
           		m_vPos.x -= 200.f * fDT;
           	}
           	if (CkeyMgr::GetInstance()->GetKeyState(KEY::D) == KEY_STATE::HOLD) {
           		m_vPos.x += 200.f * fDT;
           	}
           }
                          
           void CObject::render(HDC _dc)
           {
                          
           	Rectangle(_dc, (int)(m_vPos.x - m_vScale.x / 2.f), (int)(m_vPos.y - m_vScale.y / 2.f),
           		(int)(m_vPos.x + m_vScale.x / 2.f), (int)(m_vPos.y + m_vScale.y / 2.f));
           }
          
          • CCore → CSceneMgr → CScene → Object순으로 update와 render 명령이 내려온다. 최종적으로 오브젝트에서 update와 render를 해준다.
        2. CObject.h

           #pragma once
                          
           //오브젝트라는 건, 가장 부모격인 녀석. 오브젝트마다 성향이 있음 
           //어떤 건 UI, 어떤 건 캐릭터... 
                          
           //오브젝트를 일괄적으로 관리할 녀석이 필요함. -> Scene
           class CObject
           {
                          
           private:
           	Vec2	m_vPos;
           	Vec2	m_vScale;
                          
           public:
           	void SetPos(Vec2 _vPos) { m_vPos = _vPos; }
           	void SetScale(Vec2 _vScale) { m_vScale = _vScale; }
                          
           	Vec2 GetPos() { return m_vPos; }
           	Vec2 GetScale() { return m_vScale; }
                          
           public:
           	void update();
           	void render(HDC _dc);
                          
           public:
           	CObject();
           	virtual ~CObject();
           };
                          
          
          • 상속관련해서 중요한 부분인데, 상속받은 자식에서 따로 정의한 동적할당 무언가가 있다면, 자식에서도 따로 소멸자 호출을 해야한다. 그러므로 부모에서 소멸자는 반드시!!! virtual로 해줘야한다.
      5. Scene
        1. CScene_Start.cpp

           #include "pch.h"
           #include "CScene_start.h"
           #include "CObject.h"
                          
           CScene_Start::CScene_Start()
           {
                          
           }
                          
           CScene_Start::~CScene_Start()
           {
                          
           }
                          
           void CScene_Start::Enter()
           {
           	//Object 추가.
           	CObject* pObj = new CObject;
           	pObj->SetPos(Vec2(640.f, 384.f));
           	pObj->SetScale(Vec2(100.f, 100.f));
           	AddObject(pObj, GROUP_TYPE::DEFAULT);
           }
                          
           void CScene_Start::Exit()
           {
                          
           }
                          
          
          • Enter() ← 씬이 처음 시작될 때 부르는 함수에서 오브젝트를 만들어 넣는다.
          • Exit() ← 씬이 끝날 때(전환될 때) 부르는 함수
        2. CScene_Start.h

           #pragma once
           #include "CScene.h"
           class CScene_Start :
               public CScene
           {
                          
           public:
                          
               //부모로부터 상속받은 가상함수인지, 일반 함수인지 보통은 구별 안되서 virtual씀. 
               //virtual을 안적어도 가상함수이긴 함... 그래도 명시적으로. 
               virtual void Enter();
               virtual void Exit();
                          
           public:
               CScene_Start();
               ~CScene_Start();
           };
                          
          
          • 가독성을 위해 부모로부터 받은 함수에는 virtual을 기입해주자!!
          • 내가 생각해도 그게 맞다.
      6. CScene.cpp

         #include "pch.h"
         #include "CScene.h"
         #include "CObject.h"
                    
         CScene::CScene()
         {
         }
                    
         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::update()
         {
         	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]->update();
         		}
         	}
         }
                    
         void CScene::render(HDC _dc)
         {
         	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]->render(_dc);
         		}
         	}
         }
                    
        
        • Scene에서는 각 update와 render마다 그 안에 있는 모든 오브젝트에게 update와 render을 하라고 명령해준다.
      7. CScene.h

         #pragma once
                    
         //전방선언하는 이유는 컴파일 속도에 영향을 주지 않기 위해. 
         class CObject;
                    
         class CScene
         {
                    
         private:
         	vector<CObject*> m_arrObj[(UINT)GROUP_TYPE::END];		//벡터 안에 모든 오브젝트 집어 넣겠다. 이런 특성(요소)를 가진만큼 나눠주기.
         	//달리말하면 그룹 갯수만큼 나눠주기.
                    
         	wstring			m_strName; //Scene 이름
                    
         public:
         	void SetName(const wstring& _strName) { m_strName = _strName; }
         	const wstring& GetName() { return m_strName; }
                    
         	void update();
         	void render(HDC _dc);
                    	
         	virtual void Enter() = 0;		//해당 Scene에 진입 시 호출.
         	virtual void Exit() = 0;		//해당 Scene에 탈출 시 호출.
                    
         protected:
         	//클래스는 헤더에 구현하면 인라인 처리가 됨. 
         	//따라서 함수 호출 비용이 사라짐. 
         	void AddObject(CObject* _pObj, GROUP_TYPE _eType)
         	{
         		m_arrObj[(UINT)_eType].push_back(_pObj);
         	}
                    
         public:
         	CScene();
                    
         	//소멸자의 가상함수 해줘야함. 씬 매니저가 모든 Scene을 부모 포인터로 관리함.
         	// CSceneMgr에서 씬을 소멸시킬때, 소멸자는 부모인 CScene만 호출됨.  
         	virtual ~CScene();
         };
                    
        
        • CScene(모든 씬의 부모)에서는 모든 오브젝트를 그 Type에 따라 나눠서 벡터로 저장하고 있다.
        • Enter와 Exit은 Scene마다 모두 천차만별 일 수 있으므로, 자식 Scene에서 구현을 해줘야한다. 따라서 순수 가상함수로 만들어줘야 한다.
        • 클래스의 함수를 헤더에 구현하면 인라인 처리가 된다. 따라서 함수 호출 비용이 사라진다. 물론 컴파일 소요 시간은 늘어나겠지만…
      8. main.cpp

         // Client.cpp : 애플리케이션에 대한 진입점을 정의합니다.
         //
                    
         #include "pch.h"
         #include "framework.h"
         #include "Client.h"
         #include "CCore.h"
                    
         #define MAX_LOADSTRING 100
         //WCHAR은 2바이트, wchar_t형임. (16비트)
         //그거 이외엔 다른 게 없음(유니코드형을 위한 것)
         // 전역 변수:
         HINSTANCE hInst;                                // 현재 인스턴스입니다.
         //메인함수가 처음 시작할 때 받아오는 값. 
                    
         HWND g_hWnd; //메인 윈도우 핸들. 
         WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
         WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.
         //100길이로 그냥 선언해놓은 것. 
                    
         // 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
         ATOM                MyRegisterClass(HINSTANCE hInstance);
         BOOL                InitInstance(HINSTANCE, int);
         LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
         INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
         //함수들은 전방 선언해놓은 것.(함수 프로토타입)
                    
         int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
             _In_opt_ HINSTANCE hPrevInstance,
             _In_ LPWSTR    lpCmdLine,
             _In_ int       nCmdShow)
         {
             UNREFERENCED_PARAMETER(hPrevInstance);
             UNREFERENCED_PARAMETER(lpCmdLine);
             //딱히 신경쓸 건 없음. 얘내 둘은 딱히 쓰이지 않는다... 라는 걸 선언한 느낌. 
             //매크로임. 
         // TODO: 여기에 코드를 입력합니다.
                    
             LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
             LoadStringW(hInstance, IDC_CLIENT, szWindowClass, MAX_LOADSTRING);
                    
             MyRegisterClass(hInstance);
                    
             // 애플리케이션 초기화를 수행합니다:
             // 여기서 윈도우 창을 생성함. 유저와 프로그램 사이의 인터페이스, 접점이라는 의미도 됨.
             if (!InitInstance(hInstance, nCmdShow))
             {
                 return FALSE;
             }
                    
             //CCore 초기화
             //이게 메인 윈도우 입니다. 이게 해상도입니다. 
             if (FAILED(CCore::GetInstance()->init(g_hWnd, POINT{1280, 768}))) {
                 MessageBox(nullptr, L"Core 객체 초기화 실패", L"ERROR", MB_OK);
                 return FALSE;
             }
                    
             //단축키 정보. 
             //리소스 뷰 -> Accelerator -> table 
             //특정 키와 조합했을 때, 키값. 
             //테이블 정보 불러와서. 
             HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CLIENT));
                    
             MSG msg;
                    
             while (true)
             {
                 //무한 반복 -> 메시지가 없으면 if문 바깥으로 무시. 
                 if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
                     //메시지를 처리하기 전에, 
                    
                     if (WM_QUIT == msg.message) {
                         break;
                     }
                    
                     if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
                     {
                         TranslateMessage(&msg);
                         DispatchMessage(&msg);
                     }
                 }
                 //메시지가 발생하지 않는 대부분의 시간. 
                 else {
                     //디자인 패턴(설계 유형)
                     //싱글톤 패턴
                    
                     //게임 속에서 렌더링이라는 건, 매 순간순간 전부 다 삭제 후, 전부 그리기. 
                     CCore::GetInstance()->progress();
                 }
             }
             //두 번재 매개변수가 ID.
                    
             return (int)msg.wParam;
         }
                    
         //
         //  함수: MyRegisterClass()
         //
         //  용도: 창 클래스를 등록합니다.
         //  창 클래스를 우리가 정의하고, 등록하는 절차는 윈도우 OS에서 제공하는 함수. 
         ATOM MyRegisterClass(HINSTANCE hInstance)
         {
             WNDCLASSEXW wcex;
             //구조체 채워서 함수에 전달하기. 
             wcex.cbSize = sizeof(WNDCLASSEX);
                    
             wcex.style = CS_HREDRAW | CS_VREDRAW;
             //WndProc이란?  전방 선언 되어 있는 위 함수. 
             //함수 포인터. 
             wcex.lpfnWndProc = WndProc;
             wcex.cbClsExtra = 0;
             wcex.cbWndExtra = 0;
             wcex.hInstance = hInstance;
             wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CLIENT));
             wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
             wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
             //처음 생성되는 메뉴바 null 값을 넣으면 메뉴바 사라짐. 
             wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_CLIENT);
                    
             //이거의 Key값을 szWindowClass로 사용함. 이 MyRegisterClass의 Key값임. 
             wcex.lpszClassName = szWindowClass;
             wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
                    
             //이건 윈도우 OS에서 제공하는 함수임... 
             return RegisterClassExW(&wcex);
         }
                    
         //
         //   함수: InitInstance(HINSTANCE, int)
         //
         //   용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
         //
         //   주석:
         //
         //        이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
         //        주 프로그램 창을 만든 다음 표시합니다.
         //
         BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
         {
             hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
             //szTitle 대신 "한재훈~" 넣어도 됨. 2번째 변수는 이름 말하는 거임. 
             //szWindowClass 키 값대로 만들어달라. 
             g_hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
                 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
                    
             if (!g_hWnd)
             {
                 return FALSE;
             }
                    
             ShowWindow(g_hWnd, nCmdShow);
             UpdateWindow(g_hWnd);
                    
             return TRUE;
         }
                    
         //
         //  함수: WndProc(HWND, UINT, WPARAM, LPARAM)
         //
         //  용도: 주 창의 메시지를 처리합니다.
         //
         //  WM_COMMAND  - 애플리케이션 메뉴를 처리합니다.
         //  WM_PAINT    - 주 창을 그립니다.
         //  WM_DESTROY  - 종료 메시지를 게시하고 반환합니다.
         //
         // 메시지가 발생한 윈도우 id값, 메시지등등이 같이 들어옴. 
         LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
         {
             switch (message)
             {
             case WM_COMMAND://왠만한 메시지(단축키)는 이렇게 여기에서 처리함. 
             {
                 int wmId = LOWORD(wParam);
                 // 메뉴 선택을 구문 분석합니다:
                 switch (wmId)
                 {
                 case IDM_ABOUT:
                     DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                     break;
                 case IDM_EXIT:
                     DestroyWindow(hWnd);
                     break;
                 default:
                     return DefWindowProc(hWnd, message, wParam, lParam);
                 }
             }
             break;
             case WM_PAINT:
             {
                    
                 PAINTSTRUCT ps;
                 //Begin에서 End까지 마무리를 할 때, 우리 윈도우에는 무효화 영역이 없다. 
                 //End안하면, 계속해서 무한히 무효화 영역이라고 생각함. 
                    
                 //메시지가 있던 없던 그림을 그릴거다. 그럴때 사용하는게 GetDC
                 HDC hdc = BeginPaint(hWnd, &ps); //Devicde Context.(그리기)
                    
                 //Rectangle(hdc, 1180, 668, 1280, 768);
                    
                 //그리기 종료. 
                 EndPaint(hWnd, &ps);
             }
             break;
             case WM_LBUTTONDOWN:
                 break;
             case WM_MOUSEMOVE:
                 break;
                    
             case WM_LBUTTONUP:
                 break;
                    
             case WM_DESTROY:
                 PostQuitMessage(0);
                 break;
                    
             case WM_KEYDOWN:
             {
             }
             break;
             default: //따로 정의되지 않은 수많은 단축키들. 
                 return DefWindowProc(hWnd, message, wParam, lParam);
             }
             return 0;
         }
                    
         // 정보 대화 상자의 메시지 처리기입니다.
         INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
         {
             UNREFERENCED_PARAMETER(lParam);
             switch (message)
             {
             case WM_INITDIALOG:
                 return (INT_PTR)TRUE;
                    
             case WM_COMMAND:
                 if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
                 {
                     EndDialog(hDlg, LOWORD(wParam));
                     return (INT_PTR)TRUE;
                 }
                 break;
             }
             return (INT_PTR)FALSE;
         }
                    
        
        • 앞선 시간에서 많이 다뤘던 것이다.
        • 주요 변경점이라면, 메시지가 올때마다 while문을 반복하는 초기 형태와는 다르게, 이젠 무한 반복하며, 메시지를 빼와서 사용한다.
      9. pch.h

         #pragma once
                    
         //미리 컴파일된 헤더. 
         //이미 제공된 애들 <- 바뀔 여지가 없으니까, 다시 컴파일 할 이유X
         //따라서 이걸 사용하면, 컴파일 속도가 빨라짐. 
                    
         //그리고 클래스를 만들때 선언해둔 미리 컴파일된 헤더 생김. 
                    
         #include <Windows.h>
         #include <iostream>
                    
         #include <vector>
         using std::vector;
                    
         #include <string>
         using std::string;
         using std::wstring;
                    
         #include "define.h"
         #include "struct.h"
        
        • 미리 컴파일된 헤더이다. 컴파일 속도가 빨라질 수 있다.

WINAPI 카테고리 내 다른 글 보러가기

댓글 남기기