-------------------------------------------------------
游戏编辑器框架教程
无心柳
-------------------------------------------------------
一、前言:
我一直在寻找DX编程和MFC类库的结合点,前段时间曾经在第二人生http//webking.com.cn/secondlife上放了一个从《计算机编程技巧与杂志》来的例子。应该说,那个例子非常不错,唯一的缺点就是它只研究了全屏幕显示的情况,而对于地图编辑器人员来说,迫切需要的是既能够完全使用原有MFC的框架又能够使用DX的功能的东西,这也是我一直在寻找的,今天(1999/12/4)总算找到了。
促使我找到这个办法的是为MUD编写的那个地图编辑器,虽然功能上没有什么欠缺,但诚如美工所言,界面太差了,按钮按下去的时候没有一个响应动作,而且也不方便使用MFC的全部功能。在完成这个地图编辑器以后,其他程序员的MFC编辑器也在赶制之中,而他们的图形显示部分是一个难题。如果要使用经典编程方法去操作和显示256色以上的图片,在程序上是非常麻烦的。能否将MFC与DDRAW的窗口模式完美地融合?在完成地图编辑器以后,我已经有了这样的设想,就等着拿到机器上去测试了。结果一试之下,完全可行,而且它是如此地简单!
二、第一个例子:
在这个例子中,我用VC的AppWizard生成了一个标准的单文档窗口并保存。然后,在视类CGameEditView的头文件中加入了四个DX成员,一个DDRAW指针,一个DDRAW4指针,一个前缓冲,一个后缓冲,具体可以看源码。在头文件中,另外加入了两个函数,一个是Msg函数,用于显示消息框,这是从别人的例子中直接复制过来的;另一个是InitDDrawSurface()函数。然后在CGameEditView.cpp里加入了这两个函数的函数体。Msg没什么说的,你直接复制上去用就行了。InitDDrawSurface你也可以直接复制,这个函数里创建了一个DDRAW对象,再利用它获取了DDRAW4的接口。然后用DDRAW4接口创建一个DDSURFACE4主屏幕缓冲页,它负责在屏幕上显示内容。最后创建一个DDSURFACE4的后台缓冲页,它负责在主缓冲在屏幕上显示之前在后台准备一下画面。仅此两个函数而已。
程序中有两个动作,一个是在预创建窗口时执行InitDDrawSurface()对DDRAW页面进行初始化,如果不成功程序就退出。另一个动作就是在视类的OnDraw()中将一块矩形区域从后台缓冲复制到前缓冲(也就是在屏幕上显示出来)。由于后台缓冲没有放入数据,所以显示的内容将是随机的,一般是黑色的。显示的步骤可以看源码上的程序注释,我一般写得比较细。
为什么不加入什么撞球等的变化动作?这是因为我只想告诉大家一个最简单的DDRAW窗口页面如何创建和使用,下面的教程中会慢慢加入如何读入位图块和显示,时间控制,窗口区域检测,剪切块,颜色键等内容。当教程结束的时候,用MFC和DDRAW做一个简单游戏应该没有问题了。
下载源码GameEdit1.zip
三、第二个例子:
1、上个例子中有个明显的漏洞,在InitDDrawSurface()中创建后台缓冲的时候,显式地指定了宽和高。在前缓冲lpMainSurface创建时,没有指定大小,因为不需要,前缓冲指的就是整个屏幕的范围,它使用的是显示内存。而后缓冲是不显示的内容,它实际是在系统内存中(窗口模式不能在显示内存中再创建后台页面)创建的一个页面,一般情况下,应该设置得和前缓冲一样大或者更大。1024*768的显式指定对一般显示器可能够了,但万一用户使用更大的分辨率就可能导致错误。所以第二个例子中首先修改了InitDDrawSurface()中创建后台缓冲的部分,用标准WINDOWS函数GetDeivceCaps获取了当前显示分辩率的高和宽(当然也可以获得颜色位数,源码中有)。然后用获取的高和宽来创建后台缓冲,这样就保证了与分辨率的大小相同。当然这也不是绝对保险,万一用户运行中修改了分辨率仍有可能导致错误,但一般我们不需要为那种情况负责了,可以不去考虑。
2、这次我们要读入一个图片,看起来挺恐怖,你好象需要去研究BITMAP的文件结构。其实不需要那么复杂,对一般的游戏和编辑器来说,根本不用去了解那些细节的问题,在DX的例程中多次用到的两个文件可以直接搬过来用,它们是ddutil.h和ddutil.cpp,你可以在大部分DX的例程中找到它们,然后复制到你程序的文件夹下,再在工程中加入这两个文件,试试重编译。直接重编译是无法成功的,这是因为MFC程序有点特殊性,要使它能被MFC所用,必须在上面提到的这两个文件首部都追加一句#include "stdafx.h"。用过这个语句,MFC就认这两个文件,然后可以正常编译通过。当然,如果你想在VIEW类中使用里的函数,还要在GameEditView.cpp的头部加入#include "ddutil.h"。这是常识,说这些好象有点多余,在源码中这几个步骤都做了而且有说明。
3、通过第2步,已经可以随时读入BMP文件了,具体的读法是非常简单的,因为你已经可以使用ddutil.cpp里的函数。如果需要,你也可以去修改这个文件的源码或者制作修改版。作为例子,我先在GameEditView.h中增加了一个DDSURFACE4的页面指针:
LPDIRECTDRAWSURFACE4 lpBitMapBuffer;//DirectDraw读入的位图页面保存在这里
再在GameEditView.cpp的预创建窗口PreCreateWindow()函数中将一幅位图读入这个位图页面。读入成功后,将这个临时页面的内容复制到后台缓冲。这样,后台缓冲就有了内容。再编译一次程序,会发现原来窗口左上角那个黑块已经变成这幅图片(一幅UO中截下来的骑马图),代码如下:
lpBitMapBuffer=DDLoadBitmap(lpDD4,"test.bmp",200,200);
if(NULL==lpBitMapBuffer)//如果没有读入成功,退出程序
{
Msg("无法装入位图,演示无法进行");
exit(-2);
}
//读入成功就复制到后台缓冲去
lpBackBuffer->BltFast(0,0,lpBitMapBuffer,&(CRect(0,0,200,200)),FALSE);
你可以看到代码中指定的位图文件名是test.bmp,所以你也需要一个这样文件名的位图文件,在下面的源码包里附有一个,如果你想换一个文件也可以,不过这个文件以后将用于演示动画的例子。
第二个例子完,需要的话请下载源码GameEdit2.zip
三、与GDI结合:
上面的两个例子的源码中没有包含资源文件,所以在使用的时候请先按照上面提到的方法建立自己的简单MFC单文档程序,再将源码文件替换你自己的文件再编译。
在第二个例子中,读入了一幅位图并显示在窗口的左上角。这在程序运行中没有任何问题,而且速度还相当快。但如果你同时运行了其他程序,或者将窗口移动到屏幕的边缘位置,会出现一些不正常现象。如果将窗口移动到屏幕边缘,在从后台到前台进行传送的时候,会发生非常矩形错误,意思是指你试图将页面传送到屏幕区域外,也就是前缓冲以外(前面说过,前缓冲与屏幕大小是一样的)。其结果并不会使你的程序当机,只是这次传送不会进行。另外,当你用其他程序的窗口盖住本程序窗口并移动上面的窗口的时候,本窗口的刷新可能导致将位图盖在其他窗口区域内。这是个严重的问题,在编写全屏幕程序的时候不会发生这种情况,因为屏幕被你的程序独占了,而在编写窗口程序的时候,你的程序就会显示非常差劲。
要解决上面的问题,一个办法是当程序失去焦点的时候,你就禁止将位图从后缓冲传送到前缓冲,当重新获得焦点的时候,再恢复位图传送。但有时候这样不理想,比如你在进行程序或者游戏的时候,同时发ICQ的信息,虽然ICQ占的屏幕面积不大,还是会导致你的程序停止更新画面,而如果你在游戏中此时正在紧张而且随时需要你干预的话,程序看起来就非常不友好。
另一个办法是与GDI结合,与GID结合会导致在图像速度上有微小的下降,但会使你的程序看来非常自然。其实方法也非常简单,就是获取后台缓冲的HDC,然后用HDC的BitBlt方法将后台缓冲复制给窗口CDC,可以将OnDraw中的BltFast的方法修改为:
HDC hdc;
lpBackBuffer->GetDC(&hdc);//获得后台缓冲的HDC
::BitBlt(pDC->m_hDC,0,0,100,100,hdc,0,0,SRCCOPY);//将后台缓冲的图片传送到窗口DC
lpBackBuffer->ReleaseDC(hdc);//不要忘了用完后释放HDC,否则会出错
为了与HDC匹配,所以使用pDC的成员m_hDC。BitBlt的具体用法可以看一下帮助文件。使用这个办法以后,无论你将窗口移动到什么位置,以及无论你用什么窗口盖住程序的窗口,都不会有任何不正常的现象发生。它唯一的缺点就是比BltFast慢一点点。因为DC的处理方法是分几次进行,第一次复制到一个DC自己的后台缓冲,然后根据窗口的合法区域分一次或多次复制到屏幕。所以这个方法适用于制作编辑器以及对速度不是极度敏感的游戏。如果要达到最高速度,当然是非全屏幕模式莫属。
本文地址:http://com.8s8s.com/it/it3895.htm