[原]【Visual C++】游戲開發(fā)筆記之十一 基礎(chǔ)動畫顯示(四) 排序貼圖
Posted on 2012-04-15 16:37 zljpp 閱讀(115) 評論(0) 編輯 收藏本系列文章由zhmxy555編寫,轉(zhuǎn)載請注明出處。 http://blog.csdn.net/zhmxy555/article/details/7385605
作者:毛星云 郵箱: happylifemxy@qq.com 歡迎郵件交流編程心得
“排序貼圖”是源自于物體遠近呈現(xiàn)的一種貼圖概念。回憶我們之前筆記的貼圖思想,先進行距離比較遠的物體的貼圖操作,然后再進行近距離物體的貼圖操作,一旦定出貼圖的順序之后就無法再改變了。
然而這樣的作法在畫面上物體會彼此遮掩的情況下就會不適用。也許會出現(xiàn)后面的物體反而遮住了前面的物體的這種不協(xié)調(diào)的畫面。為了避免這種因為貼圖順序而固定而產(chǎn)生的錯誤畫面,必須在每一次窗口重新顯示時動態(tài)地重新決定畫面上每一個物體的貼圖順序。
那么,如何動態(tài)決定貼圖順序呢?我們可以采用排序的方式。
為了演示排序如何運用在貼圖中,我們舉一個例子。假設(shè)現(xiàn)在有10只要進行貼圖的小牛圖案,先把它存在一個數(shù)組之中,從2D平面的遠近角度來看,Y軸坐標比較小的是比較遠的物體。如果我們以小牛的Y軸坐標(要排序的值被我們稱作鍵值)來對小牛數(shù)組由小到大進行排序,最后會使得Y軸坐標小的小牛排在數(shù)組的前面,而進行畫面貼圖時則由數(shù)組由小到大一個個進行處理,這樣便可實現(xiàn)“遠的物體先貼圖“的目的了。
這里我們使用氣泡排序(Bubble Sort)作為我們的排序法,因為此方法有程序代碼簡單,排序效率中等,屬于穩(wěn)定(stable)排序法的特點。這里的穩(wěn)定排序法的特性,會使得Y軸坐標相同的物體,不必再去考慮它X坐標上的排序。
下面我們貼出以C/C++寫出的氣泡排序法的代碼,對”pop[ ]“數(shù)組的各數(shù)據(jù)成員的Y值為鍵值來排序,輸出的參數(shù)為”n“表示要排序的數(shù)組大小:
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) { //進行數(shù)組元素的交換 tmp = pop[j+1]; pop[j+1] = pop[j]; pop[j] = tmp; f = true; } } if(!f) //無交換操作則結(jié)束循環(huán) break; } }
各種排序法為C/C++中比較核心的知識點,還不太熟悉的朋友,可以參看各種C++的教程進行深入學(xué)習(xí)。在這里我就不多做介紹了。
接下來,我們來利用一個范例來演示氣泡排序法在畫面上貼圖的運用,讓動畫能呈現(xiàn)出接近真實的遠近層次效果。這個范例比較有趣,會產(chǎn)生多只恐龍隨機跑動,每次進行畫面貼圖前先完成排序操作,并對恐龍跑動進行貼圖坐標的修正,呈現(xiàn)出比較順暢真實的動畫來。
廢話這里就不多說了,直接上已經(jīng)詳細注釋的代碼(這回的代碼量就有些大了,不過我專門注釋得更詳細了些,其實它比之前的代碼還更好懂):
#include "stdafx.h" #include <stdio.h> //定義一個結(jié)構(gòu)體 struct dragon //定義dragon結(jié)構(gòu),代表畫面上的恐龍,其結(jié)構(gòu)成員x和y為貼圖坐標,dir為目前恐龍的移動方向 { int x,y; int dir; }; //定義常數(shù) const int draNum = 12; //定義常數(shù)draNum,代表程序在畫面上要出現(xiàn)的恐龍數(shù)目,在此設(shè)定為12個 //全局變量定義 HINSTANCE hInst; HBITMAP draPic[4],bg; //draPic[4]儲存恐龍上下左右移動的連續(xù)圖案,bg為存儲背景圖 HDC hdc,mdc,bufdc; HWND hWnd; DWORD tPre,tNow; int picNum; dragon dra[draNum]; //按照draNum的值建立數(shù)組dra[],產(chǎn)生畫面上出現(xiàn)的恐龍。 //全局函數(shù)聲明 ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void MyPaint(HDC hdc); //****WinMain函數(shù),程序入口點函數(shù)************************************** 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 //消息循環(huán) 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; } //****設(shè)計一個窗口類,類似填空題,使用窗口結(jié)構(gòu)體************************* 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); } //****初始化函數(shù)************************************* // 加載位圖并設(shè)定各初始值 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); //設(shè)定所有恐龍初始的貼圖坐標都為(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; } } //****自定義繪圖函數(shù)********************************* // 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); //貼上恐龍圖之前調(diào)用BubSort()函數(shù)進行排序 //下面這個for循環(huán),按照目前恐龍的移動方向dra[i].dir,選取對應(yīng)的位圖到bufdc中,并設(shè)定截切的大小。每一張要在窗口上出現(xiàn)的恐龍圖案依次先在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循環(huán),決定每一只恐龍下一次的移動方向及貼圖坐標 for(i=0;i<draNum;i++) { switch(rand()%4) //隨機數(shù)除以4的余數(shù)來決定下次移動方向,余數(shù)0,1,2,3分別代表上,下,左,右 { //case 0里面的代碼,按照目前的移動方向來修正因為各個方向圖案尺寸不一致而產(chǎn)生的貼圖坐標誤差,加入恐龍每次移動的單位量(上,下,左,右每次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; } //在計算出新的貼圖坐標之后,還需判斷此新的坐標會不會使得恐龍貼圖超出窗口邊界,若超出,則將該方向上的坐標設(shè)定為剛好等于臨界值 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; } } } //****消息處理函數(shù)*********************************** LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { int i; case WM_DESTROY: //窗口結(jié)束消息,撤銷各種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; }
程序運行結(jié)果如下:

從圖中可以看出,由于貼圖前進行了排序操作,因此使得恐龍彼此之間沒有錯誤的遮掩。
我們也可以按自己的喜好,通過設(shè)定程序中最前面定義的draNum常數(shù)值來改變畫面上出現(xiàn)的恐龍數(shù)目。
筆記十一到這里就結(jié)束了。
本節(jié)源代碼請點擊這里下載: 【Visual C++】Code_Note_11
感謝一直支持【Visual C++】游戲開發(fā)筆記系列專欄的朋友們,也請大家繼續(xù)關(guān)注我的博客,我一有空就會把自己的學(xué)習(xí)心得,覺得比較好的知識點寫出來和大家一起分享。
精通游戲開發(fā)的路還很長很長,非常希望能和大家一起交流,共同學(xué)習(xí)和進步。
大家看過后覺得有啟發(fā)的話可以頂一下這篇文章,讓更多的朋友有機會看到它。也希望大家可以多留言來和我探討編程相關(guān)的問題。最后,謝謝大家一直的支持~~~
The end