[GItHub] - https://github.com/kakukaku86/-Direct2D-Game-FrameWork
오늘부터 시간이 날때마다 예전 생각을 하면서 간단한 2D 게임을 만들어 볼까..합니다.
코드는 모두 GitHub에서 공유가 될 예정이며, WINAPI가 아닌 D2D (DirectX Software Development Kit)를 사용할 예정입니다.
D2D의 경우 렌더링 하는 방식에서 기존에 API와 차이점이 있습니다. 기술적인 면에서 설명을 하는것 보다 조금 더 쉽게 설명하자면...그래픽면에서 조금 더 렌더링이 매끄럽고, API에서 지원되지 않는 다양한 그래픽 효과를 줄 수 있습니다.
또한 API에서 지원하지 않는 포맷 (대표적으로 .png)도 일부 사용이 가능합니다.
저도 혼자 MSDN에서 독학해서 작성하는 코드라서 부족한 부분이 많이 있을 것 같습니다.
D2D 랜더 방식이나 자세한 API 설명에 대해서는 아래 사이트를 참고해주세요.
MSDN - [How to render by using a Direct2D device context?]
GitHub 도 초보인지라...제가 혹시 잘못알고 있는 부분이라면 누구든지 해당 코드에 대해서 수정하거나 같이 만들어 갈 수 있도록 해당 GitHub에서 수정 및 변경이 가능합니다. 브랜치를 따서 사용해주시면 더욱 감사하겠습니다.
언어는 C++ 이며 사용할 프로그램은 비주얼 2015 커뮤니티 버전입니다.
오늘은 기본적으로 윈도우 화면을 띄우는것부터 시작하겠습니다.
블로그에는 특정 코드에 관해서만 올리도록 하겠습니다.
전체 코드는 GitHub에서..확인해주세요..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | #include "GameBase.h" GameBase* GameBase::gameBase = nullptr; GameBase::GameBase() : D2DFactory(nullptr) , D2DRenderTarget(nullptr) , hWnd(nullptr) , hInstance(nullptr) , hdc(nullptr) { GameBase::gameBase = this; } GameBase::~GameBase() { this->Destory(); } HRESULT GameBase::Initialize() { return 0; } void GameBase::Destory() { ReleaseDC(hWnd, hdc); SAFE_RELEASE(D2DbitMapRenderTarget); SAFE_RELEASE(D2DRenderTarget); SAFE_RELEASE(D2DFactory); } HRESULT GameBase::Create(HINSTANCE _hInstance) { // Create Basic Windows WNDCLASS wndClass; _hInstance = hInstance; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndClass.hInstance = hInstance; wndClass.lpfnWndProc = WndProc; wndClass.lpszClassName = WINNAME; wndClass.lpszMenuName = NULL; wndClass.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wndClass); RECT rc = { 0, 0, WINSIZEX, WINSIZEY }; AdjustWindowRect(&rc, WINSTYLE, false); SetWindowPos(hWnd, NULL, WINSTARTX, WINSTARTY, (rc.right - rc.left), (rc.bottom - rc.top), SWP_NOZORDER | SWP_NOMOVE); hWnd = CreateWindow(WINNAME, WINNAME, WINSTYLE, WINSTARTX, WINSTARTY, WINSIZEX, WINSIZEY, NULL, (HMENU)NULL, hInstance, NULL); ShowWindow(hWnd, SW_SHOWNORMAL); // Create Direct2D Set if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &D2DFactory))) { std::wstring errMsg = TEXT(" D2DFactory 생성에 실패하였습니다."); MessageBox(GetActiveWindow() , errMsg.c_str() , L"Create D2DFactory Fail" , MB_ICONEXCLAMATION ); return -1; } RECT windowsDC; GetClientRect(hWnd, &windowsDC); D2D1_SIZE_U size = D2D1::SizeU(windowsDC.right, windowsDC.bottom); if (FAILED(D2DFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hWnd, size), &D2DRenderTarget))) { std::wstring errMsg = TEXT(" D2DRenderTarget 생성에 실패하였습니다."); MessageBox(GetActiveWindow() , errMsg.c_str() , L"Create D2DRenderTarget Fail" , MB_ICONEXCLAMATION ); return -1; } if (FAILED(D2DRenderTarget->CreateCompatibleRenderTarget(D2D1::SizeF(float(WINSIZEX), float(WINSIZEY)), &D2DbitMapRenderTarget))) { std::wstring errMsg = TEXT(" D2DBitmapRenderTarget 생성에 실패하였습니다."); MessageBox(GetActiveWindow() , errMsg.c_str() , L"Create D2DBitmapRenderTarget Fail" , MB_ICONEXCLAMATION ); return -1; } if (FAILED(Initialize())) { std::wstring errMsg = TEXT(" 초기화에 실패하였습니다."); MessageBox(GetActiveWindow() , errMsg.c_str() , L"Device Initialize Fail" , MB_ICONEXCLAMATION ); return -1; } hdc = GetDC(hWnd); return NO_ERROR; } INT GameBase::Loop() { MSG message = { 0 }; while (message.message != WM_QUIT) { if(PeekMessage(&message , NULL , 0 , 0 , PM_REMOVE)) { TranslateMessage(&message); DispatchMessage(&message); } else { this->Update(); this->Render(); } } return 0; } void GameBase::Update() { } void GameBase::Render() { } LRESULT CALLBACK GameBase::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (GameBase::gameBase != nullptr) return GameBase::gameBase->MsgProc(hWnd, uMsg, wParam, lParam); return DefWindowProc(hWnd, uMsg, wParam, lParam); } LRESULT GameBase::MsgProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { switch (iMessage) { case WM_CREATE: break; case WM_MOUSEMOVE: //_ptMouse.x = LOWORD(lParam); //_ptMouse.y = HIWORD(lParam); break; case WM_KEYDOWN: switch (wParam) { case VK_ESCAPE: PostMessage(hWnd, WM_DESTROY, 0, 0); break; } break; case WM_DESTROY: PostQuitMessage(0); break; default: break; } return DefWindowProc(hWnd, iMessage, wParam, lParam); } | cs |
일반적인 윈도우 API와 생성 부분에서 다른점이라면 .. 다음과 같이 Factory를 생성해야 한다는 점입니다.
1 2 3 4 5 6 7 8 9 10 | if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &D2DFactory))) { std::wstring errMsg = TEXT(" D2DFactory 생성에 실패하였습니다."); MessageBox(GetActiveWindow() , errMsg.c_str() , L"Create D2DFactory Fail" , MB_ICONEXCLAMATION ); return -1; } | cs |
D2D1CreateFactory 함수로 Direct2D의 객체를 생성하기 위해서 사용합니다.
(조금은 낯설수도 있는데 Direct3D에서도 비슷한 함수를 사용하게 됩니다.)
HRESULT WINAPI D2D1CreateFactory( _In_ D2D1_FACTORY_TYPE factoryType, _In_ REFIID riid, _In_opt_ const D2D1_FACTORY_OPTIONS *pFactoryOptions, _Out_ void **ppIFactory); |
해당 함수로 Factory를 생성하게 되고 HRESULT 를 반환하기 때문에 해당 함수로 예외사항을
같이 체크하였습니다.
다음은 동일한 내용으로 이미지(화면에 렌더링)를 그리기 위해서는 Render Target 이 필요합니다.
아래에서는 CreateHwndRenderTarget과 CreateCompatibleRenderTarget로 2개의 Render Target을
생성하였는데 나중에 더블 버퍼링을 하기 위해서 미리 2개를 생성했습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | if (FAILED(D2DFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hWnd, size), &D2DRenderTarget))) { std::wstring errMsg = TEXT(" D2DRenderTarget 생성에 실패하였습니다."); MessageBox(GetActiveWindow() , errMsg.c_str() , L"Create D2DRenderTarget Fail" , MB_ICONEXCLAMATION ); return -1; } if (FAILED(D2DRenderTarget->CreateCompatibleRenderTarget(D2D1::SizeF(float(WINSIZEX), float(WINSIZEY)), &D2DbitMapRenderTarget))) { std::wstring errMsg = TEXT(" D2DBitmapRenderTarget 생성에 실패하였습니다."); MessageBox(GetActiveWindow() , errMsg.c_str() , L"Create D2DBitmapRenderTarget Fail" , MB_ICONEXCLAMATION ); return -1; } | cs |
HRESULT CreateHwndRenderTarget( [ref] const D2D1_RENDER_TARGET_PROPERTIES &renderTargetProperties, [ref] const D2D1_HWND_RENDER_TARGET_PROPERTIES &hwndRenderTargetProperties, [out] ID2D1HwndRenderTarget **hwndRenderTarget ); |
[참고] https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd742780(v=vs.85).aspx
전혀 어려운 부분은 없습니다.
기존에 윈도우 API가 윈도우를 생성한후에 바로 WIN_PAINT에서 그렸다면, 단지 Factory라는 객체를
추가해서 해당 Factory 객체를 이용하여 그리는 것만 추가되었습니다.
렌더링할때 생성한 윈도우에 그리기 위해 해당 DC를 사용한가는것 또한 역시 같습니다.
입력처리에 경우 전용 입력처리가 있지만 기존에 익숙한 WIN API도 호환이 되므로 WINAPI 입력처리를 사용하였습니다.
[결과]
빌드를 하게 되면 기본적인 600 x 600 윈도우 창이 뜨게 됩니다.
이번에는 여기까지 입니다. 다음에는 기본적인 이미지 출력처리와 방법에 대해서 알아보겠습니다.
감사합니다.