這里我們提出一種游戲循環的概念,游戲循環是將原先程序中的消息循環加以修改,方法是判斷其中的內容目前是否有要處理的消息,如果有則進行處理,否則按照設定的時間間隔來重繪畫面。下面是接下來一段游戲循環的程序代碼:
//游戲循環
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;
}
程序的運行結果如下圖: