DirectDraw 游戏编程基础(3)
游戏使计算机的发展超越了晶体管时代
例一的扩展(DDEX2和DDEX3)
DDEX1包含了一个最基本的DirectDraw的实现方法。它生成了DirectDraw和DirectDrawSurface对象,同时也生成了一个主表面(Surface)和与之相关的后台缓冲区,并在后台缓冲区打印文本,并可以在表面(Surface)之间进行切换。
在DirectX 3 SDK(DDEX2)中的第二个DirectDraw 例程扩展了关于DDEX1应用程序。DDEX2包括将一个位图文件载入到后台缓冲区的函数。
第三个DirectDraw 例程将这一函数进一步地扩展了。除了主表面(Surface)和后台缓冲区之外,DDEX3还生成了两个隐屏表面(Surface),并且在每一个隐屏表面(Surface)之中都载入了一个位图文件。然后,DDEX3使用IDirectDrawSurface::BltFast方法,将一个隐屏的内容复制到后台缓冲区中。之后,弹出这些缓冲区,并且将下一个隐屏表面(Surface)的内容复制到后台缓冲区。
以下的部分将更详细地检查这一新的函数。
在一个表面(Surface)上载入一个位图(Bitmap)
就与DDEX1一样,dolnit是DDEX2应用程序的初始化函数。虽然,在DDEX2中,DirectDraw的初始化方式表面上与在 DDEX1中的DirectDraw的初始化方式不太一样,但它们的实质是一样的。这一过程如下列的程序代码所示:
LPddPal = DDLoadPalette(LpDD, szBackground); if (LpddPal == NULL) goto error; ddrval = LpDDSprimary->SetPalette(LpDDPal); if( ddral != DD_OK ) goto error; // Load a bitmap into the back buffer。 Ddrval = DDReLoadBitmap(LpDDSBack, szBackground); if( ddrval != DD_OK ) goto error;
生成调色板
这个程序代码的第一行是:从DDLoadPalette函数返回一个值。如果你想知道在哪能找到DDLoadPalette,你可以在\DXSDK|SAMPLES|MISC目录中的Ddutil.cpp文件中找到它。你会发现,在DirectX 3 SDk的大部分DirectDraw例程中都使用了Ddutil.cpp文件。最为关键的是:该文件上包括了能从文件中或是从资源中载入位图和调色板的函数。这些函数的代码并非一遍遍地重复出现在例程文件中,而且被放置在能被重复使用的同一文件之中。
注意:如果你正在使用MS Developer Studio(微软开发工作室)编辑DDEX2和用DirectX 3 SDK提供的其它工具,你必须把Ddutil.cpp文件插入到DDEXx文件工作区的文件表中。重申一遍:在工作区中必须包括Ddutil.cpp:
1. 在插入(insert)菜单上,单击Files进入Projeects。
2. 单击Browse.
3. 单击DXSDK\SDK\SAMPLES\MISC\目录。
4. 单击Ddutil.cpp
5. 单击ADD
对于DDEX2来说,从Back.bmp文件中,DDLoadPalette创建了一个DirectDraew对象。DDLoadPalette函数实际上是来检查用以产生调色板的一个文件或资源是否存在。如果不存在的话,该函数就创建一个缺省的调色板,对于DDEX2 来说,DDLoadPalette函数从文件中提取调色板信息,并通过ape指针将其存储在一个指定的结构中,然后它生成DirectDrawPalette 对象。如下面的代码所示:
pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL); return ddpal;
当IDirectDraw::Createpalette方法返回后,ddpal参数将指向DirectDrawPalette对象,其中,对象DirectDrawPalette是从DDLoadPalettede的调用返回的。
Ape参数是一个指针,它可以包括2,4,16或256个入口,呈直线分布。这些入口的数目由IDirctDraw::CreatePalette参数决定。在这种情况下,dwFLags参数被设置为DDPCAPS_8BIt,它表示:在这个结构中有256个入口。每个入口包括4位(一位红通道,一位绿通道,一位兰通道和一个标志位)。
设置调色板
在生成调色板之后,你要通过调用IDirectDrawSurface::SetPalette方法,将DirectDrawPalette对象的指针转到主表面(Surface)上,如下列代码所示:
ddrval = LpDDSPrimary->SetPalette(LpDDPal); if( ddrval != DD_Ok ) // SetPalette failed
一旦你已经调用了IdirectDrawSurface::SetPalette,DirectDrawPalette对象就被嵌入到DirectDrawSurface对象中了。不论何时你需要改变这一调色板,你要作的就是生成一个新的调色板并重新设置该调色板。(这就如例程中所做的一样。然而,也有其它的改变调色板的方式。我们可以在其它例程中看到)。
在后台缓冲区中载入一个位图文件
一旦DirectDrawPalette对象被嵌入到DirectDrawSurface对象之中,DDEX2就将Back.bmp bitmap载入到后台缓冲区中。使用下例的程序代码可实现该过程:
// Load a bitmap into the back buffer. Ddrval = DDReLoadBitmap(LpDDSBack, szBackground); if( ddrval != DD_Ok ) // Load failed
DDReLoadBitmap是出现在Ddutil.cpp中的另一个函数。它从一个文件或资源中将一个位图文件载入到一个已经存在的DirectDraw表面(Surface)之中。(就象在DDEX5中那样,你可以使用DDLoadBitmap创造一个表面(Surface)并且将位图载入那个表面(Surface))。对于DDEX2来说,DDReLoadBitmap把szBackground指向的Back.bmp载入到ipDDSBack指向的后台缓冲区,DDReLoadBitmap调用DDCopyBitmap函数,将文件复制到后台缓冲区中,并且将缓冲区扩展到适当的。
DDCopyBitmap函数将位图复制到内存之中,然而利用GetObject函数得到位图的大小。DDCopyBitmap然后使用下列的代码得到后台缓冲区的大小(它可以放置位图):
// // get size of surface // ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_HEIGHT DDSD_WIDTH; pdds->GetSurfaceDesc(&ddsd);
ddsd是指向DDSRFACEDESC结构的一个指针。该结构存储了DirectDraw表面(Surface)的当前描述。在这种情况下,我们需要注意的是:DDSURFACEDESC的成员描述这个表面(Surface)的高度和宽度,分别表示为:DDSD_HEIGHT和 DDSD_WIDTH。调用IDirectDrawSurface::GetSurfaceDesc方法,把适当的值来载入到这个结构。对于DDEX2来说,这些值将是:高480和宽640。
DDCopyBitmap函数锁定表面(Surface)并将位图文件复制到后台缓冲区,使用StretchBit函数延伸或压缩后台缓冲区到可适用的大小。表示如下:
if ((hr = pdds->GetDC(&hdc)) == DD_OK) { StretchBlt(hdc, 0,0,ddsd.dwWidth, ddsd.dwHeight, hdcImage,x, y, dx, dy, SRCCOPY); pdds->ReleaseDC(hdc); }
弹出表面(Surface)
在DDEX2例程中的弹出表面(Surface)操作本质上与在DDEX1例程中的弹出操作是同样的过程。但是在表面(Surface)丢失的情况下,你必须通过调用DDReLoadBitmap函数,在表面(Surface)恢复之后,,将bitmap再次载入到后台缓冲区中。
从一个隐屏表面(Surface)按位隔行拷贝
DDEX2是在后台缓冲区中取出和放入位图的,然后在后台缓冲区和主缓冲区之间切换。这并不是一个展示位图的很实际的方法。DDEX3扩展了DDEX2的功能,它包括了两个隐屏缓冲区,且在其内部存放有两个位图(一个对应于偶行屏幕,另一个对应于奇行屏幕)。DDEX3把一个屏幕按位隔行拷贝到后台缓冲区中,再把另外一个屏幕按位隔行拷贝到另一个后台缓冲区中,然后弹出表面(Surface)。
生成隐屏表面(Surface)
下列的代码在DDEX3 中加到dolnit 函数可生成两个隐屏 缓冲区:
// Create an offscreen bitmap. Ddsd.dwFlags = DDSD_CAPS DDSD_HEIGHT DDSD_WIDTH; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwHeight = 480; ddsd.dwWidth = 640; ddrval = lpDD->CreateSurface( &ddsd,&lpm DDSOne,NULL); if (ddrval != DD_OK) { return initFail(hwnd); } //Create another offscreen bitmap. Ddrval = lpDD->CreateSurface( &ddsd,&lpm DDSTwo,NULL); if (ddrval != DD_OK) { return initFail(hwnd); }
如代码中所示,dwFlags成员设定了应用程序将使用DDSAPS结构,并且设置缓冲区的高度和宽度。表面(Surface)是一个平面式隐屏缓冲区,就如同通过设置在DDSCAPS结构中的DDSCAPS_OFFSCREEN标志所表示的一样。在DDSURFACEDESc结构中,高度和宽度被分别设置为480和640。通过使用IDirectDraw::CreateSurface方法,表面(Surface)就这样被生成了。
因为两个隐屏缓冲区有着同样的大小,故生成第二个缓冲区的唯一要求就是再运行IDirectDraw::CreateSurface(当然,要用不同的指针名字)。
通过在DDSCAPS结构中,或是设置DDSCAPS_SYSTEMMEMORY,或是设置DDSCAP_VIDEOMEMORY的容量,你可以将该:隐屏缓冲区或是放置在系统内存中或是显存中。通过将位图存盘在显存中,你可以增加隐屏表面(Surface)和缓冲区之间切换的速度。当我们开始讨论位图动画时,速度将变得更加重要。但是,此时你应当注意:如果你仅为隐屏缓冲区设置DDScAPS_VIDEOMEMORY,而没有足够的显存来保存整个位图文件,那么,当你试图创建表面(Surface)时,就会返回一个DDERR_OUTOFVIDEOMEMORY的错误值。
将位图文件载入后台缓冲区
在两个隐屏表面(Surface)生成后,DDEX#使用INITSURFACES函数,从Frnt.bmp文件中将位图文件载入到表面(Surface)中。InitSurfaces函数使用Ddutil.cpp中的DDCopyBitmap载入这两个位图文件,如下列代码所示:
// Load our bitmap resource. Hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL),szBitmap, IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION); if (hbm == NULL) return FALSE; DDCopyBitmap(lpDDSone,hbm,0,0,640,480); DDCopyBitmap(lpDDSTwo,hbm,480,640,480); DeleteObject(hbm); return TRUE;
如果你在MS Paint(微软画笔)或是另一个绘画程序中看到Frnt.bmp文件,你可以看到位图文件是由两个屏幕组成的(其中一个在另一个的上部)。DDCopyBitmap函数在屏幕相汇点上将位图文件一分为二,并将第一份位图文件载入第一个隐屏表面(Surface)(IPDDSOne)中,同时将第二份位图载入第二个隐屏表面(Surface)(IPDDSTwo)中。
将隐屏表面(Surface)按位隔行拷贝到后台缓冲区
WM TIMER包含了写表面(Surface)和弹出表面(Surface)的代码。在DDEX3的情况下,它包含下列的代码,用来选择适当的隐屏表面(Surface),并将它按位隔行拷贝到后台缓冲区中。
rcRECT.LEFT =0; RCRECT.TOP =0; RCRECT.right =640; rcRect.bottom =480; if(phase) { pdds = lpDDSTwo; phase = 0; } else { pdds = lpDDSOne; phase = 1; } while(1) { ddrval =lpDDSBack->BltFast(0,0,pdds,&rcRect,FALSE); if(ddrval == DD_OK) { break; }
"phase"决定了将哪一个隐屏表面(Surface)按位隔行拷贝到后台缓冲区中。然后,IDirectDrawSurface::BltFAst方法被调用,并将已经被选择好的隐屏表面(Surface)按位隔行拷贝在后台缓冲区中,开始位置为(0,0),它位于屏幕的左上角。参数rcRect指向结构Rect,它定义了隐屏表面(Surface)的左上角和右下角。最后的参数被设置为FALSE(或0),这就表明了没有专门的转移标志以备使用。
在这里,我很想补充说明的是:在何种情况下应该选择IDirectDrawSurface::Blt方法,在何种情况下应该选择IDirectDrawSurface::BltFast方法。如果你正在从一个隐屏缓冲区中进行一次按位隔行拷贝,你应当使用IDirectDrawSurface::BltFast。如果你的系统显存中是使用硬件进行按位隔行拷贝,你虽然不会真正提高拷贝的速度,但是,它会节省系统模拟硬件时间,从而使整个按位隔行拷贝时间缩短约10%。因此,我推荐读者使用IDirectDrawSurface::BltFast进行所有的显示操作(从显存按位隔行拷贝到显存中)。如果你正在从系统内存中按位隔行拷贝,或者要求专门的硬件标志位,这样的话,你就必须使用IDirectDrawSurface::Blt。
一旦隐屏表面(Surface)被载入后台缓冲区中,后台缓冲区和主表面(Surface)就如同前边的例程中所显示的一样被弹出。