【Visual C++】游戏开发笔记之十一 基础动画显示(四) 排序贴图

标签: 游戏c++nullimagecallback
46445人阅读 评论(109) 收藏 举报
分类:

本系列文章由zhmxy555编写,转载请注明出处。 

http://blog.csdn.net/zhmxy555/article/details/7385605

作者:毛星云    邮箱: happylifemxy@163.com  




------------------------------------------------------------------------------------------------------------------------------

浅墨历时一年为游戏编程爱好者锻造的著作《逐梦旅程: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++】Code_Note_11


感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们,也请大家继续关注我的博客,我一有空就会把自己的学习心得,觉得比较好的知识点写出来和大家一起分享。

精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习和进步。

大家看过后觉得有启发的话可以顶一下这篇文章,让更多的朋友有机会看到它。也希望大家可以多留言来和我探讨编程相关的问题。

最后,谢谢大家一直的支持~~~


The end






------------------------------------------------------------------------------------------------------------------------------

浅墨历时一年为游戏编程爱好者锻造的著作《逐梦旅程:Windows游戏编程之从零开始》

如果你喜欢浅墨写的【Visual C++】游戏开发系列博客文章,那么你一定会爱上这本书。

这是浅墨专门为热爱游戏编程的朋友们写的入门级游戏编程宝典。



------------------------------------------------------------------------------------------------------------------------------

57
1

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:5626567次
    • 积分:39311
    • 等级:
    • 排名:第102名
    • 原创:138篇
    • 转载:25篇
    • 译文:8篇
    • 评论:9178条
    【浅墨的第二本著作】
    【关于浅墨】
    • ■ 毛星云,网络ID“浅墨,90后,热爱游戏开发、游戏引擎、计算机图形学、图像处理等技术,就职于腾讯互娱。
    • ■ 微软最有价值专家
    • ■ 著作《逐梦旅程:Windows游戏编程之从零开始》《OpenCV3编程入门》
    • ■ 中国2013年度十大杰出IT博客作者
    • ■ CSDN 2012年博客大赛年度博客之星
    • ■ CSDN 2012年度十大风云专栏作者
    • ■ 常活跃于知乎豆瓣等网络社区
    • ■ 本科毕业于南京航空航天大学中国乌克兰航天联合培养班,获乌克兰国立航空航天大学与南京航空航天大学双学位
    • ■硕士就读于南京航空航天大学航天学院(2013级硕士研究生),已于2016年三月毕业
    • ■ 邮箱: happylifemxy#163.com(#换成@)
    • PS:平时精力有限,大家的邮件不一定都能回复,请见谅。
    【浅墨的第一本著作】
    【浅墨的微博】
    博客专栏
    最新评论