本系列文章由zhmxy555編寫,轉載請註明出處。 http://blog.csdn.net/zhmxy555/article/details/7385605
作者:毛星雲 郵箱: happylifemxy@qq.com 歡迎郵件交流編程心得
“排序貼圖”是源自於物體遠近呈現的一種貼圖概念。回憶我們之前筆記的貼圖思想,先進行距離比較遠的物體的貼圖操作,然後再進行近距離物體的貼圖操作,一旦定出貼圖的順序之後就無法再改變了。
然而這樣的作法在畫面上物體會彼此遮掩的情況下就會不適用。也許會出現後面的物體反而遮住了前面的物體的這種不協調的畫面。為了避免這種因為貼圖順序而固定而產生的錯誤畫面,必須在每一次視窗重新顯示時動態地重新決定畫面上每一個物體的貼圖順序。
那麼,如何動態決定貼圖順序呢?我們可以採用排序的方式。
為了示範排序如何運用在貼圖中,我們舉一個例子。假設現在有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為儲存背景圖HDChdc,mdc,bufdc;HWNDhWnd;DWORDtPre,tNow;intpicNum;dragon dra[draNum]; //按照draNum的值建立數組dra[],產生畫面上出現的恐龍。//全域函式宣告ATOMMyRegisterClass(HINSTANCE hInstance);BOOLInitInstance(HINSTANCE, int);LRESULT CALLBACKWndProc(HWND, UINT, WPARAM, LPARAM);voidMyPaint(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++】Code_Note_11
感謝一直支援【Visual C++】遊戲開發筆記系列專欄的朋友們,也請大家繼續關注我的部落格,我一有空就會把自己的學習心得,覺得比較好的知識點寫出來和大家一起分享。
精通遊戲開發的路還很長很長,非常希望能和大家一起交流,共同學習和進步。
大家看過後覺得有啟發的話可以頂一下這篇文章,讓更多的朋友有機會看到它。也希望大家可以多留言來和我探討編程相關的問題。
最後,謝謝大家一直的支援~~~
The end