《MFC游戏开发》笔记三 游戏贴图与透明特效的实现

本系列文章由七十一雾央编写,转载请注明出处。

http://blog.csdn.net/u011371356/article/details/9313239

作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5&mod=personinfo


   

       对于一个游戏来说,画面的华丽程度在很大程度上决定了它的火热程度,记得以前初中时候我在网上找游戏玩时,首先看的就是画面是不是好看,技能是不是酷炫,呵呵。而精美游戏的实现就是通过贴图来实现啦,因此要想做出一个好游戏,光有Coder是不够的,必须要有给力的美工,当然还要有好的策划,好的数值设定什么的。不过大家自己学做游戏也不用担心素材的问题了,网上有很多,大家如果不是做商业游戏,用别人的是没什么问题的。


       在这一节笔记里,我会讲解使用CBitmap和CImage两种贴图方式,并且会讲解一下透明贴图的实现。我先解释一下,所谓透明贴图,是指贴图只贴前景图,背景和大背景融合,比如大家贴了一张地图后,要在上面再贴一个小人,如果不采用透明贴图的话,那么小人图的背景也会被贴上。


一、一些基本的知识


       创建一个窗口之后,显示的屏幕上便划分出三个区域,即屏幕区(Screen),窗口区(Window)与内部窗口区(Client)DeviceContext(设备内容)一般简称为DC,简单来说,DC就是程序可以进行绘图的地方。


       若要取得窗口的DC,可以调用下面这个函数:

HDC GetDC(); //取得DC

       

       若使用GetDC()函数取得窗口DC后,必须使用ReleaseDC()函数将DC释放。

Int ReleaseDC(HDC 要释放的DC名称);//释放DC,若运行成功,返回整数1,若失败返回0

       

       我简单解释一下需要释放DC的原因:

       每个进程的GDI句柄数是由上限的(在注册表中配置,一般值为10000)。如果不关闭,就会有句柄泄露(这些句柄会在进程结束时被关闭)。大家在编程要养成良好的习惯,就如同new的空间用完一定要delete一样,虽然可能表面上不会影响你的程序的运行,但是埋下了隐患。

       

       在我们建立的MFC单文档程序中,大家在调整好窗口后,剩下的文件大家就只需要关注CChildView.h和CChildView.cpp这两个文件了,这个视图类会展示出程序的画面。

       

       大家需要定义变量或者属性的时候可以在CChildView.h里面进行,就作为这个类的成员变量就好了。这些变量如果需要初始化,请放在CChildView.cpp中的BOOL CChildView::PreCreateWindow(CREATESTRUCT&cs) 函数里进行。在后面的教程中,如果未特别说明,就是按照这个规则来的。

       

       在CChildView.h中有个函数void CChildView::OnPaint() ,这个就是绘图函数啦,我们需要绘制在屏幕上的所有东西就丢给它就好了。当产生WM_PAINT绘图消息时(比如别的窗口从我们的游戏窗口经过了等,就需要重新绘制一遍画面,windows就会产生一个WM_PAINT消息加入到程序的消息队列中去),这个OnPaint()函数就会被调用一遍。但是大家知道,游戏为了保持一个高的帧数(FPS),是需要不断的重绘画面的,因此我们就需要使得这个OnPaint()函数不断的被执行,这个可以采用定时器来做到,在后面我会进行讲解。

       

       因此,在程序中绘图部分,我们首先要知道的是画图的地方,即获得DC,通过GetDC这个API可以实现,接着需要做的就是获得画图地方的大小,就是内部窗口区(Client),这个微软为我们提供了GetClientRect这个API,然后我们就可以画图啦,当然在最后需要释放DC哦。

       

       讲了这么多,我们开始动手写代码吧。

       大家在CChildView.h中加入变量定义

CRect m_client;

       我解释一下,CRect 是一个矩形类,它由四个成员变量:top,bottom,left,right,可以用来记录一个矩形的左上角和右下角的坐标,一般我们都是用它来保存一个矩形物体的大小的。

       

       接下来,在CChildView.cpp中的void CChildView::OnPaint()函数中写入下面的代码就好了

