DirectDraw颜色键和剪切处理

 


☆ 颜色键

颜色键使一个位图被拷贝到另外一个位图上时,不使全部的象素都显现。例如:当你把一个精灵(游戏中会动的对象通常都称做精灵)拷贝到地图上(背景上)时,这个精灵位图通常不会是一个精灵形状的位图,它一般都是一个矩形位图,位图里包含你所须要的精灵(除非你的精灵就是一个矩形机器人^_^),不使用颜色键拷贝的结果如图一:

DirectDraw颜色键和剪切处理

【图一】
这并非咱们想在游戏中获得的效果。游戏中,这个精灵是不会有那个黑色的底框的。地图是先于精灵显示的,那么精灵走到树后时,还应有相应被遮挡的部分,这个先不讨论,下一节再说。如今,对咱们更重要的是,若是不该用颜色键,这个精灵将永远带着这个黑色底框,这是绝对不能容忍的。
为了解决这个问题,咱们使用源颜色键。这个源颜色键告诉你精灵矩形的哪些颜色将不被拷贝(固然咱们是让黑色不被拷了^_^)。一个颜色键由两个值组成:一个低位颜色值,一个高位颜色值。当一个颜色键被申请使用时,在两个值之间的颜色,包括这两个值的颜色都将不会被拷贝。在DirectX中有一个结构用来处理它,叫做DDCOLORKEY,看看吧:

typedef struct _DDCOLORKEY{
    DWORD dwColorSpaceLowValue;
    DWORD dwColorSpaceHighValue;
} DDCOLORKEY, FAR* LPDDCOLORKEY;


很简单的结构,我就不解释了。我将展现给你使用了颜色键以后的效果。我使用颜色键的高位和低位两个值仅仅把黑色包括在它们之间。所以,黑色是惟一不会被拷贝的颜色。图二就是使用颜色键的结果:

【图二】
好多了,是否是?这就是咱们想获得的结果!如今,在我告诉你怎样创建和使用颜色键以前,我还有说一说目标颜色键,尽管咱们的确咱们不经常使用到它(咱们经常使用的是源颜色键)。鉴于源颜色键定义了哪些颜色键不能被拷贝,目标颜色键定义了哪些颜色不能被写入(覆盖)。听起来很怪异,是否是?我也有同感。^_^ 举个实例你就明白了。当你要把A位图拷贝到B位图的下面,意思就是把A位图做为背景,例如因为某种理由,须要把一个文本框拷贝到空的后缓冲区,而后再把背景画面拷贝到这个后缓冲区,但你又不能覆盖先前的文本框。所以,在后缓冲区里除了文本框的那些黑色的部分才能被写入象素。看看图三,真相大白:

【图三】
我也不清楚你何时须要处理这种状况,可是你的确可能用到(一旦你用到了,可千万要告诉我哦,我一直没有遇到这种状况呢^_^)。如今,你已经知道什么是颜色键了,让咱们看看怎样使用它们吧!

☆ 设置颜色键

在DirectDraw中有两种方法使用颜色键。第一种,你能够连接一个颜色键(或者两个,若是你同时使用源和目标颜色键)到表面,而后在位块传输时定义DDBLT_KEYSRC,DDBLT_KEYDEST,DDBLTFAST_SRCCOLORKEY或DDBLTFAST_DESTCOLORKEY标志,具体使用哪一个标志,取决于你使用哪一个位块传输函数和使用哪一种颜色键。第二种,你能够建立一个颜色键,而后经过DDBLTFX结构传送给位块传输操做。当你不断地须要使用颜色键时,我向你推荐第一种方法;反之,当你偶然要使用一次颜色键,就用第二种方法吧!
你能够把颜色键连接到已经创建好了的表面,也能够在创建表面的同时创建颜色键。两种方法我都将详细告诉你。假设你工做在16-bit显示模式下,是565象素格式,你要在后缓冲区使用一个仅包含黑色的源颜色键。若是你的后缓冲区已经创建好了,你就能够简单创建一个DDCOLORKEY结构,而后把它传递给IDirectDrawSurface7::SetColorKey()函数,以下所示:

HRESULT SetColorKey(
    DWORD dwFlags,
    LPDDCOLORKEY lpDDColorKey
);


记住要用FAILED()宏检测这个函数的返回值,保证一切都在计划之中。函数的参数很简单:
※ DWORD dwFlags:决定所使用颜色键类型的标志。如下三个是你将用到的:

◎ DDCKEY_COLORSPACE:该结构包含一个颜色范围,若是结构包含的是单独的颜色键则不做设置。
◎ DDCKEY_DESTBLT:该结构指定颜色键或者颜色范围做为用于位块传输操做的目标颜色键。
◎ DDCKEY_SRCBLT:该结构指定颜色键或者颜色范围做为用于覆盖操做的颜色键。

※ LPDDCOLORKEY lpDDColorKey:这是指向DDCOLORKEY结构的指针。

