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,
LPDIRECTDRAWCLIPPER FAR *lplpDDClipper,
IUnknown FAR *pUnkOuter
);
※ 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;
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 dwSize;
DWORD iType;
DWORD nCount;
DWORD nRgnSize;
RECT rcBound;
} 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 t 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()。就是下面这个东东:
// 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 t 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
HRESULT SetClipList(
LPRGNDATA lpClipList,
DWORD dwFlags
);
你所要做的就是把RGNDATA的指针传递给它。参数dwFlags没有用,设置为0。现在,剪切列表设置好了,还需要一步,就是把剪切列表链接到你所要控制的表面上,这需要调用SetClipper()函数,它将表面指针作为其唯一的参数:
LPRGNDATA lpClipList,
DWORD dwFlags
);
HRESULT SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper);
你知道应该怎样做:就是把你已经设置好的接口的指针传递给它。任何时候,你要位块传输一个有剪裁板相关联的表面,剪裁板将做所有的工作。所以如果你要在屏幕上显示一个贴片的一部分,例如传输一个矩形坐标为{-10,-10,6,6},或者类似的矩形贴图,都不会有麻烦的。很不错吧,嗯?关于剪切的最后一件事情是必须要用free()函数释放你用malloc()设置的内存空间。还有,就是由于某种原因在调用SetClipList()或SetClipper()失败后,在返回错误代码前或你要根据失败的结果进行操作前,要释放内存空间。在你完成用LPRGNDATA的指针设置剪切列表后,这个指针就没有存在的意义了,所以它占用的内存空间将被立即释放。
总结
到此,关于DirectDraw的部分就讨论完了!你真的从这六章里学到了很多的知识,如果你坚持到现在,那么祝贺你,你真的已经走了很长一段路了。对于这一章我们所学习的,我将把它们组合在一起,给你一个Demo程序,它将从资源调用位图,使用位块传输位图,颜色填充,放缩位图比例,使用剪切功能。
仍然有些东东我们应该讨论,例如页面的切换(flipping),双缓冲区的应用等,无论如何,我们还将继续,所以不必担心我们会遗漏重要的内容。
现在,最初的原始积累已经结束,我们以后的焦点会从创建Windows程序转移到创建一个贴图基础的RPG游戏。以后的章节将包括用DirectInput建立一个好的输入机制,写一个基础的引擎脚本,播放背景音乐和配音,等等。下一章,我们将学习为贴图游戏制作一个简单的卷轴游戏引擎,很容易的,没有你想象的那么难哦!
