这里我们提出一种游戏循环的概念,游戏循环是将原先程序中的消息循环加以修改,方法是判断其中的内容目前是否有要处理的消息,如果有则进行处理,否则按照设定的时间间隔来重绘画面。下面是接下来一段游戏循环的程序代码:
//游戏循环
while( msg.message!=WM_QUIT ) //注释点1(详细内容见下)
{
if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) ) //注释点2(详细内容见下)
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
tNow = GetTickCount(); //注释点3
if(tNow-tPre >= 100) //注释点4
MyPaint(hdc);
}
}
我们来讲解一下游戏循环片段中的几个重点。
<1>注释点1:当收到的msg.message不是窗口结束消息WM_QUIT,则继续运行循环,其中msg是一个MSG的消息结构,其结构成员message则是一个消息类型的代号。
<2>注释点2:使用PeekMessage()函数来检测目前是否有需要处理的消息,若检测到消息(包含WM_QUIT消息)则会返回一个非“0”值,否则返回“0”.因此在游戏循环中,若检测到消息便进行消息的处理,否则运行else叙述之后的程序代码。这里我们要注意的是,PeekMessage()函数不能用原先消息循环的条件GetMessage()取代,因为GetMessage()函数只有在取得WM_QUIT消息时才会返回“0”,其他时候则是返回非“0”值或“-1”(发生错误时)
<3>注释点3:GetTickCount()函数会取得系统开始运行到目前所经过的时间,单位是毫秒(milliseconds)。 之前我理解错了,在这里感谢worldy的指出我的错误。
DWORD GetTickCount() //取得系统开始到目前经过的时间
这里取得时间的目的主要是可以搭配接下来的判断式,用来调整游戏运行的速度,使得游戏不会因为运行计算机速度的不同而跑得太快或者太慢。
<4>注释点4:if条件式中,“tPre”记录前次绘图的时间,而“tNow-tRre”则是计算上次绘图到这次循环运行之间相差多少时间。这里设置为若相差40个单位时间以上则再次进行绘图的操作,通过这个数值的控制可以调整游戏运行的速度。这里设定40个单位时间(微秒)的原因是,因为每隔40个单位进行一次绘图的操作,那么1秒钟大约重绘窗口1000/40=25次刚好可以达到期望值。
由于循环的运行速度远比定时器发出时间信号来得快,因此使用游戏循环可以更精准地控制程序运行速度并提高每秒钟画面重绘的次数。
了解了游戏循环使用的基本概念之后,接下来的范例将以游戏循环的方法进行窗口的连续贴图,更精确地制作游戏动画效果。
#include “stdafx.h”
#include <stdio.h>
//全局变量声明
HINSTANCE hInst;
HBITMAP man[7];
HDC hdc,mdc;
HWND hWnd;
DWORD tPre,tNow,tCheck; //声明三个函数来记录时间,tPre记录上一次绘图的时间,tNow记录此次准备绘图的时间,tCheck记录每秒开始的时间
int num,frame,fps; //num用来记录图号,frame用来累加每次画面更新的次数,fps(frame per second)用来记录每秒画面更新的次数
//全局函数的声明
ATOM MyRegisterClass
(HINSTANCE hInstance);
BOOL InitInstance
(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
void MyPaint(HDC hdc);
//***WinMain函数,程序入口点函数**************************************
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
MyRegisterClass(hInstance);
//运行初始化函数
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
GetMessage(&msg,NULL,NULL,NULL); //感谢xiaoxiangp的提醒,需要在进入消息循环之前初始化msg,避免了死循环发生的可能性。
//游戏循环
while( msg.message!=WM_QUIT )
{
if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
tNow = GetTickCount();
if(tNow-tPre >= 100) //当此次循环运行与上次绘图时间相差0.1秒时再进行重绘操作
MyPaint(hdc);
}
}
return msg.wParam;
}
//****设计一个窗口类,类似填空题,使用窗口结构体*************************
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW |
CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hCursor = LoadCursor(NULL,
IDC_ARROW);
wcex.hbrBackground = (HBRUSH)
(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = “canvas”;
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
//****初始化函数*************************************
// 从文件加载位图
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
char filename[20] = “”;
int i;
hInst = hInstance;
hWnd = CreateWindow(“canvas”, “动画演示” ,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
MoveWindow(hWnd,10,10,600,450,true);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
hdc = GetDC(hWnd);
mdc = CreateCompatibleDC(hdc);
//载入各个人物位图
for(i=0;i<7;i++)
{
sprintf(filename,“man%d.bmp”,i);
man[i] = (HBITMAP)LoadImage
(NULL,filename,IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
}
num = 0;
frame = 0;
MyPaint(hdc);
return TRUE;
}
//****自定义绘图函数*********************************
// 1.计算与显示每秒画面更新次数
// 2.按照图号顺序进行窗口贴图
void MyPaint(HDC hdc)
{
char str[40] = “”;
if(num == 7)
num = 0;
frame++; //画面更新次数加1
if(tNow - tCheck >= 1000) //
tbw判断此次绘图时间由前一秒算起是否已经达到1秒钟的时间间隔。若是,则将目前的‘frame’值赋给“fps”,表示这一秒内所更新的画面次数,然后将“frame”值回0,并重设下次计算每秒画面数的起始时间“iCheck”.
{
fps = frame;
frame = 0;
tCheck = tNow;
}
SelectObject(mdc,man[num]); //选用要更新的图案到mdc中,再输出显示每秒画面更新次数的字符串到mdc上,最后将mdc的内容贴到窗口中。
sprintf(str,“每秒显示 %d个画面”,fps);
TextOut(mdc,0,0,str,strlen(str));
BitBlt(hdc,0,0,600,450,mdc,0,0,SRCCOPY);
tPre = GetTickCount(); //记录此次绘图时间,供下次游戏循环中判断是否已经达到画面更新操作设定的时间间隔。
num++;
}
//******消息处理函数*********************************
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
int i;
switch (message)
{
case WM_DESTROY: //窗口结束消息
DeleteDC(mdc);
for(i=0;i<7;i++)
DeleteObject(man[i]);
ReleaseDC(hWnd,hdc);
PostQuitMessage(0);
break;
default: //其他消息
return DefWindowProc(hWnd,
message, wParam, lParam);
}
return 0;
}
程序的运行结果如下图: