使用标准GDI实现游戏品质的动画系统.docx

上传人:b****6 文档编号:4967631 上传时间:2022-12-12 格式:DOCX 页数:18 大小:206.02KB
下载 相关 举报
使用标准GDI实现游戏品质的动画系统.docx_第1页
第1页 / 共18页
使用标准GDI实现游戏品质的动画系统.docx_第2页
第2页 / 共18页
使用标准GDI实现游戏品质的动画系统.docx_第3页
第3页 / 共18页
使用标准GDI实现游戏品质的动画系统.docx_第4页
第4页 / 共18页
使用标准GDI实现游戏品质的动画系统.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

使用标准GDI实现游戏品质的动画系统.docx

《使用标准GDI实现游戏品质的动画系统.docx》由会员分享,可在线阅读,更多相关《使用标准GDI实现游戏品质的动画系统.docx(18页珍藏版)》请在冰豆网上搜索。

使用标准GDI实现游戏品质的动画系统.docx

使用标准GDI实现游戏品质的动画系统

使用标准GDI实现游戏品质的动画系统

燕良2002年1月

前言2

GDI基础3

绘制一个位图(Bitmap)对象3

常用像素格式4

Windows下的基本动画系统4

动画驱动方式4

播放动画5

消除闪烁6

透明色(colorkey)处理7

Alpha混合9

读取JPEG,GIF文件10

子窗口管理12

进阶技巧--使用DIB14

像素操作14

RLE压缩15

参考15

华山论键15

其它类库16

前言

说到实现游戏品质的动画,很多人会立刻想到DirectX,没错DirectDraw很强大,但是并不是必须用DirectDraw才行。

动画后面的理论和技巧都是一样的,这和末端使用什么API没有太大关系(如果那API不是太~~慢的话)。

就笔者实现的NewImageLib的测试结果,内部所有像素数据的存储和运算都纯软件实现,最后一步输出到屏幕使用GDI的性能比DirectDraw低不到10%,在Window9X系统上要低20%左右,这对很多软件来说是绝对可以接受的。

现在应用程序界面越做越华丽,除了支持SKIN外,很多人都想在程序中加入一些例如sprite动画这种原本用在游戏上的技术,因为这原因引入DirectXAPI,显然是不值得的(况且DX版本升级频繁,DX8中已经用DirectGraphic取代了DirectDraw)。

本文将以笔者使用标准GDI函数实现的商业游戏为例,带你进入高品质2D动画编程领域,并且保证其设备无关性。

本文假设读者有C/C++语言知识,Windows编程基础,GDI基本概念。

下面我将主要讲述我在过去工作中积累的经验和一些技巧,但是将不讲解以上基本概念。

读者最好有MFC基础,本文给出的代码将主要使用MFC,但是其中的道理却不限于MFC。

GDI基础

绘制一个位图(Bitmap)对象

GDI的所有操作都是在DC(devicecontext)上进行的,所以首先你应该有DC的概念,如果你对DC还不了解,现在就去翻一翻Windows编程的书吧。

首先我们要Load一个Bitmap对象,使用Win32API可以写成这样:

//从资源Load一个位图,如果从文件load的话,可以使用:

:

LoadImage()

HBITMAPhbmp=:

:

LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MYBMP));

如果使用MFC可以这样写:

CBitmapbmp;

Bmp.LoadBitmap(IDB_MYBMP);

想把这个位图对象绘制到窗口上就要先得到窗口的DC,然后对这个DC操作。

请留意创建MemoryDC的代码,后面会用到。

Win32API的版本:

//假设位图大小为100*100像素

//假设hwnd是要绘制的窗口的HANDLE

HDChwnddc=:

:

GetDC(hwnd);

HDCmemdc=:

:

CreateCompatibleDC(hwnddc);

HBITMAPoldbmp=:

:

SelectObject(memdc,hbmp);

:

:

BitBlt(hwnddc,0,0,100,100,memdc,0,0,SRCCOPY);

if(oldbmp)

:

:

SelectObject(memdc,oldbmp);

DeleteDC(memdc);

:

:

ReleaseDC(hwnd,hwnddc);

MFC版本:

//假设是在一个CWnd派生类的成员函数中

CClientDCdc(this);

CDCmemdc;

memdc.CreateCompatibleDC(&dc);

CBitmap*oldbmp=memdc.SelectObject(&bmp);

dc.BitBlt(0,0,100,100,&memdc,0,0,SRCCOPY);

if(oldbmp)

memdc.SelectObject(oldbmp);

也可以这样:

CClientDCdc(this);

dc.DrawState(CPoint(0,0),CSize(100,100),&bmp,DST_BITMAP);

基本的代码就是这样,当然有更多的API可以用,这就要看你自己的了。

常用像素格式

要进行图像编程的化对像素格式不了解似乎说不过去。

我想应该有较多的人并不太了解,所以这里简要的介绍一下。

1.8bit

也叫做256色模式。

每个像素占一个字节,使用调色板。

调色板实际上是一个颜色表,简单的讲就是,我们有256个油漆桶(因为像素的取值范围是0到255),每个油漆桶里面漆的颜色都由红,绿,蓝(RGB)三中基本的油漆按不同比例配置而成。

所以我们指定一个像素的颜色的时候只需要指定它用的第几号桶就好了。

这种模式造就了DOS时代的神奇模式—13H(320*200*256色),因为320*200*1Byte正好是16bit指针寻址能力的范围。

这种模式有2的18次方种颜色(通过改变调色板实现),可以同时显示256中颜色。

这模式刚刚推出的时候,有人惊呼这是人类智慧的结晶呢!

也是这种模式造就了1992年WestWood的<<卡兰蒂亚传奇>>和1995年大宇资讯的<<仙剑奇侠传>>这样的经典游戏。

在Windows下硬件调色板应该极少用到,但是你可以用软件调色板来压缩你的动画,这也是在2D游戏中常用的技巧。

2.16bit

这也是笔者最喜欢的模式。

它不使用调色板。

每个像素占两个字节,存储RGB值。

我觉得这种像素格式的效果(同时显示颜色数)和存储量(也影响速度)取得了比较好的统一。

但是如果你是写应用程序的话,我劝你不要用它。

因为它的RGB值都不是整个BYTE,例如565模式(16bit的一种模式),它的RGB所占用的bit就是这样的:

RRRRRGGGGGGBBBBB

3.24bit

每个像素有三个BYTE,分别存储RGB值,这对你来说是不是很方便?

是不是太好了?

可惜对我们可怜的计算机却不是,因为CPU访问奇数的地址会很费劲,而且在硬件工艺上也有很多困难(具体我也不太清楚,请做过硬件的高手指点),所以你会发现你的显卡不支持这种模式,但是你可以在自己的软件中使用。

4.32bit

每个像素4个BYTE,分别存储RGBA,A值就是Alpha,也就是透明度,可以用像素混合算法实现多种效果,后面你就会看到。

Windows下的基本动画系统

动画驱动方式

先略说一下动画的基本原理,程序播放动画一般过程都是:

绘制—擦除—绘制,这样的重复过程,只要你重复的够快,至少每秒16次(被称作16FPS,FrameperSecond),我们可怜的眼睛就分辨不出单帧的图像了,看上去就是动画了。

在Windows环境下要驱动这样重复不停的操作有两种方法:

1.设置Timer

这很简单,只要设置一个足够短的Timer,然后响应WM_TIME(对应MFC中的OnTimer函数)就可以满足绝大部分应用程序的需要。

缺点是不够精确,而且Win2000和Win9x系统的精确性又有较大差异。

2.在消息循环中执行动画操作

这是在游戏中常用的方法,一般都会把WinMain中的消息循环写成这样:

while(TRUE)

{

//Lookformessages,ifnonearefoundthen

//updatethestateanddisplayit

if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))

{

if(0==GetMessage(&msg,NULL,0,0))

{

//WM_QUITwasposted,soexit

return(int)msg.wParam;

}

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else

{

if(g_bActive)//在主窗口不激活时不更新,以节省资源

{

//执行动画更新操作

}

//Makesurewegotosleepifwehavenothingelsetodo

elseWaitMessage();

}

}

如果你使用MFC,则需要重载CWinAPP的Run虚函数,把上述消息循环替换进去。

播放动画

现在我们有了一个适当的时机执行更新操作了,现在就让我们试试动画吧。

