[摘要]燕良 2002年1月http://www.diamondgarden.net/ 前言 2GDI基础 3绘制一个位图(BITMAP)对象 3常用像素格式 4WINDOWS下的基本动画系统 4动画驱动方...
燕良 2002年1月
http://www.diamondgarden.net/
前言 2
GDI基础 3
绘制一个位图(BITMAP)对象 3
常用像素格式 4
WINDOWS下的基本动画系统 4
动画驱动方式 4
播放动画 5
消除闪烁 6
透明色(COLOR KEY)处理 7
ALPHA混合 9
读取JPEG,GIF文件 10
子窗口管理 12
进阶技巧--使用DIB 14
像素操作 14
RLE压缩 15
参考 15
华山论键 15
其它类库 16
前言
说到实现游戏品质的动画,很多人会立刻想到DirectX,没错DirectDraw很强大,但是并不是必须用DirectDraw才行。动画后面的理论和技巧都是一样的,这和末端使用什么API没有太大关系(如果那API不是太~~慢的话)。就笔者实现的NewImage Lib的测试结果,内部所有像素数据的存储和运算都纯软件实现,最后一步输出到屏幕使用GDI的性能比DirectDraw低不到10%,在Window9X系统上要低20%左右,这对很多软件来说是绝对可以接受的。
现在应用程序界面越做越华丽,除了支持SKIN外,很多人都想在程序中加入一些例如sprite动画这种原本用在游戏上的技术,因为这原因引入DirectX API,显然是不值得的(况且DX版本升级频繁,DX8中已经用DirectGraphic取代了DirectDraw)。本文将以笔者使用标准GDI函数实现的商业游戏为例,带你进入高品质2D动画编程领域,并且保证其设备无关性。
本文假设读者有C/C++语言知识,Windows编程基础,GDI基本概念。下面我将主要讲述我在过去工作中积累的经验和一些技巧,但是将不讲解以上基本概念。读者最好有MFC基础,本文给出的代码将主要使用MFC,但是其中的道理却不限于MFC。
GDI基础
绘制一个位图(Bitmap)对象
GDI的所有操作都是在DC(device context)上进行的,所以首先你应该有DC的概念,如果你对DC还不了解,现在就去翻一翻Windows编程的书吧。
首先我们要Load一个Bitmap对象,使用Win32 API可以写成这样:
file://从资源Load一个位图,如果从文件load的话,可以使用::LoadImage()
HBITMAP hbmp=::LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MYBMP));
如果使用MFC可以这样写:
CBitmap bmp;
Bmp.LoadBitmap(IDB_MYBMP);
想把这个位图对象绘制到窗口上就要先得到窗口的DC,然后对这个DC操作。请留意创建MemoryDC的代码,后面会用到。
Win32 API的版本:
file://假设位图大小为100*100像素
file://假设hwnd是要绘制的窗口的HANDLE
HDC hwnddc=::GetDC(hwnd);
HDC memdc=::CreateCompatibleDC(hwnddc);
HBITMAP oldbmp=::SelectObject(memdc,hbmp);
::BitBlt(hwnddc,0,0,100,100,memdc,0,0,SRCCOPY);
if(oldbmp)
::SelectObject(memdc,oldbmp);
DeleteDC(memdc);
::ReleaseDC(hwnd,hwnddc);
MFC版本:
file://假设是在一个CWnd派生类的成员函数中
CClientDC dc(this);
CDC memdc;
memdc.CreateCompatibleDC(&dc);
CBitmap *oldbmp=memdc.SelectObject(&bmp);
dc.BitBlt(0,0,100,100,&memdc,0,0,SRCCOPY);
if(oldbmp)
memdc.SelectObject(oldbmp);
也可以这样:
CClientDC dc(this);
dc.DrawState(CPoint(0,0),CSize(100,100),&bmp,DST_BITMAP);
基本的代码就是这样,当然有更多的API可以用,这就要看你自己的了。J
常用像素格式
要进行图像编程的化对像素格式不了解似乎说不过去。我想应该有较多的人并不太了解,所以这里简要的介绍一下。
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就是这样的:
RRRR RGGG GGGB BBBB
3. 24bit
每个像素有三个BYTE,分别存储RGB值,这对你来说是不是很方便?是不是太好了?可惜对我们可怜的计算机却不是,因为CPU访问奇数的地址会很费劲,而且在硬件工艺上也有很多困难(具体我也不太清楚,请做过硬件的高手指点),所以你会发现你的显卡不支持这种模式,但是你可以在自己的软件中使用。
4. 32bit
每个像素4个BYTE,分别存储RGBA,A值就是Alpha,也就是透明度,可以用像素混合算法实现多种效果,后面你就会看到。
Windows下的基本动画系统
动画驱动方式
先略说一下动画的基本原理,程序播放动画一般过程都是: 绘制—擦除—绘制,这样的重复过程,只要你重复的够快,至少每秒16次(被称作16FPS,Frame per Second),我们可怜的眼睛就分辨不出单帧的图像了,看上去就是动画了。
在Windows环境下要驱动这样重复不停的操作有两种方法:
1. 设置Timer
这很简单,只要设置一个足够短的Timer,然后响应WM_TIME(对应MFC中的OnTimer函数)就可以满足绝大部分应用程序的需要。缺点是不够精确,而且Win2000和Win9x系统的精确性又有较大差异。
2. 在消息循环中执行动画操作
这是在游戏中常用的方法,一般都会把WinMain中的消息循环写成这样:
while( TRUE )
{
// Look for messages, if none are found then
// update the state and display it
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
if( 0 == GetMessage(&msg, NULL, 0, 0 ) )
{
// WM_QUIT was posted, so exit
return (int)msg.wParam;
}
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
if( g_bActive )//在主窗口不激活时不更新,以节省资源
{
file://执行动画更新操作
}
// Make sure we go to sleep if we have nothing else to do
else WaitMessage();
}
}
如果你使用MFC,则需要重载CWinAPP的Run虚函数,把上述消息循环替换进去。
播放动画
现在我们有了一个适当的时机执行更新操作了,现在就让我们试试动画吧。下面的代码将不再提供Win32的版本。
为了叙述方便,我需要一个播放动画的窗口,它必须是一个CWnd的派生类,假设这个类叫做CMyView,我们将在这个窗口中绘制动画。首先我们为这个类添加一个成员函数”void CMyView::RenderView()”,你可以使用上面提到的方法调用这个函数。
现在准备工作都做好了,我们的动画该怎么存储呢?别提动画GIF89a格式(如果你觉得只有GIF才有动画的话,那我劝你去做美术好了,别干程序了),如果你只想要个简单的动画播放当然可以,但是如果你想要做复杂点的,交互式动画,我劝你还是别用那东西。假设我们有一个4帧的动画,怎么存储它呢?我首先想到的就是存4个BMP文件,然后读入到一个CBitmap对象数组中,但是尊敬的大师Scott Meyers警告我们不要使用多态数组,因为编译器在某些情况下不能准确计算数组中对象的大小,所以下标运算符会产生可怕的效果。然后我就想到了用CBitmap指针数组,这到是不错,不过管理起来稍嫌麻烦。现在看看我最终的解决方法吧。把一个帧序列安顺序拼接成一个文件,象这样:
然后用它创建一个CImageList对象,让我们仔细看一下创建的方法,使用
BOOL CImageList::Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow );
函数,前面两个参数用来指定我们一帧动画的尺寸。这样就创建了一个空的ImageList,这样做的好处是可扩展行比较强。下面我们需要把那个帧序列文件Load到一个CBitmap对象中,你可以存成JPG或者GIF文件来节省容量(后面将提到读取这些文件的简单方法,并且附一个实用类)。当我们有了一个合适的CBitmap对象后,可以把他添加到我们的ImageList中,使用:
BOOL CImageList::int Add( CBitmap* pbmImage, COLORREF crMask );
一个实例:
const int SPRIRT_WIDTH=32;
const int SPIRIT_HEIGHT=32;
….
m_myimglist.Create(SPIRIT_WIDTH,SPIRIT_HIGHT,ILC_COLOR24
关键词:运用规范GDI完成游戏品质的动画系统