CDC *cDC=this->GetDC();   //获得当前窗口的DC   
GetClientRect(&m_client);   //获得窗口的尺寸
//加入我们要绘制的代码。。。。。。
ReleaseDC(cDC);           //释放DC   

       到现在绘图的准备工作都做完了,接下来我们就开始绘图了。


二、使用CBitmap类进行绘图

    

       大家看名字就可以知道,这个类是用来绘制位图的,即以“.bmp”为后缀的图片。一般游戏之中,需要使用的图片比较多,都会将图片先存为文件,然后从文件中读取,而从文件中读取图片的步骤有以下几步:

       1.建立一个与窗口DC兼容的内存DC

       我们加载的图片是需要先放在一个内存中的DC里,然后把需要显示的图片从内存DC中绘到窗口DC中即可,我们可以认为内存DC就是一个个备胎,每个DC都是一个屌丝男士,窗口DC就是女神啦,女神需要谁去帮忙做事的时候,那个屌丝男士就和女神在一起了,当然是暂时的,呵呵。

        我们首先定义一个内存DC对象;        

CDC m_bgcDC;

        然后就开始创建一个DC了;       

m_bgcDC.CreateCompatibleDC(NULL);

        看名字我们就可以知道它的意思了,创建一个兼容的DC。它的参数是CDC*类型的,即一个DC的指针,函数会创建和参数DC兼容的DC。如果我们将参数填为NULL,函数会自动创建和当前程序DC兼容的内存DC,所以我们可以直接设置为NULL。

        

       2.加载资源中的位图

       首先将位图添加进资源中,方法是右键工程名->添加->资源,选择Bitmap,点击导入,然后找到你的位图即可。位图添加进来后会有一个ID,大家在资源视图下的Bitmap那一栏下可以看到,位图的ID可以修改,默认是第一章是IDB_BITMAP1,事实上它是一个整型数字。大家可以将其编号为连续的数字,方便在循环中加载位图,和使用它们来显示动画效果。

       接下来,就是代码部分了,我们先定义一个位图对象;

CBitmap m_bgBitmap;

       然后就可以从资源中加载了  

m_bgBitmap.LoadBitmap(IDB_BITMAP1);

      

       3.选用位图对象

       加载完毕后,我们需要做的就是将这张图和这个DC关联起来,可以说是将这个图放到内存DC中去,这个就是设置备胎的属性了。  

m_bgcDC.SelectObject(&m_bgBitmap);

       参数为要选择的位图对象指针。

      

       4.将内存DC的内容粘贴到窗口DC中,绘制出来

      由于我们的窗口DC使用的是指针,因此使用方式为

cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_bgcDC,0,0,SRCCOPY);

      BitBlt这个函数的原型如下:

BOOL BitBlt(          
	int nXDest,       //目的DC的起始x
	int nYDest,       //起始y
	int nWidth,       //宽度
	int nHeight,      //高度
	HDC hdcSrc,       //源DC
	int nXSrc,        //源DC起始x
	int nYSrc,        //起始y
	DWORD dwRop       //贴图方式,大家设置为SRCCOPY就可以了
);

         经过这四步后,我们已经可以显示出图片了     

     赶紧运行一下程序,大家看到图片了吗?   

   

三、使用CImage类进行贴图         

   

       CImage是MFC中一种图片处理的类,其头文件为atlimage.h,如果是VS2010或者VS2012,就不需要包含头文件了。它对PNG格式的图片支持很好,对于其他格式的图片需要借助CBitmap类实现,由于比较麻烦且使用价值感觉不大,所以就略去了,我主要讲解一下对于PNG格式的处理方法。     

       CImage的使用非常的简单,它的使用分为三步:       

       1.定义一个CImage对象

CImage   m_hero;

       

       2.加载图片

m_hero.Load("hero_No.png");
       Load函数的参数是图片的路径,这里使用的是相对路径

       

      3.绘制图片

