【Visual C++】遊戲開發筆記之十一 基礎動畫顯示(四) 排序貼圖

“排序貼圖”是源自於物體遠近呈現的一種貼圖概念。回憶我們之前筆記的貼圖思想,先進行距離比較遠的物體的貼圖操作,然後再進行近距離物體的貼圖操作,一旦定出貼圖的順序之後就無法再改變了。


然而這樣的作法在畫面上物體會彼此遮掩的情況下就會不適用。也許會出現後面的物體反而遮住了前面的物體的這種不協調的畫面。爲了避免這種因爲貼圖順序而固定而產生的錯誤畫面,必須在每一次窗口重新顯示時動態地重新決定畫面上每一個物體的貼圖順序。

那麼,如何動態決定貼圖順序呢?我們可以採用排序的方式。



爲了演示排序如何運用在貼圖中,我們舉一個例子。假設現在有10只要進行貼圖的小牛圖案,先把它存在一個數組之中,從2D平面的遠近角度來看,Y軸座標比較小的是比較遠的物體。如果我們以小牛的Y軸座標(要排序的值被我們稱作鍵值)來對小牛數組由小到大進行排序,最後會使得Y軸座標小的小牛排在數組的前面,而進行畫面貼圖時則由數組由小到大一個個進行處理,這樣便可實現“遠的物體先貼圖“的目的了。



這裏我們使用氣泡排序(Bubble Sort)作爲我們的排序法,因爲此方法有程序代碼簡單,排序效率中等,屬於穩定(stable)排序法的特點。這裏的穩定排序法的特性,會使得Y軸座標相同的物體,不必再去考慮它X座標上的排序。



下面我們貼出以C/C++寫出的氣泡排序法的代碼,對”pop[ ]“數組的各數據成員的Y值爲鍵值來排序,輸出的參數爲”n“表示要排序的數組大小:


  1. void BubSort(int n)  
  2. {  
  3.     int i,j;  
  4.     bool f;  
  5.     pop tmp;  
  6.   
  7.     for(i=0;i<n-1;i++)  
  8.     {  
  9.         f = false;  
  10.         for(j=0;j<n-i-1;j++)  
  11.         {  
  12.             if(pop[j+1].y < pop[j].y)  
  13.             {       //進行數組元素的交換  
  14.                 tmp = pop[j+1];  
  15.                 pop[j+1] = pop[j];  
  16.                 pop[j] = tmp;  
  17.                 f = true;  
  18.             }  
  19.         }  
  20.         if(!f)                 //無交換操作則結束循環  
  21.             break;  
  22.     }  
  23. }  


各種排序法爲C/C++中比較核心的知識點,還不太熟悉的朋友,可以參看各種C++的教程進行深入學習。在這裏我就不多做介紹了。



接下來,我們來利用一個範例來演示氣泡排序法在畫面上貼圖的運用,讓動畫能呈現出接近真實的遠近層次效果。這個範例比較有趣,會產生多隻恐龍隨機跑動,每次進行畫面貼圖前先完成排序操作,並對恐龍跑動進行貼圖座標的修正,呈現出比較順暢真實的動畫來。


