【Visual C++】遊戲開發筆記之八——基礎動畫顯示(二)遊戲迴圈的使用

來源:互聯網
上載者:User

本系列文章由zhmxy555編寫,轉載請註明出處。  http://blog.csdn.net/zhmxy555/article/details/7355377

作者:毛星雲    郵箱: happylifemxy@qq.com    歡迎郵件交流編程心得


在筆記七中我們講解了用定時器來產生動畫的效果。定時器的使用固然簡單方便,但是事實上這樣的方法僅適合用在顯示簡易動畫及小型的遊戲程式中。因為一般而言,遊戲本身需要顯示順暢的遊戲畫面,使玩家感覺不到延遲的狀態。基本遊戲畫面必須在一秒鐘之內更新至少25次以上,這一秒鐘內程式還必須進行訊息的處理和大量數學運算甚至音效的輸出等操作。而使用定時器的訊息來驅動這些操作,往往達不到所要求的標準,不然就會產生畫面顯示不順暢和遊戲回應時間太長的情況。


這裡我們提出一種遊戲迴圈的概念,遊戲迴圈是將原先程式中的訊息迴圈加以修改,方法是判斷其中的內容目前是否有要處理的訊息,如果有則進行處理,否則按照設定的時間間隔來重繪畫面。下面是接下來一段遊戲迴圈的程式碼:

//遊戲迴圈    while( msg.message!=WM_QUIT )               //注釋點1(詳細內容見下)    {        if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )       //注釋點2(詳細內容見下)        {            TranslateMessage( &msg );            DispatchMessage( &msg );        }else{tNow = GetTickCount();                        //注釋點3if(tNow-tPre >= 100)                   //注釋點4MyPaint(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;HWNDhWnd;DWORDtPre,tNow,tCheck;                 //聲明三個函數來記錄時間,tPre記錄上一次繪圖的時間,tNow記錄此次準備繪圖的時間,tCheck記錄每秒開始的時間int num,frame,fps;                    //num用來記錄圖號,frame用來累加每次畫面更新的次數,fps(frame per second)用來記錄每秒畫面更新的次數//全域函數的聲明ATOM MyRegisterClass(HINSTANCE hInstance);BOOL InitInstance(HINSTANCE, int);LRESULT CALLBACKWndProc(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++;        //畫面更新次數加1if(tNow - tCheck >= 1000)               //判斷此次繪圖時間由前一秒算起是否已經達到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;}



程式的運行結果如:



當然想要得到上述動畫,我們需要把幾幅位元影像檔案放到工程檔案夾下。


這個範例中我們設定的畫面更新時間間隔為0.1秒,所以每秒鐘最多會更新10次畫面。不過如果在運行這個例子的時候同時也運行其他的程式,那麼CPU必須馬上出去處理所開啟的其他程式,因此可能會使得每秒畫面的更新次數稍稍下降。

 

 


 

筆記八到這裡就結束了。


本節原始碼請點擊這裡下載:【Visual C++】Code_Note_8


請大家繼續關注【Visual C++】遊戲開發筆記系列。

非常希望能與大家一起交流,共同學習和進步。

最後,謝謝大家的支援~~~

 

The end

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.