就这么多。你能够根据你所须要使用的颜色键适当地定义位块传输的标志。注意,一个颜色键连接到表面,并不意味着你每一次必须使用它。若是你只定义了DDBLT-WAIT或DDBLTFAST_WAIT标志,颜色键将被忽略。下面是设置颜色键的方法:

DDCOLORKEY ckey;
ckey.dwColorSpaceLowValue = RGB_16BIT565(0, 0, 0); // or we could just say '0'
ckey.dwColorSpaceHighValue = RGB_16BIT565(0, 0, 0);
if (FAILED(lpddsBack->SetColorKey(DDCKEY_SRCBLT, &ckey)))
{
    // error-handling code here
}


若是你要为已经创建的颜色键连接一个表面,有几件事情你须要作。首先,当你定义DDSURFACEDESC2结构的有效成员时,你须要使dwFlags成员包含DDSD_CKSRCBLT或者DDSD_CKDESTBLT标志,具体使用哪一个标志,取决于你要使用哪一种颜色键。回头再看看DDSURFACEDESC2结构,它包含两种DDCOLORKEY结构。一种称为ddcdCKSrcBlt,另外一种称为ddcdCKDestBlt,填写适当的结构来建立表面。你就须要干这么多!如下是关于640×480大小的离屏表面的实例代码:

// set up surface description structure
INIT_DXSTRUCT(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CKSRCBLT;
ddsd.dwWidth = 640; // width of the surface
ddsd.dwHeight = 480; // and its height
ddsd.ddckCKSrcBlt.dwColorSpaceLowValue = RGB_16BIT(0,0,0); // color key low value
ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = RGB_16BIT(0,0,0); // color key high value
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; // type of surface

// now create the surface
if (FAILED(lpdd7->CreateSurface(&ddsd, &lpddsBack, NULL)))
{
    // error-handling code here
}


关于颜色键的部分到此结束。如今咱们能够进行本章最后一项了——剪切。

☆ IDirectDrawClipper接口

假设你有一个图形,而你却只想把它的一部分显示在屏幕上。你应该怎样作呢?若是你曾经在DOS下编写过游戏,你可能对剪切望而生畏。Well,在DirectX下,这只是小菜一碟!首先的首先,这的确是很容易作到的,由于DirectX用矩形来作位块传输,改变矩形的坐标要比指定内存中的哪一部分图形被拷贝(就像在DOS下所作的)要容易的多。其次,DirectDraw还为此提供了一个接口——IDirectDrawClipper。
DirectDraw的剪切性能彻底能够知足你的要求,你不但能够剪切矩形区域,你还能够剪切任意多边形区域!真的是很棒!若是你在屏幕上同时要显示一个主窗口,在屏幕的一边显示一个状态栏,在屏幕的底部显示一个文字提示栏,而且用黑色分隔开这些区域,你能够用DirectDraw的剪切功能完成它,很是容易的!
作这样的操做你须要分几步走。首先你得获得一个指向IDirectDrawClipper接口的指针。没什么难的,只须要调用IDirectDraw7::CreateClipper(),以下:

HRESULT CreateClipper(
    DWORD dwFlags,
    LPDIRECTDRAWCLIPPER FAR *lplpDDClipper,
    IUnknown FAR *pUnkOuter
);


在你调用这个函数前,你应该首先声明一个LPDIRECTDRAWCLIPPER类型的指针,这样你才能把它的地址传递给这个函数。记着要检测函数的调用是否成功哦!如下是函数参数的解释:
※ DWORD dwFlags:简直就是幸福——这个参数尚未使用过,设置为0。^_^
※ LPDIRECTDRAWCLIPPER FAR *lplpDDClipper:把你的LPDIRECTDRAWCLIPPER指针的地址传递给它。
※ IUnknown FAR *pUnkOuter:你知道怎么作,设置为NULL。^_^

一旦你有了本身的接口指针,下一件事就是建立剪切列表(clip list)。多个剪切的矩形组成了剪切列表。须要使用到RGNDATA结构,它包含了足够的信息来定义一个任意的区域,看看它的原形吧:

typedef struct _RGNDATA {
    RGNDATAHEADER rdh;
    char Buffer[1];
} RGNDATA;


我须要详细解说一下它的参数。
※ RGNDATAHEADER rdh:它是RGNDATA结构中嵌套的一个结构。它包含了第二个参数——Buffer的全部信息。它定义了须要剪切区域里的矩形的数目,整个区域的形状等信息。咱们过一下子再具体讨论它。
※ char Buffer[1]:这并不意味着是只有一个值得数组;它将是在内存中任意大小的区域来控制着实际的剪切区域数据。一样的,对于RGNDATA结构,咱们要声明一个指向该结构的指针,而后使用malloc()函数为RGNDATAHEADER设置足够的内存空间,也就是为剪切列表设置足够的空间。有一件事我要提醒你:剪切列表里的矩形按从上到下,而后从左到右排列,不能交迭。

我已经意识到你有些胡涂了,没关系,继续学习,一切会好起来的。下面是RGNDATAHEADER结构的原形,它比较好理解:

typedef struct _RGNDATAHEADER {
    DWORD dwSize;
    DWORD iType;
    DWORD nCount;
    DWORD nRgnSize;
    RECT rcBound;
} RGNDATAHEADER;


※ DWORD dwSize:结构的大小。简单的使用sizeof(RGNDATAHEADER)好了。
※ DWORD iType:它描述了每一个区域的外形。它是另有玄机的,之后咱们再把它扩展开来讨论。如今,你只要把它设置为RDH_RECTANGLES就行了,这也正是咱们须要的。
※ DWORD nCount:这是组成该区域的矩形的数量。换句话说,就是你的剪切列表理的矩形数。
※ DWORD nRgnSize:为缓冲区的大小设置它,将获得自身的区域数据。因为咱们使用n个矩形组成了剪切区域,因此它的大小应该是sizeof(RECT) *nCount。
※ DWORD rcBound:这是一个矩形类型,包含了剪切列表里的全部矩形。一般你把它设置成表面上须要剪切部分的尺寸。

如今,咱们能够创建一个剪切列表了。首先咱们声明一个LPRGNDATA的指针,分配给剪切列表足够的内存空间;而后根据上面咱们所学的设置每一个成员。让咱们看看最简单的实例,你可能常常要用到它哦!它只有一个剪切区域,而且,就是整个屏幕,是640×480显示模式的。如下就是代码:

// first set up the pointer -- we allocate enough memory for the RGNDATAHEADER
// along with one RECT. If we were using multiple clipping area, we would have
// to allocate more memory.
LPRGNDATA lpClipList = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER) + sizeof(RECT));