下面的代码将不再提供Win32的版本。

为了叙述方便,我需要一个播放动画的窗口,它必须是一个CWnd的派生类,假设这个类叫做CMyView,我们将在这个窗口中绘制动画。

首先我们为这个类添加一个成员函数”voidCMyView:

:

RenderView()”,你可以使用上面提到的方法调用这个函数。

现在准备工作都做好了,我们的动画该怎么存储呢?

别提动画GIF89a格式(如果你觉得只有GIF才有动画的话,那我劝你去做美术好了,别干程序了),如果你只想要个简单的动画播放当然可以,但是如果你想要做复杂点的,交互式动画,我劝你还是别用那东西。

假设我们有一个4帧的动画,怎么存储它呢?

我首先想到的就是存4个BMP文件,然后读入到一个CBitmap对象数组中,但是尊敬的大师ScottMeyers警告我们不要使用多态数组,因为编译器在某些情况下不能准确计算数组中对象的大小,所以下标运算符会产生可怕的效果。

然后我就想到了用CBitmap指针数组,这到是不错,不过管理起来稍嫌麻烦。

现在看看我最终的解决方法吧。

把一个帧序列安顺序拼接成一个文件,象这样:

然后用它创建一个CImageList对象,让我们仔细看一下创建的方法,使用

BOOLCImageList:

:

Create(intcx,intcy,UINTnFlags,intnInitial,intnGrow);

函数,前面两个参数用来指定我们一帧动画的尺寸。

这样就创建了一个空的ImageList,这样做的好处是可扩展行比较强。

下面我们需要把那个帧序列文件Load到一个CBitmap对象中,你可以存成JPG或者GIF文件来节省容量(后面将提到读取这些文件的简单方法,并且附一个实用类)。

当我们有了一个合适的CBitmap对象后,可以把他添加到我们的ImageList中,使用:

BOOLCImageList:

:

intAdd(CBitmap*pbmImage,COLORREFcrMask);

一个实例:

constintSPRIRT_WIDTH=32;

constintSPIRIT_HEIGHT=32;

….

m_myimglist.Create(SPIRIT_WIDTH,SPIRIT_HIGHT,ILC_COLOR24|ILC_MASK,1,1);

if(bmp.Load(“myani.bmp”))

m_myimglist.Add(&bmp,RGB(152,152,152));

好了,现在我们已经准备好了这些数据,让我们来实作渲染函数吧,下面这端代码可以循环播放上面的4帧动画,并且支持透明色(如果你不知道这个名字,稍后有讲解)哦!

voidCMyView:

:

RenderView()

{

CclientDCdc(this);

Staticintcurframe=0;

m_myimglist.Draw(&dc,curframe,Cpoint(0,0),ILD_TRANSPARENT);

curframe++;

If(curframe>m_myimglist.GetImageCount())

Curframe=0;

}

上面这个代码没有写擦除的操作,因为这根据具体需要有较大不同。

如果你只有一个精灵动画的话,你可以用一个Bitmap对象保存精灵所占矩形区域的图像。

你也可能需要有一个大的背景图每帧都要更新(这里我不讨论象dirtyrect这样的优化方法),所以你只要每次都画背景,然后画精灵就好了。

怎么样?

你已经实现了基本的动画系统,就是这么简单。

消除闪烁

如果你真正实现上面的代码的话,你会发现画面一闪一闪的,十分的不爽。

很多人都会怪到GDI头上,他们又会骂MS,说GDI太慢了。

其实非也(不是指MS不该骂,呵呵),任何直接写屏幕的操作都会产生闪烁,在DOS下直接写显存或者用DirectDrawAPI直接写PrimarySurface都会闪烁,因为你每个更新显示的操作都会被用户马上看到(因为垂直回扫的原因,或许会有延迟)。

消除闪烁最简单也是最经典的方法就是双缓冲(Doublebuffer)。

所谓的双缓冲其实道理非常简单,就是说我们在其它地方(简单的说就是不针对屏幕,不显示出来的地方)开辟一个存储空间,我们把所有的动画都要渲染到这个地方,而不是直接渲染到屏幕上(针对屏幕的存储区域)。

