【Visual C++】遊戲開發筆記之十一 基礎動畫顯示(四) 排序貼圖
淺墨歷時一年為遊戲程式設計愛好者鍛造的著作:《逐夢旅程:Windows遊戲程式設計之從零開始》如果你喜歡淺墨寫的【Visual C++】遊戲開發系列部落格文章,那麼你一定會愛上這本書。這是淺墨專門為熱愛遊戲程式設計的朋友們寫的入門級遊戲程式設計寶典。噹噹網|京東商城|亞馬遜------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
“排序貼圖”是源自於物體遠近呈現的一種貼圖概念。回憶我們之前筆記的貼圖思想,先進行距離比較遠的物體的貼圖操作,然後再進行近距離物體的貼圖操作,一旦定出貼圖的順序之後就無法再改變了。
然而這樣的作法在畫面上物體會彼此遮掩的情況下就會不適用。也許會出現後面的物體反而遮住了前面的物體的這種不協調的畫面。為了避免這種因為貼圖順序而固定而產生的錯誤畫面,必須在每一次視窗重新顯示時動態地重新決定畫面上每一個物體的貼圖順序。
那麼,如何動態決定貼圖順序呢?我們可以採用排序的方式。
為了演示排序如何運用在貼圖中,我們舉一個例子。假設現在有10只要進行貼圖的小牛圖案,先把它存在一個數組之中,從2D平面的遠近角度來看,Y軸座標比較小的是比較遠的物體。如果我們以小牛的Y軸座標(要排序的值被我們稱作鍵值)來對小牛陣列由小到大進行排序,最後會使得Y軸座標小的小牛排在陣列的前面,而進行畫面貼圖時則由陣列由小到大一個個進行處理,這樣便可實現“遠的物體先貼圖“的目的了。
這裡我們使用氣泡排序(Bubble Sort)作為我們的排序法,因為此方法有程式程式碼簡單,排序效率中等,屬於穩定(stable)排序法的特點。這裡的穩定排序法的特性,會使得Y軸座標相同的物體,不必再去考慮它X座標上的排序。
下面我們貼出以C/C++寫出的氣泡排序法的程式碼,對”pop[ ]“陣列的各資料成員的Y值為鍵值來排序,輸出的引數為”n“表示要排序的陣列大小:
void BubSort(int n) { int i,j; bool f; pop tmp; for(i=0;i<n-1;i++) { f = false; for(j=0;j<n-i-1;j++) { if (pop[j+1].y < pop[j].y) { //進行陣列元素的交換 tmp = pop[j+1]; pop[j+1] = pop[j]; pop[j] = tmp; f = true; } } if(!f) //無交換操作則結束迴圈 break; } }
各種排序法為C/C++中比較核心的知識點,還不太熟悉的朋友,可以參看各種C++,資料結構的教程進行深入學習。在這裡我就不多做介紹了。
接下來,我們來利用一個範例來演示氣泡排序法在畫面上貼圖的運用,讓動畫能呈現出接近真實的遠近層次效果。這個範例比較有趣,會產生多隻恐龍隨機跑動,每次進行畫面貼圖前先完成排序操作,並對恐龍跑動進行貼圖座標的修正,呈現出比較順暢真實的動畫來。
廢話這裡就不多說了,直接上已經詳細註釋的程式碼(這回的程式碼量就有些大了,不過我專門註釋得更詳細了些,其實它比之前的程式碼還更好懂):
#include "stdafx.h"#include <stdio.h>//定義一個結構體struct dragon //定義dragon結構,代表畫面上的恐龍,其結構成員x和y為貼圖座標,dir為目前恐龍的移動方向{ int x,y; int dir;};//定義常數const int draNum = 12; //定義常數draNum,代表程式在畫面上要出現的恐龍數目,在此設定為12個//全域性變數定義HINSTANCE hInst;HBITMAP draPic[4],bg; //draPic[4]儲存恐龍上下左右移動的連續圖案,bg為儲存背景圖HDC hdc,mdc,bufdc;HWND hWnd;DWORD tPre,tNow;int picNum;dragon dra[draNum]; //按照draNum的值建立陣列dra[],產生畫面上出現的恐龍。//全域性函式宣告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);//初始化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) 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){ HBITMAP bmp; hInst = hInstance; int i; hWnd = CreateWindow("canvas", "繪圖視窗" , WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } MoveWindow(hWnd,10,10,640,480,true); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); hdc = GetDC(hWnd); mdc = CreateCompatibleDC(hdc); bufdc = CreateCompatibleDC(hdc); bmp = CreateCompatibleBitmap(hdc,640,480); //建立一個空點陣圖並放入mdc中 SelectObject(mdc,bmp); //載入各張恐龍跑動圖及背景圖,這裡以0,1,2,3來代表恐龍的上,下,左,右移動 draPic[0] = (HBITMAP)LoadImage(NULL,"dra0.bmp",IMAGE_BITMAP,528,188,LR_LOADFROMFILE); draPic[1] = (HBITMAP)LoadImage(NULL,"dra1.bmp",IMAGE_BITMAP,544,164,LR_LOADFROMFILE); draPic[2] = (HBITMAP)LoadImage(NULL,"dra2.bmp",IMAGE_BITMAP,760,198,LR_LOADFROMFILE); draPic[3] = (HBITMAP)LoadImage(NULL,"dra3.bmp",IMAGE_BITMAP,760,198,LR_LOADFROMFILE); bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,640,480,LR_LOADFROMFILE); //設定所有恐龍初始的貼圖座標都為(200,200),初始的移動方向都為向左。 for(i=0;i<draNum;i++) { dra[i].dir = 3; //起始方向 dra[i].x = 200; //貼圖的起始X座標 dra[i].y = 200; //貼圖的起始Y座標 } MyPaint(hdc); return TRUE;}//氣泡排序void BubSort(int n){ int i,j; bool f; dragon tmp; for(i=0;i<n-1;i++) { f = false; for(j=0;j<n-i-1;j++) { if(dra[j+1].y < dra[j].y) { tmp = dra[j+1]; dra[j+1] = dra[j]; dra[j] = tmp; f = true; } } if(!f) break; }}//****自定義繪圖函式*********************************// 1.對視窗中跑動的恐龍進行排序貼圖// 2.恐龍貼圖座標修正void MyPaint(HDC hdc){ int w,h,i; if(picNum == 8) picNum = 0; //在mdc中先貼上背景圖 SelectObject(bufdc,bg); BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY); BubSort(draNum); //貼上恐龍圖之前呼叫BubSort()函式進行排序 //下面這個for迴圈,按照目前恐龍的移動方向dra[i].dir,選取對應的點陣圖到bufdc中,並設定截切的大小。每一張要在視窗上出現的恐龍圖案依次先在mdc上進行透明貼圖的操作。 for(i=0;i<draNum;i++) { SelectObject(bufdc,draPic[dra[i].dir]); switch(dra[i].dir) { case 0: w = 66; h = 94; break; case 1: w = 68; h = 82; break; case 2: w = 95; h = 99; break; case 3: w = 95; h = 99; break; } BitBlt(mdc,dra[i].x,dra[i].y,w,h,bufdc,picNum*w,h,SRCAND); BitBlt(mdc,dra[i].x,dra[i].y,w,h,bufdc,picNum*w,0,SRCPAINT); } //將最後畫面顯示在視窗中 BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY); tPre = GetTickCount(); //記錄此次繪圖時間 picNum++; //下面這個for迴圈,決定每一隻恐龍下一次的移動方向及貼圖座標 for(i=0;i<draNum;i++) { switch(rand()%4) //隨機數除以4的餘數來決定下次移動方向,餘數0,1,2,3分別代表上,下,左,右 { //case 0裡面的程式碼,按照目前的移動方向來修正因為各個方向圖案尺寸不一致而產生的貼圖座標誤差,加入恐龍每次移動的單位量(上,下,左,右每次20個單位)而得到下次新的貼圖座標 case 0: //上 switch(dra[i].dir) { case 0: dra[i].y -= 20; break; case 1: dra[i].x += 2; dra[i].y -= 31; break; case 2: dra[i].x += 14; dra[i].y -= 20; break; case 3: dra[i].x += 14; dra[i].y -= 20; break; } //在計算出新的貼圖座標之後,還需判斷此新的座標會不會使得恐龍貼圖超出視窗邊界,若超出,則將該方向上的座標設定為剛好等於臨界值 if(dra[i].y < 0) dra[i].y = 0; dra[i].dir = 0; break; //其他方向按照和上面相同的方法計算 case 1: //下 switch(dra[i].dir) { case 0: dra[i].x -= 2; dra[i].y += 31; break; case 1: dra[i].y += 20; break; case 2: dra[i].x += 15; dra[i].y += 29; break; case 3: dra[i].x += 15; dra[i].y += 29; break; } if(dra[i].y > 370) dra[i].y = 370; dra[i].dir = 1; break; case 2: //左 switch(dra[i].dir) { case 0: dra[i].x -= 34; break; case 1: dra[i].x -= 34; dra[i].y -= 9; break; case 2: dra[i].x -= 20; break; case 3: dra[i].x -= 20; break; } if(dra[i].x < 0) dra[i].x = 0; dra[i].dir = 2; break; case 3: //右 switch(dra[i].dir) { case 0: dra[i].x += 6; break; case 1: dra[i].x += 6; dra[i].y -= 10; break; case 2: dra[i].x += 20; break; case 3: dra[i].x += 20; break; } if(dra[i].x > 535) dra[i].x = 535; dra[i].dir = 3; break; } }}//****訊息處理函式***********************************LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ switch (message) { int i; case WM_DESTROY: //視窗結束訊息,撤銷各種DC DeleteDC(mdc); DeleteDC(bufdc); for(i=0;i<4;i++) DeleteObject(draPic[i]); DeleteObject(bg); ReleaseDC(hWnd,hdc); PostQuitMessage(0); break; default: //其他訊息 return DefWindowProc(hWnd, message, wParam, lParam); } return 0;}
程式執行結果如下:
從圖中可以看出,由於貼圖前進行了排序操作,因此使得恐龍彼此之間沒有錯誤的遮掩。
我們也可以按自己的喜好,通過設定程式中最前面定義的draNum常數值來改變畫面上出現的恐龍數目。
筆記十一到這裡就結束了。
感謝一直支援【Visual C++】遊戲開發筆記系列專欄的朋友們,也請大家繼續關注我的部落格,我一有空就會把自己的學習心得,覺得比較好的知識點寫出來和大家一起分享。
精通遊戲開發的路還很長很長,非常希望能和大家一起交流,共同學習和進步。
大家看過後覺得有啟發的話可以頂一下這篇文章,讓更多的朋友有機會看到它。也希望大家可以多留言來和我探討程式設計相關的問題。最後,謝謝大家一直的支援~~~
The end
淺墨歷時一年為遊戲程式設計愛好者鍛造的著作:《逐夢旅程:Windows遊戲程式設計之從零開始》如果你喜歡淺墨寫的【Visual C++】遊戲開發系列部落格文章,那麼你一定會愛上這本書。這是淺墨專門為熱愛遊戲程式設計的朋友們寫的入門級遊戲程式設計寶典。噹噹網|京東商城|亞馬遜------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------