在本节中,我们将介绍PeekMessage()
函数,以及该函数与GetMessage()
函数的区别。实际上,GetMessage()
函数并没有什么区别,只是它的工作方式在游戏和持续活动中并没有取得惊人的成绩。我们将讨论这是什么原因以及PeekMessage()又是如何解决这个问题的。
GetMessage()的循环结构
在上一节中,我们使用GetMessage()
函数构建了一个简单的Windows应用程序,我们使用GetMessage()
函数和另外两个函数来创建一个循环体,用于处理所有收到的Windows消息,然而这种设计存在一个缺陷。下图是我们采用这种方式的循环体的工作流程图:
当我们创建窗口后,进入事件循环体中,并通过GetMessage(
)函数进行循环。然后,GetMessage()
等待消息,在收到消息后将其发送到下一步TranslateMessage()
进行处理。这个流程对于Windows编程来说是完全合乎逻辑的,因为WIndows应用程序通常什么都不做,而是一直等待,直到收到一条消息后才进行处理。然而这对于游戏来所并不适用,因为在这个等待的过程中,我们需要每秒创建30到60帧完全渲染的3D图像,并且毫无延迟低显示在屏幕上。这里就会遇到一个问题,Windows不可能每秒发送30条消息来进行图像渲染和显示。
一个新的函数PeekMessage()
为了解决前面提到的问题,我们将用新函数PeekMessage()
替换目前的GetMessage()
函数。这两个函数本质上是相同的,但是有一个最主要的区别:PeekMessage()
函数并不会等待任何东西。PeekMessage()首先查看消息队列,并检查是否有任何消息在等待。如果没有,则允许程序继续运行,去执行我们所需的操作;如果有消息则进行处理。
下面我们先详细介绍一下PeekMessage()
,其函数原型为:
BOOL PeekMessage(LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg);
前4个参数我们应该比较熟悉,因为它们和GetMessage()
的四个参数相同。但是第五个参数是新的,这个参数的作用是指示从事件队列中检索到的消息是应该保留在事件队列中还是应该删除。我们可以将PM_REMOVE
或PM_NOREMOVE
放在这个参数上,第一个表示在检索消息时将消息从队列中移除,第二个是将消息保留在队列中供以后检索。我们在此处使用PM_REMOVE
值,以使其保持相对简单的状态。
那么我们如何在程序中实现它呢?以下是我们主循环部分的代码:
// enter the main loop:
// this struct holds Windows event messages
MSG msg = {0};
// 进入无限消息循环
while(TRUE)
{
// 检查队列中是否有消息正在等待
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// translate keystroke messages into the right format
TranslateMessage(&msg);
// send the message to the WindowProc function
DispatchMessage(&msg);
// check to see if it's time to quit
if(msg.message == WM_QUIT)
break;
}
else
{
// 在这里运行游戏代码
// ...
// ...
}
}
现在,我们的程序就可以根据需要及时处理其他的事情,而不必一直等待Windows及其消息。下面我们简要介绍一下新出现的代码:
代码 | 作用 |
---|---|
while(TRUE) |
我们创建了一个无限循环,当我们关闭游戏时,它就会退出 |
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) |
我们不再是等待消息,而是检索里面的内容。如果有消息,函数返回TRUE,否则返回FALSE。如果有消息,那么将运行TranslateMessage()和DispatchMessage(),如果没有消息则转而执行游戏代码 |
if(msg.message == WM_QUIT) |
如果我们的消息是WM_QUIT,则意味着将退出无限循环并返回Windows。之前的主循环没有这行代码,因为如果要退出的话,GetMessage()将返回FALSE,从而中断了while()循环。但是这里没有这种机制,因此需要我们自己去处理它。 |
运行新的循环
下面我们用PeekMessage()修改之后的新代码。运行这个程序看起来与之前并没有什么不同,它仅仅影响你可以多久更改一次窗口。目前我们已经掌握了基本游戏编程所需的所有Windows知识,下面就可以开始进行DX编码了。
// include the basic windows header file
#include <windows.h>
#include <windowsx.h>
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// the handle for the window, filled by a function
HWND hWnd;
// this struct holds information for the window class
WNDCLASSEX wc;
// clear out the window class for use
ZeroMemory(&wc, sizeof(WNDCLASSEX));
// fill in the struct with the needed information
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";
// register the window class
RegisterClassEx(&wc);
// calculate the size of the client area
RECT wr = {0, 0, 500, 400}; // set the size, but not the position
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); // adjust the size
// create the window and use the result as the handle
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // name of the window class
L"Our First Windowed Program", // title of the window
WS_OVERLAPPEDWINDOW, // window style
300, // x-position of the window
300, // y-position of the window
wr.right - wr.left, // width of the window
wr.bottom - wr.top, // height of the window
NULL, // we have no parent window, NULL
NULL, // we aren't using menus, NULL
hInstance, // application handle
NULL); // used with multiple windows, NULL
// display the window on the screen
ShowWindow(hWnd, nCmdShow);
// enter the main loop:
// this struct holds Windows event messages
MSG msg = {0};
// Enter the infinite message loop
while(TRUE)
{
// Check to see if any messages are waiting in the queue
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// translate keystroke messages into the right format
TranslateMessage(&msg);
// send the message to the WindowProc function
DispatchMessage(&msg);
// check to see if it's time to quit
if(msg.message == WM_QUIT)
break;
}
else
{
// Run game code here
// ...
// ...
}
}
// return this part of the WM_QUIT message to Windows
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// sort through and find what code to run for the message given
switch(message)
{
// this message is read when the window is closed
case WM_DESTROY:
{
// close the application entirely
PostQuitMessage(0);
return 0;
} break;
}
// Handle any messages the switch statement didn't
return DefWindowProc (hWnd, message, wParam, lParam);
}