在GDI中,直接针对屏幕就是窗口DC,”不可见的地方”一般可以用MemoryDC。

在把所有动画渲染到后台缓冲之后,再一下次整体拷贝到屏幕缓冲区!

在纯软件2D图形引擎中,双缓冲一般意味着在内存中开辟一个区域用来存储像素数据。

而在DirectDraw中可以创建BackSurface,在把所有动画渲染到BackSuface上之后,然后使用Flip操作使其可见,Flip操作因为只是设置可见surface的地址,所以非常快速。

让我们重写一下voidCMyView:

:

RenderView()函数,来用GDI实现双缓冲:

voidCMyView:

:

RenderView()

{

CClientDCdc(this);

CRectrc;

GetClientRect(rc);

CDCmemdc;

memdc.CreateCompatibleDC(&dc);

CBitmapbmp;

Bmp.CreateCompatibleBitmap(&dc,rc.Width(),rc.Height());

CBitmap*oldbmp=memdc.SelectObject(&bmp);

Staticintcurframe=0;

m_myimglist.Draw(&memdc,curframe,Cpoint(0,0),ILD_TRANSPARENT);

curframe++;

If(curframe>m_myimglist.GetImageCount())

Curframe=0;

if(oldbmp)

memdc.SelectObject(oldbmp);

dc.BitBlt(0,0,rc.Width(),rc.Height(),&memdc,0,0,SRCCOPY);

}

其中创建一个Bitmap对象,然后选入MemoryDC是必须的,因为CreateCompatibleDC所创建的DC里面只含有一个1*1像素的单色Bitmap对象,所以如果缺了这个步骤,任何在MemoryDC上的绘图操作都会没有效果。

延伸出一个问题,CreateCompatibleBitmap函数的第一个参数显然不可写成&memdc,如果那样的化,你就创建了一个单色的位图,我想你肯定不希望这样。

重写后的函数看上去似乎多了很多无谓的操作,这是因为我们现在只有一个动画对象,如果我们有多个动画,而且还需要绘制动画的子窗口,那这样做的效果就会非常的好,不会有任何闪烁,而且向文章最后提到的图形MUD客户端,还能达到60FPS呢(在我家的赛阳433上)。

到此为止,我们的基本动画系统已经有了一个很好的基础了。

透明色(colorkey)处理

透明色就是指在绘制一张图片的时候,该颜色的像素不会被绘制上去,这通常用来做游戏的spirit动画,所以你可以看到各种形状不规则的人物动画。

但是他们的数据都是一个矩形的像素区域,只是绘制的时候有些像素不被画上去罢了。

GDI提供一个TransparentBlt()函数来支持ColorKey,你可以在MSDN中查到该函数的说明。

但是我的代码中使用这个函数后,在Win9X系统下产生了严重的资源泄漏,但是在Win2000下却没事,所以如果你也发现这问题的话,我建议你使用下面的代码,来把一个CBitmap透明的绘制到DC上。

假设你有一个CBitmap的派生类CMyBitmap:

BOOLCMyBitmap:

:

DrawTransparentInPoint(CDC*pdc,intx,inty,COLORREFmask/*要过滤掉的颜色值*/)

{

//Quickreturn

if(pdc->GetSafeHdc()==NULL)

returnFALSE;

if(m_hObject==NULL)

returnFALSE;

CRectDRect;

DRect=Rect();

DRect.OffsetRect(x,y);

if(!

pdc->RectVisible(&DRect))

returnFALSE;

COLORREFcrOldBack=pdc->SetBkColor(RGB(255,255,255));

COLORREFcrOldText=pdc->SetTextColor(RGB(0,0,0));

CDCdcimg,dctrans;

if(dcimg.CreateCompatibleDC(pdc)!

=TRUE)

returnFALSE;

if(dctrans.CreateCompatibleDC(pdc)!

=TRUE)

returnFALSE;

CBitmap*oldbmpimg=dcimg.SelectObject(this);

CBitmapbmptrans;

if(bmptrans.CreateBitmap(Width(),Height(),1,1,NULL)!

=TRUE)

returnFALSE;

CBitmap*oldbmptrans=dctrans.SelectObject(&bmptrans);

dcimg.SetBkColor(mask);

dctrans.BitBlt(0,0,Width(),Height(),&dcimg,0,0,SRCCOPY);

pdc->BitBlt(x,y,Width(),Height(),&dcimg,0,0,SRCINVERT);

pdc->BitBlt(x,y,Width(),Height(),&dctrans,0,0,SRCAND);

pdc->BitBlt(x,y,Width(),Height(),&dcimg,0,0,SRCINVERT);

if(oldbmpimg)

dcimg.SelectObject(oldbmpimg);

if(oldbmptrans)

dctrans.SelectObject(oldbmptrans);

pdc->SetBkColor(crOldBack);

pdc->SetTextColor(crOldText);

returnTRUE;

}

