2023. 5. 20. 18:23ㆍWin32 API
1. 그림판 사각형
이전 시간에 사용자의 키보드 입력을 통해 우리가 그린 사각형을 실시간으로 이동시키는 코드를 작성했었다. 오늘 해볼 것은 아래와 같이 그림판에서 사각형을 그리는 버튼을 누른 후 사각형을 그리는 행위를 구현할 것이다.
이 버튼을 누르고 사각형을 어떻게 그리게 될까? 우선 사각형을 무슨 색 선으로 그릴지 결정한 후에 사각형을 그린 지점을 마우스로 선택하고 아래의 그림에서 A지점부터 B지점까지 마우스의 왼쪽 버튼을 꾹 누르고 있는 상태로 마우스를 이동시키면 아래의 그림과 같이 사각형이 그려질 것이다.
이걸 구현하려면 어떻게 해야할까? 우선 몇가지 필요한 것이 있다. 다음 상황에 대한 마우스 좌표와 이벤트가 필요하다.
- 마우스 왼쪽 버튼을 누를 때
- 마우스가 움직일 때
- 마우스 왼쪽 버튼을 뗐을 때
우리는 위의 상황에 대한 이벤트를 메세지를 통해 처리할 수 있다는 것을 이전 시간에 배운 적이 있다. 또한 마우스 입력에 관한 기능을 담당하는 함수를 찾는 시도를 할 수 있을 것이다. 그렇게 찾아보면 아래와 같은 것들을 발견할 수 있다. 또한 편의를 위해 새로운 구조체를 만들기로 했다.
#include <vector>
// 사각형 오브젝트 구조체
struct tObjInfo
{
POINT objPos;
POINT objScale;
};
// 사각형을 담는 배열
std::vector<tOBjInfo> g_objVec;
// 사각형의 왼쪽 위의 위치
POINT g_ptLT;
// 사각형의 오른쪽 아래의 위치
POINT g_ptRB;
// 마우스 좌클릭이 눌린 상태인가?
bool g_bLButtonDown = false;
// 마우스 좌클릭에 대한 이벤트들
case WM_LBUTTONDWON: // 마우스가 눌렸을 때의 이벤트
case WM_LBUTTONMOVE: // 마우스가 움직일 때의 이벤트
case WM_LBUTTONUP: // 마우스가 떼졌을 때의 이벤트
여기까지 했으면 이제 각 이벤트에 대한 처리를 해주는 코드를 아래와 같이 추가해야 한다. 당연히 WndProc에 있는 switch 내에 적절하게 추가하면 될 것이다. 코드를 보고 흐름을 이해해야 한다. 그림판에서 사각형을 그릴 때, 어떤 흐름으로 작동이 일어나는지 보고 마우스 좌클릭을 누를 때, 이동할 때, 뗐을 때 어떻게 작동하는지 흐름을 이해해야 한다.
- 좌클릭을 눌렀을 때: 우리가 그릴 사각형의 좌상단의 좌표를 대입하고 버튼이 눌렸다는 것을 알린다.
- 누른 상태로 이동할 때: 실시간으로 사각형의 우하단의 좌표를 대입하고 사각형을 그린다.
- 좌클릭을 뗐을 때: 최종적으로 완성된 사각형이 그려져야 하며 사각형의 대한 정보가 입력되고 배열에 저장되며 버튼이 떼졌다는 것을 알린다.
// WM_PAINT에 있는 사각형을 그리는 기존의 코드는 이렇게 바뀐다.
if (g_bLButtonDown)
Rectangle(hdc, g_ptLT.x, g_ptLT.y, g_ptRB.x, g_ptRB.y);
for (size_t i = 0; i < objVec.size(); ++i)
{
Rectangle(hdc
, g_objVec[i].objPos.x - g_objVec[i].objScale.x / 2
, g_objVec[i].objPos.y - g_objVec[i].objScale.y / 2
, g_objVec[i].objPos.x + g_objVec[i].objScale.x / 2
, g_objVec[i].objPos.y + g_objVec[i].objScale.y / 2);
}
// 생략
case WM_LBUTTONDWON:
g_ptLT.x = LOWORD(lParam);
g_ptLT.y = HIWORD(lParam);
g_bLButtonDown = true;
break;
case WM_LBUTTONMOVE:
g_ptRB.x = LOWORD(lParam);
g_ptRB.y = HIWORD(lParam);
InvalidateRect(hwnd, nullptr, true);
break;
case WM_LBUTTONUP:
{
tObjInfo info;
info.objPos.x = (g_ptLT.x + g_ptRB.x) / 2;
info.objPos.y = (g_ptLT.y + g_ptRB.y) / 2;
info.objScale.x = abs(g_ptLT.x - g_ptRB.x);
info.objScale.y = abs(g_ptLT.y - g_ptRB.y);
objVec.push_back(info);
g_bLButtonDown = false;
InvalidateRect(hwnd, nullptr, true);
}
break;
// 생략
2. 구조 변경
지금까지 잘 따라와서 적절하게 기존에 있었던 코드를 잘 변경시켜서 실행을 시켰다면 그림판에서 사각형을 그리는 기능이 정상적으로 작동하게 될 것이다. 지금까지 짰던 코드는 이벤트 처리를 WM_PAINT, WM_LBUTTONDOWN과 같은 윈도우에서 자체적으로 제공하는 메세지 기반으로 처리를 했는데 강의에 따르면 사실 이는 매우 느리다고 한다. 그래서 구조를 좀 변경할 것이다. 그리고 이 글의 제목에서 나온 PeekMessage가 드디어 여기서 등장하게 된다.
우리가 지금 이 느려터진 메세지 기반의 코드를 작성하는 이유가 바로 GetMessage를 사용하고 있기 때문인데 이를 고쳐야 한다. 애초에 본질적으로 GetMessage는 메세지를 받을 때까지 대기를 하는 특성을 가졌고 강제로 매 프레임마다 이벤트를 발생시켜야 하는 이상GetMessage는 상당히 느릴 수 밖에 없다. 그래서 PeekMessage를 사용해야 하는 것이다. 이 함수는 메세지 큐에서 메세지가 발생했는지 그냥 살짝 보는 느낌의 함수이다. 즉, GetMessage처럼 대기할 이유가 없다.
BOOL WINAPI PeekMessageW(_Out_ LPMSG lpMsg, _In_opt_ HWND hwnd,
_In_ UINT wMsgFilterMin, _In_ UINT wMsgFilterMax, _In_ UINT wRemoveMsg);
// 반환값: 메세지를 가져왔으면 true, 아니면 false를 반환함
// lpMsg: 메세지 큐에서 가져올 메세지를 여기에 넣어줌
// hwnd: 어떤 윈도우를 대상으로 메세지 큐에서 메세지를 가져올지 세팅함
// 만약 nullptr을 넣으면 해당 스레드에 있는 모든 대상에게 메세지를 전달함
// wMsgFilterMin: 모든 메세지에는 고유한 값을 가지고 있는데 그 값보다 크거나 같은 값만 검색
// wMsgFilterMax: 마찬가지로 여기에 전달된 값보다 작거나 같은 값만 검색해옴
// 만약 0, 0을 넣으면 모든 값을 대상으로 메세지를 검색함
// wRemoveMsg: 메세지 큐에서 메세지를 가져오면 그 메세지를 큐에서 지울 것인지 세팅함
while (true)
{
// msg에 메세지를 전달하면 해당 스레드에 있는 모든 대상으로 메세지를 가져오며
// 그 메세지의 범위는 제한이 없고 가져온 메세지는 큐에서 제거된다는 뜻이다.
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
// WM_QUIT은 프로그램 종료 메세지이다.
if (msg.message == WM_QUIT)
break;
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 매 프레임마다 실행될 코드를 작성
}
}
이렇게 메인 루프의 구조를 변경했다. 이제 GetMessage를 통해 처리를 하지 않아도 되고 그만큼 속도고 빨라지게 되었다. 그런데 아직 변경할 것이 남았다. 사실 main.cpp가 좀 난잡한 감이 있어서 Singleton이라는 디자인 패턴을 이용해서 main.cpp에 있는 코드를 조금 정리할 것이다. 다만 이 부분은 다음 글에서 다루게 될 것 같다.
다음 포스팅은 높은 확률로 다시 인프런의 DX12 강의 정주행으로 넘어갈 것이다. 정확히는 DX12와 Win32 API를 병행하면서 공부하겠다는 뜻이다. DX12를 공부하다가 모르는 Win32 API 키워드가 나왔을 때, 그냥 넘어가지 않고 강좌를 통해 공부를 하고 넘어가겠다는 뜻이다. 다음 포스팅은 Singleton으로 작성된 Core에 해당하는 클래스들을 만들건데 그 코드를 작성하고 포스팅을 마치게 될 것 같다. 그 구조가 완성되고 나면 이제 본격적으로 그 위에 렌더링을 하는 작업을 하게 될 것이다. 그럼 다음 글로 DX12에서 보자.
'Win32 API' 카테고리의 다른 글
어소트락 게임 아카데미 Win32 API 정주행 2회차 - 메세지와 이벤트 (0) | 2023.05.20 |
---|---|
어소트락 게임 아카데미 Win32 API 정주행 1회차 - main.cpp (0) | 2023.05.18 |
어소트락 게임 아카데미 Win32 API 정주행 - Intro (0) | 2023.05.18 |