개발하는 리프터 꽃게맨입니다.

[WinAPI] 게임을 위한 메인 루프 만들기 본문

Window API

[WinAPI] 게임을 위한 메인 루프 만들기

파워꽃게맨 2024. 1. 31. 19:56

https://powerclabman.tistory.com/77

 

[WinAPI] hWnd 윈도우 핸들, hDC

hWnd 핸들 윈도우란 뭔가? 윈도우의 핸들은 운영체제의 커널 오브젝트를 사용하기 위해 필요한 장치라고 볼 수 있습니다. 우리가 프로그램으로 만든 윈도우는 고유 식별 id가 있습니다. 이런 고유

powerclabman.tistory.com

 

이전 포스팅에서 말했듯

GetMessage는 게임 루프를 돌리는데 적합하지 않습니다.

 

게임은 보통 1초에 60번의 업데이트 및 렌더링을 제공해야하는데,

GetMessage의 경우 메시지 큐에서 메시지를 꺼내고, 어떤 메시지인지 확인하는 과정이 시간이 생각보다 많이 걸리기 때문이죠.

더하여 메시지 큐에 메시지를 넣어주지 않으면 무한정 대기하는 참사가 일어납니다.

강제로 메시지를 넣어줄수도 있긴 한데 이거도 오버헤드가 만만치 않습니다.

 

그래서 우리는 메인 루프를 살짝 바꿔줄겁니다.

메시지를 받아서 처리하는 것이 아니라 그냥 일반적인 while 문으로 말이죠.

 

 

이것을

 

이런 식으로 바꿉니다.

왜 그런지 하나하나 알아봅시다.

 

일단 우리는 더 이상 별도로 윈도우 메시지를 처리하지 않을겁니다.

 

그래서 while 조건문에 

msg.message != WM_QUIT 라고 적어놨는데 이것은

WM_QUIT 즉, '프로그램 종료'라는 메시지가 존재할 때만, 안전하게 루프를 탈출하고 프로그램을 종료한다는 의미입니다.

 

이제 PeekMessage 를 봅시다.

이 함수는 큐에 메시지가 있는 경우에는 메시지를 확인하고, 없으면 넘깁니다.

즉, 굳이 대기하지 않고 넘어간다는 의미죠.

 

기존 getmessage는 메시지가 있으면 메시지를 처리하고, 없으면 메시지를 대기하는 모습을 보였는데,

대조되는 모습입니다.

 

물론 메시지 큐에 메시지가 들어오면 처리는 수행합니다.

대기하지 않고 넘어간다는게 중요하죠.

 

메시지가 없을 경우 false를 반환하고

/* 메인 루프 */ 를 수행합니다.

 

즉, else 문에 게임에 필요한 업데이트 & 렌더링 함수를 넣어주면 되겠습니다.

 

그러면 깔끔하게 정리해서 최종 

게임을 위한 Win32 메인 코드를 보여드리겠습니다.

 

#include "framework.h"
#include "Main.h"

/* 전역 변수 */
HINSTANCE	hInst;		// 핸들 인스턴스
HWND		ghwnd;		// 핸들 윈도우

/* 윈도우 클래스 및 타이틀 */
LPCWSTR		gwinClassName = L"MyAPIGame";
LPCWSTR		gwinTitleName = L"MyAPIGame";

/* 전방 선언 */
ATOM                MyRegisterClass(HINSTANCE hInstance);	// 클래스 등록함수
BOOL                InitInstance(HINSTANCE, int);			// 인스턴스 초기화
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);	// 윈도우 프로시저

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{

	/* 윈도우 정보를 등록 */
	MyRegisterClass(hInstance);

	/* 핸들 윈도우를 생성 */
	if (!InitInstance(hInstance, nCmdShow))
		return FALSE;

	MSG msg = {};

	// 기본 메시지 루프입니다:
	while (msg.message != WM_QUIT)
	{
		if (::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			/* 메인 루프 */
		}
	}

	return (int)msg.wParam;
}


ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEXW wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWAPISTUDY));
	wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = nullptr;
	wcex.lpszClassName = gwinClassName;
	wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassExW(&wcex);
}


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

	/* 윈도우 사이즈 조절 */
	RECT windowRect = { 0,0,800,600 };

	::AdjustWindowRect(&windowRect, WS_OVERLAPPED, false);

	HWND hWnd = CreateWindowW(gwinClassName, gwinTitleName, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0,
		windowRect.right - windowRect.left,
		windowRect.bottom - windowRect.top,
		nullptr, nullptr, hInstance, nullptr);

	ghwnd = hWnd; /* hwnd는 쓸데가 많으니 전역으로 저장해놓고 사용 */

	if (!hWnd)
		return FALSE;

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}