[原]【Visual C++】游戏开发笔记之十一 基础动画显示(四) 排序贴图
本系列文章由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为存储背景图 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++】Code_Note_11
感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们,也请大家继续关注我的博客,我一有空就会把自己的学习心得,觉得比较好的知识点写出来和大家一起分享。
精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习和进步。
大家看过后觉得有启发的话可以顶一下这篇文章,让更多的朋友有机会看到它。也希望大家可以多留言来和我探讨编程相关的问题。最后,谢谢大家一直的支持~~~
The end