Alpha混合

Alpha混合是一种像素混合的方法。

所谓的像素混合就是使用一定的算法把两个像素的值混合成一个新的像素值(倒,和没说一样),通常我们都把两个像素的值,分别叫做源(src)和目的(dst),然后把混合后的结果存入dst中:

dst=srcblenddst

如果源像素和目的像素都是RGBA格式,你可以使用每个像素的Alpha信息(或者叫做Alpha通道)组合出各种运算公式,例如

dst=src*src.alpha+dst*dst.alpha;

或者

dst=src*src.alpha+dst*(1-src.alpha)//这里我们假设alpha值是0~1的浮点数。

可惜标准GDI没有支持类似这种操作的函数(起码我没找到),它只支持另一种Alpha混合,我把它叫做constalphablend,也就是把两幅都不包含Alpha通道的图像的按照一个固定的Alpha值混合到一起,也就是每个像素都使用同一Alpha值。

GDI的支持这个操作的函数是:

AlphaBlend(

HDChdcDest,

intnXOriginDest,

intnYOriginDest,

intnWidthDest,

inthHeightDest,

HDChdcSrc,

intnXOriginSrc,

intnYOriginSrc,

intnWidthSrc,

intnHeightSrc,

BLENDFUNCTIONblendFunction

);

这个API的参数个数略多了一些,但是我想其中的位置参数你可以轻松搞定,还有就是源DC和目的DC,当然了,我们的GDI只能对DC操作,而不是对我们的像素数据,而我们只要把我的位图select到DC中就OK了,最后一个参数是一个结构,是用来指定Alpha的运算方式的,请看一个实际的例子:

BLENDFUNCTIONbf;

bf.AlphaFormat=0;

bf.BlendFlags=0;

bf.BlendOp=AC_SRC_OVER;

bf.SourceConstantAlpha=100;//指明透明度,取值范围是0~255

AlphaBlend(pdc->GetSafeHdc(),rc.left,rc.top,rc.Width(),rc.Height(),

memdc.GetSafeHdc(),0,0,rc.Width(),rc.Height(),bf);

也许你看过很多游戏,在弹出文字对话框的时候都是在游戏画面上蒙一层半透明的黑色,然后在这上面印字。

使用上述操作就可以达到此效果。

你可以先建立一个MemoryDC,然后把他填充为黑,然后把Alpha值设为128,然后混合到你要绘制的DC上(不一定是窗口DC哦,记得我们前面将的双缓冲吗?

)就OK了。

读取JPEG,GIF文件

JPEG压缩算法综合的信号学和视觉心理学,而GIF格式,特别是支持动画的GIF89a格式为了节约容量也做了很多种非常变态的优化,所以要写一个完全支持这些标准格式的解码器相当困难,也没有必要。

如果你需要进行JPEG文件的读写我推荐你使用IntelJpegLib,速度相当令人满意。

而GIF由于授权问题,没有任何官方组织提供的读写代码。

如果你只是需要读入JPEG和静态GIF(或者只一帧的动态GIF),我推荐你使用Windows提供的OleLoadPicture函数,下面这段代码可以把一个JPG,GIF,BMP读入到Bitmap对象中:

BOOLCIJLBitmap:

:

Load(LPCTSTRlpszPathName)

{

BOOLbSuccess=FALSE;

//Freeupanyresourcewemaycurrentlyhave

DeleteObject();

//openthefile

CFilef;

if(!

f.Open(lpszPathName,CFile:

:

modeRead))

{

TRACE(_T("Failedtoopenfi

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 军事

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1