廢話這裏就不多說了,直接上已經詳細註釋的代碼(這回的代碼量就有些大了,不過我專門註釋得更詳細了些,其實它比之前的代碼還更好懂):


  1. #include "stdafx.h"  
  2. #include <stdio.h>  
  3.   
  4. //定義一個結構體  
  5. struct dragon        //定義dragon結構,代表畫面上的恐龍,其結構成員x和y爲貼圖座標,dir爲目前恐龍的移動方向  
  6. {  
  7.     int x,y;  
  8.     int dir;  
  9. };  
  10.   
  11. //定義常數  
  12. const int draNum = 12;  //定義常數draNum,代表程序在畫面上要出現的恐龍數目,在此設定爲12個  
  13. //全局變量定義  
  14. HINSTANCE hInst;  
  15. HBITMAP draPic[4],bg;   //draPic[4]儲存恐龍上下左右移動的連續圖案,bg爲存儲背景圖  
  16. HDC     hdc,mdc,bufdc;  
  17. HWND    hWnd;  
  18. DWORD   tPre,tNow;  
  19. int     picNum;  
  20. dragon  dra[draNum];   //按照draNum的值建立數組dra[],產生畫面上出現的恐龍。  
  21.   
  22.   
  23. //全局函數聲明  
  24. ATOM                MyRegisterClass(HINSTANCE hInstance);  
  25. BOOL                InitInstance(HINSTANCEint);  
  26. LRESULT CALLBACK    WndProc(HWNDUINTWPARAMLPARAM);  
  27. void                MyPaint(HDC hdc);  
  28.   
  29. //****WinMain函數,程序入口點函數**************************************  
  30. int APIENTRY WinMain(HINSTANCE hInstance,  
  31.                      HINSTANCE hPrevInstance,  
  32.                      LPSTR     lpCmdLine,  
  33.                      int       nCmdShow)  
  34. {  
  35.     MSG msg;  
  36.   
  37.     MyRegisterClass(hInstance);  
  38.   
  39.     //初始化  
  40.     if (!InitInstance (hInstance, nCmdShow))   
  41.     {  
  42.         return FALSE;  
  43.     }  
  44.     GetMessage(&msg,NULL,NULL,NULL);//初始化msg  
  45.     //消息循環  
  46.     while( msg.message!=WM_QUIT )  
  47.     {  
  48.         if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )  
  49.         {  
  50.             TranslateMessage( &msg );  
  51.             DispatchMessage( &msg );  
  52.         }  
  53.         else  
  54.         {  
  55.             tNow = GetTickCount();  
  56.             if(tNow-tPre >= 100)  
  57.                 MyPaint(hdc);  
  58.         }  
  59.     }  
  60.   
  61.     return msg.wParam;  
  62. }  
  63.   
  64. //****設計一個窗口類,類似填空題,使用窗口結構體*************************  
  65. ATOM MyRegisterClass(HINSTANCE hInstance)  
  66. {  
  67.     WNDCLASSEX wcex;  
  68.   
  69.     wcex.cbSize = sizeof(WNDCLASSEX);   
  70.     wcex.style          = CS_HREDRAW | CS_VREDRAW;  
  71.     wcex.lpfnWndProc    = (WNDPROC)WndProc;  
  72.     wcex.cbClsExtra     = 0;  
  73.     wcex.cbWndExtra     = 0;  
  74.     wcex.hInstance      = hInstance;  
  75.     wcex.hIcon          = NULL;  
  76.     wcex.hCursor        = NULL;  
  77.     wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);  
  78.     wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);  
  79.     wcex.lpszMenuName   = NULL;  
  80.     wcex.lpszClassName  = "canvas";  
  81.     wcex.hIconSm        = NULL;  
  82.   
  83.     return RegisterClassEx(&wcex);  
  84. }  
  85.   
  86. //****初始化函數*************************************  
  87. // 加載位圖並設定各初始值  
  88. BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)  
  89. {  
  90.     HBITMAP bmp;  
  91.     hInst = hInstance;  
  92.     int i;  
  93.   
  94.     hWnd = CreateWindow("canvas""繪圖窗口" , WS_OVERLAPPEDWINDOW,  
  95.         CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);  
  96.   
  97.     if (!hWnd)  
  98.     {  
  99.         return FALSE;  
  100.     }  
  101.   
  102.     MoveWindow(hWnd,10,10,640,480,true);  
  103.     ShowWindow(hWnd, nCmdShow);  
  104.     UpdateWindow(hWnd);  
  105.   
  106.     hdc = GetDC(hWnd);  
  107.     mdc = CreateCompatibleDC(hdc);  
  108.     bufdc = CreateCompatibleDC(hdc);  
  109.   
  110.     bmp = CreateCompatibleBitmap(hdc,640,480);  //建立一個空位圖並放入mdc中  
  111.     SelectObject(mdc,bmp);  
  112.   
  113.   
  114.     //加載各張恐龍跑動圖及背景圖,這裏以0,1,2,3來代表恐龍的上,下,左,右移動  
  115.     draPic[0] = (HBITMAP)LoadImage(NULL,"dra0.bmp",IMAGE_BITMAP,528,188,LR_LOADFROMFILE);  
  116.     draPic[1] = (HBITMAP)LoadImage(NULL,"dra1.bmp",IMAGE_BITMAP,544,164,LR_LOADFROMFILE);  
  117.     draPic[2] = (HBITMAP)LoadImage(NULL,"dra2.bmp",IMAGE_BITMAP,760,198,LR_LOADFROMFILE);  
  118.     draPic[3] = (HBITMAP)LoadImage(NULL,"dra3.bmp",IMAGE_BITMAP,760,198,LR_LOADFROMFILE);  
  119.     bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,640,480,LR_LOADFROMFILE);  
  120.   
  121.   
  122.     //設定所有恐龍初始的貼圖座標都爲(200,200),初始的移動方向都爲向左。  
  123.     for(i=0;i<draNum;i++)  
  124.     {  
  125.         dra[i].dir = 3;    //起始方向  
  126.         dra[i].x = 200;    //貼圖的起始X座標  
  127.         dra[i].y = 200;    //貼圖的起始Y座標  
  128.     }  
  129.   
  130.     MyPaint(hdc);  
  131.   
  132.     return TRUE;  
  133. }  
  134.   
  135. //氣泡排序  
  136. void BubSort(int n)  
  137. {  
  138.     int i,j;  
  139.     bool f;  
  140.     dragon tmp;  
  141.   
  142.     for(i=0;i<n-1;i++)  
  143.     {  
  144.         f = false;  
  145.         for(j=0;j<n-i-1;j++)  
  146.         {  
  147.             if(dra[j+1].y < dra[j].y)  
  148.             {  
  149.                 tmp = dra[j+1];  
  150.                 dra[j+1] = dra[j];  
  151.                 dra[j] = tmp;  
  152.                 f = true;  
  153.             }  
  154.         }  
  155.         if(!f)  
  156.             break;  
  157.     }  
  158. }  
  159.   
  160. //****自定義繪圖函數*********************************  
  161. // 1.對窗口中跑動的恐龍進行排序貼圖  
  162. // 2.恐龍貼圖座標修正  
  163. void MyPaint(HDC hdc)  
  164. {  
  165.     int w,h,i;  
  166.   
  167.     if(picNum == 8)  
  168.         picNum = 0;  
  169.   
  170.     //在mdc中先貼上背景圖  
  171.     SelectObject(bufdc,bg);  
  172.     BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY);  
  173.   
  174.     BubSort(draNum);    //貼上恐龍圖之前調用BubSort()函數進行排序  
  175.   
  176.   
  177.     //下面這個for循環,按照目前恐龍的移動方向dra[i].dir,選取對應的位圖到bufdc中,並設定截切的大小。每一張要在窗口上出現的恐龍圖案依次先在mdc上進行透明貼圖的操作。  
  178.     for(i=0;i<draNum;i++)  
  179.     {  
  180.         SelectObject(bufdc,draPic[dra[i].dir]);  
  181.         switch(dra[i].dir)  
  182.         {  
  183.             case 0:  
  184.                 w = 66;  
  185.                 h = 94;  
  186.                 break;  
  187.             case 1:  
  188.                 w = 68;  
  189.                 h = 82;  
  190.                 break;  
  191.             case 2:  
  192.                 w = 95;  
  193.                 h = 99;  
  194.                 break;  
  195.             case 3:  
  196.                 w = 95;  
  197.                 h = 99;  
  198.                 break;  
  199.         }  
  200.         BitBlt(mdc,dra[i].x,dra[i].y,w,h,bufdc,picNum*w,h,SRCAND);  
  201.         BitBlt(mdc,dra[i].x,dra[i].y,w,h,bufdc,picNum*w,0,SRCPAINT);  
  202.     }  
  203.   
  204.     //將最後畫面顯示在窗口中  
  205.     BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);  
  206.   
  207.     tPre = GetTickCount();         //記錄此次繪圖時間  
  208.     picNum++;  
  209.   
  210.   
  211.     //下面這個for循環,決定每一隻恐龍下一次的移動方向及貼圖座標  
  212.     for(i=0;i<draNum;i++)  
  213.     {  
  214.         switch(rand()%4)          //隨機數除以4的餘數來決定下次移動方向,餘數0,1,2,3分別代表上,下,左,右  
  215.         {  
  216.             //case 0裏面的代碼,按照目前的移動方向來修正因爲各個方向圖案尺寸不一致而產生的貼圖座標誤差,加入恐龍每次移動的單位量(上,下,左,右每次20個單位)而得到下次新的貼圖座標  
  217.             case 0:                      //上  
  218.                 switch(dra[i].dir)  
  219.                 {  
  220.                     case 0:   
  221.                         dra[i].y -= 20;  
  222.                         break;  
  223.                     case 1:  
  224.                         dra[i].x += 2;  
  225.                         dra[i].y -= 31;  
  226.                         break;  
  227.                     case 2:   
  228.                         dra[i].x += 14;  
  229.                         dra[i].y -= 20;  
  230.                         break;  
  231.                     case 3:  
  232.                         dra[i].x += 14;  
  233.                         dra[i].y -= 20;  
  234.                         break;  
  235.                 }  
  236.                 //在計算出新的貼圖座標之後,還需判斷此新的座標會不會使得恐龍貼圖超出窗口邊界,若超出,則將該方向上的座標設定爲剛好等於臨界值  
  237.                 if(dra[i].y < 0)  
  238.                     dra[i].y = 0;  
  239.                 dra[i].dir = 0;  
  240.                 break;  
  241.             //其他方向按照和上面相同的方法計算  
  242.             case 1:                     //下  
  243.                 switch(dra[i].dir)  
  244.                 {  
  245.                     case 0:  
  246.                         dra[i].x -= 2;  
  247.                         dra[i].y += 31;  
  248.                         break;  
  249.                     case 1:  
  250.                         dra[i].y += 20;  
  251.                         break;  
  252.                     case 2:  
  253.                         dra[i].x += 15;  
  254.                         dra[i].y += 29;  
  255.                         break;  
  256.                     case 3:  
  257.                         dra[i].x += 15;  
  258.                         dra[i].y += 29;  
  259.                         break;  
  260.                 }  
  261.   
  262.                 if(dra[i].y > 370)  
  263.                     dra[i].y = 370;  
  264.                 dra[i].dir = 1;  
  265.                 break;  
  266.             case 2:                     //左  
  267.                 switch(dra[i].dir)  
  268.                 {  
  269.                     case 0:  
  270.                         dra[i].x -= 34;  
  271.                         break;  
  272.                     case 1:  
  273.                         dra[i].x -= 34;  
  274.                         dra[i].y -= 9;  
  275.                         break;  
  276.                     case 2:  
  277.                         dra[i].x -= 20;  
  278.                         break;  
  279.                     case 3:  
  280.                         dra[i].x -= 20;  
  281.                         break;  
  282.                 }  
  283.                 if(dra[i].x < 0)  
  284.                     dra[i].x = 0;  
  285.                 dra[i].dir = 2;  
  286.                 break;  
  287.             case 3:                     //右  
  288.                 switch(dra[i].dir)  
  289.                 {  
  290.                     case 0:  
  291.                         dra[i].x += 6;  
  292.                         break;  
  293.                     case 1:  
  294.                         dra[i].x += 6;  
  295.                         dra[i].y -= 10;  
  296.                         break;  
  297.                     case 2:  
  298.                         dra[i].x += 20;  
  299.                         break;  
  300.                     case 3:  
  301.                         dra[i].x += 20;  
  302.                         break;  
  303.                 }  
  304.                 if(dra[i].x > 535)  
  305.                     dra[i].x = 535;  
  306.                 dra[i].dir = 3;  
  307.                 break;  
  308.         }  
  309.     }  
  310. }  
  311.   
  312. //****消息處理函數***********************************  
  313. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
  314. {  
  315.     switch (message)  
  316.     {  
  317.         int i;  
  318.   
  319.         case WM_DESTROY:                    //窗口結束消息,撤銷各種DC    
  320.             DeleteDC(mdc);  
  321.             DeleteDC(bufdc);  
  322.             for(i=0;i<4;i++)  
  323.                 DeleteObject(draPic[i]);  
  324.             DeleteObject(bg);  
  325.             ReleaseDC(hWnd,hdc);  
  326.             PostQuitMessage(0);  
  327.             break;  
  328.         default:                            //其他消息  
  329.             return DefWindowProc(hWnd, message, wParam, lParam);  
  330.    }  
  331.    return 0;  
  332. }  


程序運行結果如下:





從圖中可以看出,由於貼圖前進行了排序操作,因此使得恐龍彼此之間沒有錯誤的遮掩。


我們也可以按自己的喜好,通過設定程序中最前面定義的draNum常數值來改變畫面上出現的恐龍數目。





筆記十一到這裏就結束了。


本節源代碼請點擊這裏下載:   【Visual C++】Code_Note_11


感謝一直支持【Visual C++】遊戲開發筆記系列專欄的朋友們,也請大家繼續關注我的博客,我一有空就會把自己的學習心得,覺得比較好的知識點寫出來和大家一起分享。

精通遊戲開發的路還很長很長,非常希望能和大家一起交流,共同學習和進步。

大家看過後覺得有啓發的話可以頂一下這篇文章,讓更多的朋友有機會看到它。也希望大家可以多留言來和我探討編程相關的問題。

最後,謝謝大家一直的支持~~~


The end



發佈了14 篇原創文章 · 獲贊 3 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章