m_hero.Draw(*cDC,100,400,60,60);
        Draw函数有多个重载类型,这里给出的是最常用的,参数分别表示要绘制的DC,起始x,起始y,宽度,高度。
 
        简单的几步过后,我们看下效果:
      
        PS:大家在尝试的时候会发现窗口在不断的闪动,这个问题大家可以先忽略,在后面我会讲解解决方法,这个要采用图像缓冲来处理。  
 
四、透明特效
       
       上面的那张截图,大家可以看到,绘制的人物的背景白色也贴出来了,这显示是我们不想要的,要去掉白色,主要有两种方法。

       

       1.如果采用的不是CImage类贴图,那么可以采用TransparentBlt函数来代替BitBlt函数进行贴图,TransparentBlt原型如下:

BOOL TransparentBlt(
        int nXOriginDest,   //前面的参数是和BitBlt是一样的含义,就不多说了,最后一个参数和用法见下面
        int nYOriginDest,
        int nWidthDest,
        int hHeightDest, 
        HDC hdcSrc, 
        int nXOriginSrc, 
        int nYOriginSrc,
        int nWidthSrc,
        int nHeightSrc, 
        UINT crTransparent
);

       它的参数最后一个表示要透明的颜色,比如对于上图的人物图片来说,我们不希望白色被显示出来,那么我们就可以设置最后一个参数为RGB(255,255,255),RGB是一个宏,其三个参数分别表示红色,绿色,蓝色三原色的分量,范围在0~255。
       采用这种方式进行透明贴图有一点需要注意,就是人物中不能含有要去掉的背景色,否则也会被去掉,因此在制作图片的时候需要注意背景色必须为纯色,且前景中不含有该颜色。

       

       2.采用CImage类贴图

       这个要进行透明贴图就简单多了,只需要将要贴的图片中不需要的部分弄成透明就可以了,贴出来就是透明的了。
       对于有些图片中含有一些特定颜色的像素,有时候仍然不是透明的,这个是CImage实现的问题。大家可以采用的方法是每张图片加载后,都自己对它处理以下,我为大家封装了一个函数:
void TransparentPNG(CImage *png)
{
    for(int i = 0; i <png->GetWidth(); i++)
    {
        for(int j = 0; j <png->GetHeight(); j++)
        {
            unsigned char* pucColor = reinterpret_cast<unsigned char*>(png->GetPixelAddress(i , j));
            pucColor[0] = pucColor[0] *pucColor[3] / 255;
            pucColor[1] = pucColor[1] *pucColor[3] / 255;
            pucColor[2] = pucColor[2] *pucColor[3] / 255;
        }
    }
}

     大家加载图片后,将CImage对象地址传递给它就好了,至于原理大家可以参考透明实现相关的知识。
     就像下面这样
TransparentPNG(&m_hero);
     透明后就是这样的了:
      
五、两种贴图方式的对比
    
       采用CBitmap类进行贴图时,操作比较麻烦,而且BMP图片占空间还比较大;使用CImage图片操作简单,而且PNG格式小巧,占用空间小。另外,还有一点很重要的区别,使用CBitmap类时,图片被打包进了可执行程序中去,导致可执行程序比较大,但是exe程序可以到处运行,而不需要那些资源图片在旁边。使用CImage类时,PNG图片是从文件中读取的,所以要运行程序,PNG图片必须在exe程序旁边,当然,exe程序也就很小了。
       由于CImage比较简单,在以后的教程中,我将都采用CImage贴图。
 

       如果您正在跟着本教程学习,建议自己打一遍代码,多尝试,遇到困难自己想办法去解决,这样会有很大的提高。有需要代码的同学

       请点击这里下载本节源代码



    

      《MFC游戏开发》笔记三到这里就结束了,更多精彩请关注下一篇。如果您觉得文章对您有帮助的话,请留下您的评论,点个赞,能看到你们的留言是我最高兴的事情,因为这让我知道我正在帮助曾和我一样迷茫的少年,你们的支持就是我继续写下去的动力,愿我们一起学习,共同努力,复兴国产游戏。

         对于文章的疏漏或错误,欢迎大家的指出。


原文地址:https://www.cnblogs.com/java20130723/p/3211352.html