WIN32 PeekMessage
카테고리: WINAPI
3일차
// Client.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//
#include "framework.h"
#include "Client.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);
//함수들은 전방 선언해놓은 것.(함수 프로토타입)
#include <vector>
using std::vector;
struct tObjInfo {
POINT g_ptObjectPos;
POINT g_ptObjScale;
};
vector<tObjInfo> g_vecInfo;
//좌 상단
POINT g_ptLT;
//우 하단.
POINT g_ptRB;
bool bLbtnDown = false;
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;
}
//단축키 정보.
//리소스 뷰 -> Accelerator -> table
//특정 키와 조합했을 때, 키값.
//테이블 정보 불러와서.
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CLIENT));
MSG msg;
//타이머도 부착된 커널 오브젝틍임.
//두 번째 매개변수가 ID.
//SetTimer(g_hWnd, 0, 0, nullptr);
//GetMessage때문에 메시지 기반 처리 방식임. 메시지가 올 때까지 기다림...
//PM_REMOVE 확인한 메시지가 있으면, 그 메시지를 제거함. -> 안 그러면 메시지가 계속 쌓임...
DWORD dwPrevCount = GetTickCount();
DWORD dwAccCount = GetTickCount();
// msg.message == WM_QUIT이면 FALSE반환함.
//그 이외에는 TRUE 당연히 계속 유지되어야 하니까.
//
//PeekMessage는 언제 반환되느냐? -> 메시지의 유무와 관계없이 반환. 그러면 언제 끝나고, 언제 끝나는지 모르는데 조건이 될 수가 없다.
//True 반환 경우 -> 꺼내온 게 있었다.
//False 반환 경우 -> 꺼내온 게 없었다.
while (true)
{
//무한 반복 -> 메시지가 없으면 if문 바깥으로 무시.
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
DWORD iTime = GetTickCount();
//메시지를 처리하기 전에,
if (WM_QUIT == msg.message) {
break;
}
//hAccelTable로 단축키를 불러와서 도움말의 ID정보가 IDM_ABOUT임.
//테이블의 단축키를 쓰지 않으면 필요없는 부분.
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
//해당 윈도우에서 그 메시지를 처리해라
//WndProc 함수 포인터로 그걸 처리함.
TranslateMessage(&msg);
DispatchMessage(&msg);
}
dwAccCount = (GetTickCount() - iTime);
}
else {
//메시지가 없는 동안 호출.
//메시지를 처리하는 호출시간의 비율은 굉장히 적음.
//메시지가 발생하지 않는 99.xx%의 시간. Game 코드 수행.
//GetMessage방식은 거의 메시지를 처리하는 시간이 적으니까, 나머지 시간은 그대로 놀게 하는 방식
//그래서, 굉장히 비효율적인 방식이라고 할 수 있겠다.
//비동기 함수로, 이 순간에 마우스가 눌려있는지 안 눌려있는지 알 수 있음.
DWORD dwCurCount = GetTickCount();
if (dwCurCount - dwPrevCount > 1000) {
float fRatio = (float)dwAccCount / 1000.f;
wchar_t szBuff[50] = {};
//wsprintf(szBuff, L"비율 : %f", fRatio);
swprintf_s(szBuff, L"비율 : %f", fRatio);
SetWindowText(g_hWnd, szBuff);
dwPrevCount = dwCurCount;
}
//Game 코드 수행
//디자인 패턴(설계 유형)
//다른 패턴은 잘 몰라도, 싱글톤 패턴은 너무나도 기본적인 것.
}
}
//두 번재 매개변수가 ID.
//KillTimer(g_hWnd, 0);
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;
HDC hdc = BeginPaint(hWnd, &ps); //Devicde Context.(그리기)
//CreatePen 쓸 때, 다 찾아보면서 해라. 검색하면 다 나옴.
//내부적으론 BGR인데 <<로 밀어서 순서 맞춰서 처리해줌.
HPEN hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
//자주 쓰는 펜, 자주 쓰는 오브젝트는 이미 다 만들어놨음.
HBRUSH hBlueBrush = CreateSolidBrush(RGB(0, 0, 255));
//hdc보고, hRedPen을 선택하라. 라고 주는거임.
//기존에 있던 defaultPen은 붕 떠버림.
//HHDIOBJ를 SelectObject가 반환함. 그래서 HPEN으로 줄 수 없으니, 캐스팅을 해줘야만 함.
//기본 펜 ID값(기존에 사용하고 있던 ID값을 받아온다)
HPEN hDefaultPen = static_cast<HPEN>(SelectObject(hdc, hRedPen));
HBRUSH hDefaultBrush = static_cast<HBRUSH>(SelectObject(hdc, hBlueBrush));
//3일차. 여러 개의 물체가 있다면???
// 물체마다, 좌표가 달라서... 한 물체가 움직이면 다른 물체에게 영향을 미칠 수 있어서 전체적으로 재갱신을 해줘야만 함.
//게임이란 렌더링, 화면을 매 순간 적용하기 위해 화면을 계속 그리는 반복의 연속임.(프레임이라는 개념)
//변경된 펜으로 사각형 그림.
if (bLbtnDown) {
Rectangle(hdc, g_ptLT.x, g_ptLT.y, g_ptRB.x, g_ptRB.y);
}
//물체가 늘어나면 늘어날수록 화면 깜빡임이 되게 심해짐...
//왜 그럴까? 마우스만 움직여도 InvalidateRect로 계속 다시 그림. 깜빡임 현상은 컴퓨터의 속도가 중요한 게 아님...
//이건 속도의 문제가 아니라 타이밍의 문제임. 사람의 눈이 60프레임이라고 할때, 우리가 눈을 깜빡이는 0.16초 이후 0.01초 사이에 총알 지나가면 총알 못봄
//우리는 그걸 그리는 중간 중간에 봐서 깜빡이는 것 처럼 보임
//현재 1번 문제.
//그래서 우리는 픽셀 데이터(도화지)를 2개를 관리할 것임. 완성된 그림만을 보여주기 위함.
//현재 프로그램 2번 문제.
//또 메시지가 존재하지 않으면 프로그램이 돌지 않는다는 문제도 존재함. 인공지능이나 AI가 움직이는 건 어떻게 하려고?
//계속 계속 돌아가게 해줘야함.
// 우회 방법 -> 일정 시간마다 일부로 메시지를 발생시키는 방법이 있긴 한데
// 정석 방법 -> 프로그램의 구조자체를 변경하는 것.
for (auto& info : g_vecInfo) {
Rectangle(hdc,
info.g_ptObjectPos.x - info.g_ptObjScale.x / 2,
info.g_ptObjectPos.y - info.g_ptObjScale.y / 2,
info.g_ptObjectPos.x + info.g_ptObjScale.x / 2,
info.g_ptObjectPos.y + info.g_ptObjScale.y / 2);
}
//빨간 펜 다 썼으니, 기본 펜으로 다시 바꿔줌.
SelectObject(hdc, hDefaultPen);
SelectObject(hdc, hDefaultBrush);
//그리고 빨간 펜을 지워준다.
DeleteObject(hRedPen);
DeleteObject(hBlueBrush);
//그리기 종료.
EndPaint(hWnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
//마우스 왼쪽 누르기.
//4바이트 param인데, 2바이트 | 2바이트 이렇게 나눠서 비트마스킹... 비슷하게 해서 마우스 좌표를 보냄.
//코드보면 비트연산(<<, >>)으로 밀어서 가져옴.
//마우스의 x좌표, y좌표.
g_ptLT.x = LOWORD(lParam);
g_ptLT.y = HIWORD(lParam);
bLbtnDown = true;
}
break;
case WM_MOUSEMOVE:
g_ptRB.x = LOWORD(lParam);
g_ptRB.y = HIWORD(lParam);
//InvalidateRect(hWnd, nullptr, true);
break;
case WM_LBUTTONUP:
{
tObjInfo info = {};
info.g_ptObjectPos.x = (g_ptLT.x + g_ptRB.x) / 2;
info.g_ptObjectPos.y = (g_ptLT.y + g_ptRB.y) / 2;
info.g_ptObjScale.x = abs(g_ptRB.x - g_ptLT.x);
info.g_ptObjScale.y = abs(g_ptRB.y - g_ptLT.y);
g_vecInfo.push_back(info);
bLbtnDown = false;
}
break;
case WM_TIMER:
{
int a = 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
{
//키가 눌리는 걸 감지하는 것.
//무슨 키냐, 라는 걸 알아야 하지 않나?
switch (wParam) {
case VK_UP:
//위쪽 화살표.
//g_ptObjectPos.y -= 10;
//누를때마다 화면 갱신을 해줘야지.
// 이렇게 하면, 기존에 그려진 부분은 그대로 남아있슴... 그래서 기존에 있던 걸 지워줘야함.
//InvalidateRect(hWnd, nullptr, false);
//true로 바꾸면, 기존에 그려져 있던 픽셀들 전부 깔끔하게 청소함.
InvalidateRect(hWnd, nullptr, true);
break;
case VK_DOWN:
//g_ptObjectPos.y += 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_LEFT:
//g_ptObjectPos.x -= 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_RIGHT:
//g_ptObjectPos.x += 10;
InvalidateRect(hWnd, nullptr, true);
break;
//꾹 누르고 있으면 한템포 느림...
//KEYDOWN은 한 번 눌리고, 어느정도 딜레이 이후에 계속 들어오는 방식이라 그럼.
case 'W':
{
int a = 0;
}
//일반 키. 대문자 기준으로만 됨.
break;
}
}
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;
}
오늘도 중요한 것만 정리해보자.
#include <vector>
using std::vector;
struct tObjInfo {
POINT g_ptObjectPos;
POINT g_ptObjScale;
};
vector<tObjInfo> g_vecInfo;
//좌 상단
POINT g_ptLT;
//우 하단.
POINT g_ptRB;
bool bLbtnDown = false;
- 처음에 이건, 마우스를 드래그하여 만든 사각형들의 벡터이다.
- 또, g_ptLT, g_ptRB로 마우스의 움직임을 받는다.
- bLbtnDown은 변수이름을 보다시피, 마우스 왼쪽 버튼을 누를 시에 true되는 변수.
//타이머도 부착된 커널 오브젝틍임.
//두 번째 매개변수가 ID.
//SetTimer(g_hWnd, 0, 0, nullptr);
//GetMessage때문에 메시지 기반 처리 방식임. 메시지가 올 때까지 기다림...
//PM_REMOVE 확인한 메시지가 있으면, 그 메시지를 제거함. -> 안 그러면 메시지가 계속 쌓임...
DWORD dwPrevCount = GetTickCount();
DWORD dwAccCount = GetTickCount();
// msg.message == WM_QUIT이면 FALSE반환함.
//그 이외에는 TRUE 당연히 계속 유지되어야 하니까.
//
//PeekMessage는 언제 반환되느냐? -> 메시지의 유무와 관계없이 반환. 그러면 언제 끝나고, 언제 끝나는지 모르는데 조건이 될 수가 없다.
//True 반환 경우 -> 꺼내온 게 있었다.
//False 반환 경우 -> 꺼내온 게 없었다.
while (true)
{
//무한 반복 -> 메시지가 없으면 if문 바깥으로 무시.
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
DWORD iTime = GetTickCount();
//메시지를 처리하기 전에,
if (WM_QUIT == msg.message) {
break;
}
//hAccelTable로 단축키를 불러와서 도움말의 ID정보가 IDM_ABOUT임.
//테이블의 단축키를 쓰지 않으면 필요없는 부분.
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
//해당 윈도우에서 그 메시지를 처리해라
//WndProc 함수 포인터로 그걸 처리함.
TranslateMessage(&msg);
DispatchMessage(&msg);
}
dwAccCount = (GetTickCount() - iTime);
}
else {
//메시지가 없는 동안 호출.
//메시지를 처리하는 호출시간의 비율은 굉장히 적음.
//메시지가 발생하지 않는 99.xx%의 시간. Game 코드 수행.
//GetMessage방식은 거의 메시지를 처리하는 시간이 적으니까, 나머지 시간은 그대로 놀게 하는 방식
//그래서, 굉장히 비효율적인 방식이라고 할 수 있겠다.
//비동기 함수로, 이 순간에 마우스가 눌려있는지 안 눌려있는지 알 수 있음.
DWORD dwCurCount = GetTickCount();
if (dwCurCount - dwPrevCount > 1000) {
float fRatio = (float)dwAccCount / 1000.f;
wchar_t szBuff[50] = {};
//wsprintf(szBuff, L"비율 : %f", fRatio);
swprintf_s(szBuff, L"비율 : %f", fRatio);
SetWindowText(g_hWnd, szBuff);
dwPrevCount = dwCurCount;
}
//Game 코드 수행
//디자인 패턴(설계 유형)
//다른 패턴은 잘 몰라도, 싱글톤 패턴은 너무나도 기본적인 것.
}
- 오늘의 핵심부분이다. 기본 WINAPI는 메시지 기반 처리라서 게임에 적합하지가 않다.
- 이유는 메시지가 올 때까지 기다리니까. 저 위의 시간 재는 코드로 해보면, 99%이상의 시간이 else문. 즉, 메시지를 받지 않을 때의 시간이다.
- 따라서 굉장한 비효율 방식으로, 이후 비동기 방식을 사용하여 메시지만 독립적으로? 처리하는 방식으로 구조를 변경하겠다.
- 그래서 GetMessage대신, PeekMessage로 메시지를 가져오는 것으로 변경. 내부는 주석을 읽어보길.
//3일차. 여러 개의 물체가 있다면???
// 물체마다, 좌표가 달라서... 한 물체가 움직이면 다른 물체에게 영향을 미칠 수 있어서 전체적으로 재갱신을 해줘야만 함.
//게임이란 렌더링, 화면을 매 순간 적용하기 위해 화면을 계속 그리는 반복의 연속임.(프레임이라는 개념)
//변경된 펜으로 사각형 그림.
if (bLbtnDown) {
Rectangle(hdc, g_ptLT.x, g_ptLT.y, g_ptRB.x, g_ptRB.y);
}
//물체가 늘어나면 늘어날수록 화면 깜빡임이 되게 심해짐...
//왜 그럴까? 마우스만 움직여도 InvalidateRect로 계속 다시 그림. 깜빡임 현상은 컴퓨터의 속도가 중요한 게 아님...
//이건 속도의 문제가 아니라 타이밍의 문제임. 사람의 눈이 60프레임이라고 할때, 우리가 눈을 깜빡이는 0.16초 이후 0.01초 사이에 총알 지나가면 총알 못봄
//우리는 그걸 그리는 중간 중간에 봐서 깜빡이는 것 처럼 보임
//현재 1번 문제.
//그래서 우리는 픽셀 데이터(도화지)를 2개를 관리할 것임. 완성된 그림만을 보여주기 위함.
//현재 프로그램 2번 문제.
//또 메시지가 존재하지 않으면 프로그램이 돌지 않는다는 문제도 존재함. 인공지능이나 AI가 움직이는 건 어떻게 하려고?
//계속 계속 돌아가게 해줘야함.
// 우회 방법 -> 일정 시간마다 일부로 메시지를 발생시키는 방법이 있긴 한데
// 정석 방법 -> 프로그램의 구조자체를 변경하는 것.
for (auto& info : g_vecInfo) {
Rectangle(hdc,
info.g_ptObjectPos.x - info.g_ptObjScale.x / 2,
info.g_ptObjectPos.y - info.g_ptObjScale.y / 2,
info.g_ptObjectPos.x + info.g_ptObjScale.x / 2,
info.g_ptObjectPos.y + info.g_ptObjScale.y / 2);
}
- case WM_PAINT: 의 내부부분. 화면깜빡임이 굉장히 심한데, 그 이유는 성능이 부족하기 떄문이 아님.
- 컴퓨터가 그리는 도중에 우리의 눈이 보기 때문. 30개를 그려야 하는데, 10개만 그린 걸 보여서…
- 그래서 현재 구조는 2가지 문제가 있는데 1번째 구조는 픽셀 데이터(도화지)를 2개를 관리하여, 완성된 그림만을 보여줄 것.
- 2번째 구조는 메시지 기반의 처리구조를 바꿀 것(위에서 바꾸었다.)
댓글 남기기