WIN32 SceneManager + 중간점검
카테고리: WINAPI
8일차 - 중간 점검
- Default
- Client.h
- framework.h
- Resouce.h
- targetver.h
- Engine
- Header
- define.h
- struct.h
- Core
- CCore.cpp
- CCore.h
- Manager
- KeyMgr
- CkeyMgr.cpp
- CkeyMgr.h
- TimeMgr
- CTimeMgr.cpp
- CTimeMgr.h
- SceneMgr
- CSceneMgr.cpp
- CSceneMgr.h
- KeyMgr
- Object
- CObject.cpp
- CObject.h
- Scene
- Scene_Start
- CScene_Start.cpp
- CScene_Start.h
- Scene_Tool
- CScene.cpp
- CScene.h
- Scene_Start
- Header
- 리소스 파일
- main.cpp
-
pch.h.
- 오늘은 중간점검을 해보자! 여느때와 같이 변화점이 있는 부분은 볼드처리 했다!!
- 모든 소스를 볼거다! 엔진 코어의 흐름을 설명할거다.
- Header
-
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을 매크로화, 오브젝트와 씬의 타입을 구분지어줬다고 볼 수 있다.
-
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형을 지원하는 커스텀 구조체이다
-
Core
-
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에서 하도록 바꾸었다. 이제 여기서는 총괄 매니징만 하는 느낌으로 생각하면 된다!!
-
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()를 지워줬다.
-
Manager
- KeyMgr
-
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; } } } }
- 키 입력을 커스텀하고, 한 프레임 안에 모두 처리하도록 한다.
- 또한 윈도우 포커싱을 통해, 지금 포커싱 중일때만 입력을 처리하도록 한다.
-
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; }; };
-
- SceneMgr
-
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으로 모든 씬을 처음에 동적할당한다.
- 이후에 씬 전환도 추가될 예정이다.
-
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으로 포인터를 받아 관리한다.
-
- TimeMgr
-
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을 계산한다.
-
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에 매크로로 저장되어 있다.
-
- Object
-
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를 해준다.
-
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로 해줘야한다.
-
- Scene
-
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() ← 씬이 끝날 때(전환될 때) 부르는 함수
-
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을 기입해주자!!
- 내가 생각해도 그게 맞다.
-
-
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을 하라고 명령해준다.
-
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에서 구현을 해줘야한다. 따라서 순수 가상함수로 만들어줘야 한다.
- 클래스의 함수를 헤더에 구현하면 인라인 처리가 된다. 따라서 함수 호출 비용이 사라진다. 물론 컴파일 소요 시간은 늘어나겠지만…
-
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문을 반복하는 초기 형태와는 다르게, 이젠 무한 반복하며, 메시지를 빼와서 사용한다.
-
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"
- 미리 컴파일된 헤더이다. 컴파일 속도가 빨라질 수 있다.
- KeyMgr
-
댓글 남기기