// this is the RECT we want to clip to: the whole display area
RECT rcClipRect = {0, 0, 640, 480};

// now fill out all the structure fields
memcpy(lpClipList->Buffer, &rcClipRect, sizeof(RECT)); // copy the actual clip region
lpClipList->rdh.dwSize = sizeof(RGNDATAHEADER); // size of header structure
lpClipList->rdh.iType = RDH_RECTANGLES; // type of clip region
lpClipList->rdh.nCount = 1; // number of clip regions
lpClipList->rdh.nRgnSize = sizeof(RECT); // size of lpClipList->Buffer
lpClipList->rdh.rcBound = rcClipRect; // the bounding RECT

一旦有了剪切列表,你须要把它做为你的剪裁板。你将调用IDirectDrawClipper接口的函数SetClipList()。就是下面这个东东:

HRESULT SetClipList(
    LPRGNDATA lpClipList,
    DWORD dwFlags
);


你所要作的就是把RGNDATA的指针传递给它。参数dwFlags没有用,设置为0。如今,剪切列表设置好了,还须要一步,就是把剪切列表连接到你所要控制的表面上,这须要调用SetClipper()函数,它将表面指针做为其惟一的参数:

HRESULT SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper);

你知道应该怎样作:就是把你已经设置好的接口的指针传递给它。任什么时候候,你要位块传输一个有剪裁板相关联的表面,剪裁板将作全部的工做。因此若是你要在屏幕上显示一个贴片的一部分,例如传输一个矩形坐标为{-10,-10,6,6},或者相似的矩形贴图,都不会有麻烦的。很不错吧,嗯?
关于剪切的最后一件事情是必需要用free()函数释放你用malloc()设置的内存空间。还有,就是因为某种缘由在调用SetClipList()或SetClipper()失败后,在返回错误代码前或你要根据失败的结果进行操做前,要释放内存空间。在你完成用LPRGNDATA的指针设置剪切列表后,这个指针就没有存在的意义了,因此它占用的内存空间将被当即释放。

☆ 总结

到此,关于DirectDraw的部分就讨论完了!你真的从这六章里学到了不少的知识,若是你坚持到如今,那么祝贺你,你真的已经走了很长一段路了。^_^ 对于这一章咱们所学习的,我将把它们组合在一块儿,给你一个Demo程序,它将从资源调用位图,使用位块传输位图,颜色填充,放缩位图比例,使用剪切功能。这个就是源代码下载的地址:http://www.aeon-software.com/downloads/clipscale.zip 。
仍然有些东东咱们应该讨论,例如页面的切换(flipping),双缓冲区的应用等,不管如何,咱们还将继续,因此没必要担忧咱们会遗漏重要的内容。^_^
如今,最初的原始积累已经结束,咱们之后的焦点会从建立Windows程序转移到建立一个贴图基础的RPG游戏。之后的章节将包括用DirectInput创建一个好的输入机制,写一个基础的引擎脚本,播放背景音乐和配音,等等。下一章,咱们将学习为贴图游戏制做一个简单的卷轴游戏引擎,很容易的,没有你想象的那么难哦!
数组

相关文章
相关标签/搜索