2D游戏编程笔记.doc
2D游戏编程笔记-1 here本人同意他人对我的文章引用,但请在引用时注明出处,谢谢作者:蒋志强2D 游戏开发与3D相比相对简单,在主要的流行平台都提供了相应的API支持其开发.比如对本地windows原生程序,系统提供了GDI;对windows 平台上更高效率的需求,以COM的形式提供DirectDraw;在托管的.NET环境下提供了GDI+;JAVA也有相应的支持2D图形的包等.不同的 API接口在调用方法上虽然有所差异,但思想和处理方法都是类似,所以熟练的使用了一种以后,可以很容易的举一返三.在我的该系列笔记中, 介绍的是DirectDraw这种编程接口.DirectDraw是microsoft推出的DirectX开发包中的一个组件,专门用于提供高效的2D 图象编程支持.从DirectX8的版本开始,DirectDraw就没有被更新了,在新的DirectX版本将用于2D的DirectDraw组件和用 于3D的Direct3D组件合并为了Graphics组件,将2D和3D图形处理合并.对于3D图形图象处理,我们只要固定观察角度,那么就等同于在处 理2D图象,这也是Graphics替代D3D和DirectDraw的原因,但是以3D的方法来处理2D的图象,也会让本来可以更简单的操作人为变得复 杂.所以我仍然选择DirectDraw编程接口.我必须先说一下COM,因为DirectX的组件都是以COM组件来实现的.我们的目的 不是要自己编写COM组件,所以我们只需要有个大概的了解就,就可以理解DirectDraw组件的工作方式了.(当然,如果你愿意对COM做更深入的学 习也是会对你很有好处的.)COM是组件对象模型(Component Object Model)的意思,它是microsoft定义的一种进行组件编写的规范,按照COM规范编写的组件就叫做COM组件组件是相对独立的软件可复用的模 块,有按照不同组件规范编写的组件,COM组件只是其中的一种类型COM组件用C+实现的,实际上一个COM组件在内存中就是一个C+的类,但 COM组件不一定必须用C+语言来编写,也可以是其他的语言(比如Delphi,VB),只要在内存中与C+编写的组件的类相同就可以COM组件对 外通过接口提供各种方法的调用DirectDraw是COM组件,我们对它的使用,也是通过它提供的接口中的方法调用每个新版本的COM组件,都必须 实现以前旧版本COM组件的接口,所以在新的DirectX9中,我们仍然可以调用老的DirectDraw7组件我们先看一下,DirectDraw在windows程序体系结构中的工作位置,如下图所示:最 上面是我们的win32应用程序,我们进行2D图象处理可以可以直接在程序中调用GDI,GDI再通过DDI,也就是显卡驱动程序调用显卡硬件进行绘图 另外一种方式是通过DirectDraw组件提供的接口中的方法来进行绘图从图中我们可以看到DirectDraw下面既有HEL层也有HAL层 HAL层是硬件抽象层,在microsoft推出DX后,要求显卡硬件制造商要遵守规范,这就是DirectX规范,我们现在在市面上看到的显卡都标有支 持DirectX9的说明,向上对操作系统提供相同的功能实现由于支持DirectX9规范的显卡,都实现了DirectX所要求的功能调用(当然同样 支持DirectX9规范的高档的显卡实现同样的功能会比入门显卡快),所以可以通过相同的硬件抽象层HAL来调用显卡的功能,而不用关心显卡的GPU是 Nvidia的还是ATI的还有一个叫做HEL的硬件仿真层,这是由于也许游戏程序中使用了DirectX9组件最新接口中的新的方法调用,但你机器上 的显卡比较老,只能支持DirectX8的规范,所以你的显卡的GPU无法完成该方法调用,这个时候就可以通过HEL让CPU来通过软件的方式来实现该调 用,而不用让GPU去做它根本没有能力做的事情当然由CPU通过软件计算会比通过GPU硬件实现要慢很多,但是我们获得的好处显而易见,游戏程序可以在 老的机器上跑通过HEL和HAL层的体系结构,实现了程序的硬件无关,这十分的有意义基础的知识概念介绍得差不多了,下一次我们要对编 程环境进行配置,并完成一个真正的DirectDraw程序,我假定我们都有windows编程的基础,如果没有,建议你参考一下Petzold的那本 windows程序设计很令人兴奋吧,突然发现已经很晚了,我也该睡觉了,明天还要上班真是可怜成都的天气实在是闷热,我去洗澡了, ByeBye. 2D游戏编程笔记-2 本人同意他人对我的文章引用,但请在引用时注明出处,谢谢作者:蒋志强要 进行DirectX的程序开发,必须要安装DirectX的SDK(Software Develop Kit),因为我们使用DirectDraw7,所以我们可以选择安装DirectX7,DirectX8或DirectX9由于COM组件的规范规 定,新的COM组件必须实现其对应老版本组件的所有接口,所以安装DirectX9是很明智的选择但是DirectX9的SDK没有带 DirectDraw的演示程序,DirectX8则带了DirectDraw的演示程序,通过阅读别人的程序是提高自己水平和自学的最重要最有效的方 法,所以我选择安装DirectX8版本的SDKDirectX的SDK可以在微软的官方网站上找到下载,完整的DirectX SDK开发包有几百兆的大小,包括了以DLL文件存在的COM组件,LIB库文件,相应的C+头文件,说明文档,工具软件和演示程序,完整的SDK开发 包非常有用但如果你觉得太大,你可以只下载LIB库文件和相应的C+头文件,你可以在游戏开发资源网下图是安装向导,你要指定安装目录我选择C:Program FilesDXSDK8作为安装目录 然后在下一步你需要选择需要安装的内容,因为我们使用VC进行开发,所以可以取消安装VB的文档和VB演示程序选项,如下图所展示 点 击Next,你可以选择Debug或者Retail类型的DirectX运行环境Debug类型会比Retail类型的运行环境慢,但它提供了在开发过 程中更多的调试信息,而Retail类型则没有这些信息为了方便我们在写程序时进行调试,我们应当选择Debug类型的运行环境然后点击Next,安 装程序则自动完成安装安装完成后,我们可以在开始菜单中找到DX8的选项,其中包括了帮助文档,辅助工具软件和VC的演示程序,如下图所展示我 们使用VC来进行DirectX的编程,当然你也可以使用其它的开发工具(比如VB)我们现在可以使用的VC有多个版本,包括VC6, VC.NET2002,VC.NET2003或VC.NET2005,我们是进行DX编程,写非托管的程序,所以使用老的VC6和最新的 VC.NET2005没有区别,但是VC6在效率和稳定性上经受了时间的考验,广受程序员的喜爱,出于可靠性的考虑我们使用VC6VC6的安装我就不介 绍了,一路点Next就OK,在VC6安装完成后,还需要进行几个设置运行VC6,点击工具菜单选项,会弹出下面的选项对话框点 中目录标签,然后将右上方的下拉选项选中Include files,然后在下面路径列表中添加DX安装目录中的C+头文件include目录设置Include files目录,是给VC指明在遇到#include <d3d8.h>这样的预编译指令时,在什么地方查找该文件然后将右上方的下拉选项选中Library files,然后在下面路径列表中添加DX安装目录中的导文件Lib目录设置Library files目录,是因为在VC链接程序链接DirectX组件时,需要库文件的信息,设置该目录后,VC链接程序会到该目录下查找使用DirectX所需 要的Lib库文件设置完Include目录和Lib目录后,就可以使用VC新建工程了,在工程如果要使用DirectDraw组件则需要进行最后一项设置创建一个工程后,VC6的菜单栏会多一个工程菜单,选择该菜单中的设置,会弹出以下的对话框窗口选 择连接标签,在对象库模块文本框中添加dxguid.lib ddraw.lib两个lib库文件,因为使用DirectDraw组件将使用这两个库文件,VC链接器将在前面设置的Library files的那些目录中查找这里指定的库文件完成这里的设置以后,我们就真正完成了所有的环境设置了现在我们来写个DirectDraw的程序首先,我们新建一个win32工程,产生一个Hello,world!的工程,然后按上面的方法设置所需要的dxguid.lib ddraw.lib库文件然后在cpp文件中添加加以下两句:#include <ddraw.h>LPDIRECTDRAW7 p_Ddraw= NULL;然 后按下F7生成exe文件,生成成功证明环境设置正确然后按F5运行程序,一个典型的Hello,world!的win32程序正常运行其实加入以上 两句后,就可以说它是一个DirectDraw程序了,因为程序中定义了一个DirectDraw接口的全局变量,尽管它的值现在为空,尽管没有产生 DirectDraw对象,但谁又能否认它是一个DirectDraw程序呢?OK,这次的内容到此结束今天知道公司里许多同事被炒了,而且欠的工资也没有发,心里很是难受,不过明天也该轮到我了哎,人生八九不如意下次我们将真正的使用DirectDraw在程序里做点事情,ByeBye 2D游戏编程笔记-3 本人同意他人对我的文章引用,但请在引用时注明出处,谢谢作者:蒋志强在 上一次的笔记中,我们定义了一个全局变量LPDIRECTDRAW7 p_Ddraw ,其中LPDIRECTDRAW是该变量的类型,在VC编程序中会遇到很多的变量类型,它们都是在头文件中通过宏进行定义的。我们不可能都能够将其名字记 忆准确。为了帮助我们在编程时正确的拼写变量名字,我们要使用一个名为Visual Assist的插件程序。事实上,在写用VC做开发时,程序员们几乎都无一例外的使用Visual Assist来帮助提高效率。其实在新版本的VS中(包括VS2003,VS2005),microsoft都为其C#的开发添加了一种叫做"智能感应" 的技术,实现和Visual Assist相同的功能(我个人感觉做得比Visual Assist更好),但对于C/C+却没有实现该技术,这让我很是困惑不解,所以我们甚至在使用VC.NET 2005时,仍需要安装Visual Assist插件。我们可以从网上找到Visual Assist X V10的版本,安装界面如下图所示一路点Next/OK安装完成后,重新打开VC6会发现左边多了一个VA View的标签,这个标签就是Visual Assist插件所加上去的,同时工具栏也增加了相应的按钮.如下图所示:OK,有了Visual Assist的帮助,现在我们可以进行我们真正的工作了.我们要做的事情可以分为以下几步:1.创建DirectDraw对象;2.设置控制级别和显示模式;3.创建页面;4.装载图片到页面;5.与GDI一起工作;6.换页处理;我们开始写程序的时候,经常会遇到很多问题,这个时候你应该去查询DX的 Document或者是MSDN,你甚至可以在创建DirectDraw对象是通过HRESULT WINAPI DirectDrawCreate( GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter );函数完成的。第一个参数是显卡的GUID,如果你的机器只有一块显卡(大多数机器都是这种情况),你直接输入NULL作为实参就可以了。第二个参数是指向IDirectDraw接口的指针的地址(就是指针的指针),你首先要定义一个IDirectDraw接口类型的指针 LPDIRECTDRAW7 p_DDraw; 将其地址&p_DDraw作为参数传递,要注意的是你必须要将&p_DDraw进行强制类型转换,转换为(void*)类型。第三个参数为以后扩展所保留的,现在没有任何用,我们直接传递NULL就可以了。创建DirectDraw对象还可以使用另外一个函数WINAPI DirectDrawCreateEx( GUID FAR * lpGuid, LPVOID *lplpDD, REFIID iid,IUnknown FAR *pUnkOuter );,该函数比老的DirectDrawCreat多一个REFIID iid参数,该参数用于指定创建的DirectDraw对象的版本,我们是要创建DDraw7的版本,所以应该传递IID_IDirectDraw7作为参数。现在我们可以使用下面的代码完成DirectDraw对象的产生:LPDIRECTDRAW p_DDraw;if(DirectDrawCreate(NULL,&p_DDraw,NULL) != DD_OK) return FALSE;产生了DirectDraw对象以后,我们就应该对它的控制级别和显示模式进行设置了。进行协作级别设置和显示模式设置分别使用IDirectDraw接口中的SetCooperativeLevel和SetDisplayMode这两个方法。DirectDraw 程序进行图像处理有两种方式,全屏和窗口。在全屏模式下,显示设备被我们的应用程序所独占,在窗口模式下,我们的程序运行在窗口状态下,其他的应用程序还 是可以在显示设备上显示。因为窗口模式需要考虑其它的程序,所以不能对显示设备“为所欲为”的 操作,比全屏模式要复杂。我们先从较简单的入手,使用全屏模式进行编程。函数HRESULT SetCooperativeLevel( HWND hWnd, DWORD dwFlags );进 行控制级别的设置,第一参数是进行DDraw操作的窗口的句柄。第二个参数指定协作级别,我们 DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN作为参数。DDSCL_EXCLUSIVE表示程序独占显示设备, DDSCL_FULLSCREEN表示程序全屏显示,使用或操作|表示程序独占设备而且全屏显示(写过SDK程序的朋友对这种或运算连接几个参数的方式应 该是很熟悉的)。为什么我知道是传递这样的参数呢?因为我查阅了DX的Document,我再次强调查阅文档非常非常重要,一定要学会查阅文档来解决问题 (learn how to learn)。我到现在为止都讲得很详细,随着笔记得继续,我将加快速度,过于细节的东西大家要到文档去查找。函数HRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags );进 行显示模式的设置。前两个参数分别指定显示的水平和垂直方向的分辨率,第三个参数是色深,第四个是刷新率,如果传递0为参数就使用当前显示设备的刷新率, 第五个参数我们用不到,直接传递0就可以了。水平/垂直分辨率和色深应该是显卡能够支持的显示模式,如果显卡不支持,则函数的返回值就不会是DD_OK。现在我们可以使用下面代码对DirectDraw对象的控制级别和显示模式进行设置:if(p_DDraw->SetCooperativeLevel(mainWindow,DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN) != DD_OK) return FALSE; if(p_DDraw->SetDisplayMode(800,600,32,0,0) != DD_OK) return FALSE;在 这里我们选择了800*600 32bit的显示模式,因为该模式可以被绝大多数显卡支持。完成了DDraw的创建和初始化以后,我们就应该创建页面(也就是Surface)了。页面这 个概念,我们可以理解为一张纸,我们可以在上面作绘图操作。我们要创建的页面的类型会有3种:主页面,它代表了当前的屏幕显示;后台页面,是当前暂时不可 见的页面,在进行换页操作后,主页面变为后台页面,后台页面链中的一个页面变为当前可见的主页面;离屏页面,是一种始终都不可见的页面,主要是用来读取图 片资源,然后blit到后台页面上。我们先来创建主页面,这是必不可少的代表显示屏幕的页面。为了要描述所创建的页面的各种属性,我们要创建一个DDSURFACEDESC2类型的结构体变量,下面的语句完成了该结构体的填充。 DDSURFACEDESC2 frontSurfaceDes; ZeroMemory(&frontSurfaceDes,sizeof(frontSurfaceDes); frontSurfaceDes.dwSize = sizeof(frontSurfaceDes); frontSurfaceDes.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT; frontSurfaceDes.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; frontSurfaceDes.dwBackBufferCount = 1; 第一句新建了一个名为frontSurfaceDes的DDSURFACEDESC2 类型的结构体变量。第二句的ZeroMemory函数将该结构体变量的成员全部清0(因为新建的变量的内部数据是随机的值,不清0的话有可能会产生莫名其妙的问题)。该结构体的dwSize 字段要填写该结构体的大小;dwFlags 字段指明该结构体的哪些字段将要被填写(除了dwSize 和dwFlags 字段以外)。DDSURFACEDESC2 结构体中的ddsCaps字段本身也是一个结构体(见ddraw.h中的定义):typedef struct _DDSCAPS2 DWORD dwCaps; / capabilities of surface wanted DWORD dwCaps2; DWORD dwCaps3; union DWORD dwCaps4; DWORD dwVolumeDepth; DUMMYUNIONNAMEN(1); DDSCAPS2;我们需要填充该结构体的dwCaps成员,描述需要创建的页面的属性。因为我们创建的页面是主页面,提供翻页支持,并且允许由多个页面复合组成页面链,所以我们填充为DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX 。我们在换页链中的后台页面只要一个,所以将dwBackBufferCount 设置为1就可以了。完成了这个比较麻烦的结构体的填充后,我们就可以创建所指定特性的页面了。但我们要首先通过LPDIRECTDRAWSURFACE7 p_FrontSurface;语句定义一个可以指向页面的指针。然后我们使用下面的代码来创建我们的主页面:if(p_DDraw->CreateSurface(&frontSurfaceDes,&p_FrontSurface,NULL) != DD_OK) return FALSE;一个DirectDraw对象只能有一个主页面,我们使用DirectDraw对象的IDirectDraw接口中的CreateSurface方法来创建页面。该方法的第一个参数是我们刚才填充的那个结构体的地址 &frontSurfaceDes ;第二个参数是页面指针的地址(即指向指针的指针)&p_FrontSurface ; 如果创建页面对象成功,则该方法将让该页面指针指向所创建的页面对象。最后一个参数是设计来用于以后扩展使用的,现在没有任何作用,我们使用NULL就可 以了。OK,我们现在已经成功创建了主页面。但我们在主页面中声明了,我们将有一个后台页面与主页面组成换页链。下面我们来创建后台页面。创建后台页面业需要填充结构体来说明所创建的页面特性,用于后台页面属性说明的是DDSCAPS2结构体。在创建主页面时候,我们提到了该结构体, DDSURFACEDESC2 结构体的一个成员就是 DDSCAPS2 结构体。我们可以使用下面的代码来创建后台页面:DDSCAPS2 backSurfaceDes; ZeroMemory(&backSurfaceDes,sizeof(frontSurfaceDes); backSurfaceDes.dwCaps = DDSCAPS_BACKBUFFER; if(p_FrontSurface->GetAttachedSurface(&backSurfaceDes,&p_BackSurface) != DD_OK) return FALSE; 第一条语句生成了一个DDSCAPS2 类型的结构体变量第二条语句将该变量内部的数据全部清0。我们需要填写的只是DDSCAPS2 结构体变量中的dwCaps字段,因为我们要创建的是后台页面所以我们用DDSCAPS_BACKBUFFER设置该字段。因为后台页面是隶属于主页面的,所以可以用IDirectDrawSurface接口中的GetAttachedSurface方法来创建后台页面,并与主页面组成换页链。该方法的第一个参数是我们刚刚填写的这个结构体的地址,第二个参数是所创建的后台页面指针的地址(又是指向指针的指针,这次大家应该都很清楚了吧)。后台页面指针的产生与主页面指针产生是完全一样的,语句LPDIRECTDRAWSURFACE7 p_BackSurface将产生这样的指针。如果该方法执行成功,生成了后台页面,则将我设置该后台指针p_BackSurface指针指向该页面对象。到现在为止,我们已经完成了DirectDraw组件初始化工作。下面我们要做的是将使用GDI在后台页面进行绘图操作,使用GDI绘图必须要得到一个HDC,我们可以使用IDirectDrawSurface接口中提供的GetDC方法来得到HDC,然后使用该HDC进行绘图。如下面的代码所示:if(p_BackSurface->GetDC(&hdc) != DD_OK) return FALSE;得 到了HDC后,我们就可以使用GDI绘图了,我们可以像是在传统的窗口上绘图一样来使用该HDC绘图,不同的是现在所进行的绘图操作结果不直接显示在屏幕 上,而是作用在当前暂时不可见的后台页面上。想看到绘图的结果,必须进行换页操作,将后台页面换为主页面时,绘制的结果才可以见到。我们使用红色画刷将后 台页面涂为红色,具体的代码如下: RECT clientRect2; GetClientRect(mainWindow,&clientRect2); HBRUSH myBrush2 = CreateSolidBrush(RGB(0,255,0); FillRect(hdc,&clientRect2,myBrush2); p_BackSurface->ReleaseDC(hdc);我们注意到最后一条语句调用了IDirectDrawSurface4接口提供的ReleaseDC方 法来释放所获得的HDC,我们在得到HDC完成绘图工作以后要记得使用该方法来进行释放(上了厕所后,要记得冲水哦_)。为了让我们看到绘图产生得结 果,我们可以设置一个定时器(如果你不清楚定时器的使用赶紧去看看Windows程序设计吧),每隔一定时间(比如500ms),产生一个 WM_TIMER消息,在定时器消息的处理部分进行换页操作。因为主页面没有进行绘图,所以将是初始的全白色的状态。每次进行换页操作将使主页面和后台页 面交换,这样我们将会看到屏幕周期性的全白和全红。下面的语句实现了换页:p_FrontSurface->Flip(NULL,DDFLIP_WAIT);该 方法的第一个参数是指定将哪个后台页面换到前台,如果使用NULL,则表示按照后台页面创建的先后顺序从换页链中取出最前面的后台页面与主页面进行换页。 换页以后,以前的主页面,将自动成为后台页面,并排在换页链的最后面。第二个参数指定进行换页操作时的参数,我们一般使用DDFLIP_WAIT,表示如果换页操作没有成果则将继续重复尝试进行该换页操作。虽 然在说法上叫做换页,事实上页面的位置并没有任何的改变,DirectDraw所进行操作仅仅是改变了页面指针的指向(而没有实际上去移动页面),当主页 面指针指向某个页面时,这个页面就是主页面,显示器上将显示该页面的内容,所以换页操作的效率是非常高的。换页这种说法只是逻辑上的描述,这种逻辑关系如 下图所示:哇,这次笔记的内容终于完成了。没想到居然写了这么长,我的老师给了我不少资料要看,还真有得忙的。但笔记当然会继续的啥,下次的笔记内容将会更有趣的。篮球世竞赛的半决赛,美国输给了希腊被淘汰掉了,欧洲的篮球水平真是提高很快呀,NBA的国际化也为欧洲培养了很多优秀球员,我想美国人对此也很矛盾吧。NBA集中全球最优秀的篮球运动员的同时,也在培养自己的敌人_!。 2D游戏编程笔记-4 本人同意他人对我的文章引用,但请在引用时注明出处,谢谢作者:蒋志强 对Windows平台来说,位图是一种非常特殊的图像格式,位图是windows操作系统直接从底层所支持的图像格式。位图分为DIB(设备无关位图)和DDB(设备相关位图两类),这两种位图彼此是有关系的,DIB和DDB之间是可以相互转换的。DDB(即设备相关位图)是windows的GDI对象的一种,他所表示的图像与具体的显示图像的系统相关,也就是说一样的DDB在不同的显示设备上显示的效果可能是不一样的,所以DDB是不用于存储的。我们常见的以文件格式保存的位图,也就是那些以bmp或dip作为后缀名的文件是设备无关位图。DIB之所以称为设备无关位图,就是因为它记录了图像的所有信息,在不同的设备上显示都会是一样的。在具体的编程中,我们有时需要将以文件格式(bmp或dip为后缀名的文件)的位图在程序运行过程中,进行载入并使用载入的位图。在这个时候,从DIB到DDB的转换就发生了。在windows平台下,我们必须使用HBITMAP这种被称为位图句柄的变量来进行引用和操作GDI位图对象,HBITMAP类型变量所引用的位图就是DDB,它与具体的显示设备相关,在具体需要显示或操作的时候DDB产生。我们可以使用LoadImage函数从文件中装载DIB,该函数返回DDB位图的句柄,从DIB到DDB的转换在调用该函数时发生。这个转换过程不用我们操心,LoadBitmap函数返回的也是DDB位图的句柄。如果你还没有搞明白DIB和DDB的区别,你可以参考windows程序设计的相应章节。我们要在程序中使用位图有两种方式:1从BMP文件中加载;2从程序的资源中加载(如果你还不太理解程序资源的相关概念,请参考windows程序设计的第十章)。LoadImage函数可以从指定目录加载位图文件,返回DDB位图句柄;该函数也可以从程序资源中加载位图返回DDB位图句柄,LoadBitmap只能从程序资源中加载位图返回DDB位图句柄。下面的代码将程序资源中的位图加载,把返回的DDB句柄赋给了一个HBITMAP变量: if(bitmap = LoadBitmap(GetModuleHandle(NULL),MAKEINTRESOURCE(IDB_NPC) = NULL) return FALSE;下面的代码将指定位图文件(颜色选择32.bmp)加载到程序中,并把返回的DDB句柄赋给了一个HBITMAP变量:if(bitmap = (HBITMAP)LoadImage(NULL,TEXT("test颜色选择32.bmp"),IMAGE_BITMAP,0,0,LR_LOADFROMFILE) = NULL) return FALSE;具体函数各个参数的意义,请查阅MSDN文档说明。当我们将位图转载以后(无论你是从文件中装载还是从程序的资源中装载),也就是我们获得一个DDB句柄以后,我们就可以做下面的事情了:将该位图放到我们离屏页面中去,下面的代码完成这样的工作: BITMAP bitmapInfor; GetObject(bitmap,sizeof(BITMAP),&bitmapInfor); if(p_PicResourceSurface->GetDC(&hdc) != DD_OK) return FALSE;HDC compataleDC = CreateCompatibleDC(hdc); SelectObject(compataleDC,bitmap); DDCOLORKEY picColorKey; picColorKey.dwColorSpaceLowValue = picColorKey.dwColorSpaceHighValue = RGB(255,0,255); if(p_PicResourceSurface->SetColorKey(DDCKEY_SRCBLT ,&picColorKey) != DD_OK) return FALSE; BitBlt(hdc,0,0,bitmapInfor.bmWidth,bitmapInfor.bmHeight,compataleDC,0,0,SRCCOPY); p_PicResourceSurface->ReleaseDC(hdc);上面的代码中的BITMAP是一个用于描述DDB位图属性的结构体,GetObject函数根据bitmap这个我们在上面载入的DDB位图的属性填充该结构体。GetDC是IDirectDrawSurface接口中的方法,用于获得页面的DC,要注意的是该方法内部调用了Lock方法,将该页面内存区域锁定,在锁定期间从锁定页面往其他地方Blit将失效(刚才我就是由于这个原因程序老是不能正常运行,大家要注意)。然后我们创建一个与从离屏页面所得到的DC相兼容的DC,并将得到的DDB位图bitmap选入该兼容DC中。然后我们使用GDI的BitBlt函数,将兼容DC中的内容传送到离屏页面的DC中,这样就完成了将DDB位图装入离屏页面的操作。要特别注意,一定要使用ReleaseDC释放,我们用GetDC所得到的离屏页面的DC,因为在GetDC时,页面将会被锁定,调用ReleaseDC将对所锁定的页面解锁。你肯定会注意到上面SetColorKey的代码,这是设置页面的关键色。因为图片资源都是矩形的,但是我们在程序中往往不想显示整个矩形的内容方框,而只是显示矩形图片中踢出背景颜色后的内容。这个时候我们设定页面的关键色,然后从该页面往其他页面(比如后台页面)进行图像Blit的时候,就可以指定关键色不进行传送。关键色可以不只一种(虽然我们平时一般都只指定一种关键色),在关键色DDCOLORKEY结构体的LowValue和HighValue之间的所有颜色都是关键色(RGB宏将颜色转换为一个整型数值)现在我们可以用BitBlt函数完成将兼容DC中的内容传送到主页面DC中,在主页面的内容就会马上显示在屏幕上.很令人兴奋吧,忙了半天,终于在屏幕上显示出内容了!上面将图像装载到主页面的方法对后台页面同样适用,操作代码完全是一样的,我就不在重复了.当后台页面装载图像后,通过换页操作,主页面与后台页面交换,以前的主页面变成现在后台页面,以前的后台页面变成现在的主页面,这样后台页面上的内容就变成主页面内容显示在了屏幕上了.当使用上面的方法把图片装载到页面后,就可以先在后台页面把绘图操作完成以后在进行页面的Flip,使用Flip函 数将使以前的主页面变为后台页面,以前的后台页面变为当前的主页面,因为主页面上面的内容将会直接显示到屏幕上,这样之前在后台页面绘制的内容将在屏幕上 显示出来。而且由于绘图是在后台页面上操作的,所以绘图的中间过程不会显示出来,程序运行时,也就不会有闪烁现象了。该函数的原型如下:HRESULT Flip( LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride, DWORD dwFlags );在代码中直接使用p_FrontSurface>Flip(NULL,0),就可以实现上面的操作了。我们需要注意的是,DDraw程序独占显示设备时,如果跳出该程序,比如按下alt+tab切换到其它程序时,或者最小化当前Ddraw程序时,DDraw程序将失去对显示设备的独占,所以这个时候Flip操作肯定会出丢失页面的错,丢失页面后,以前页面中装载的图象都会丢失需要重新装入,所以我们一定要处理这个情况。我们可以在代码中这样进行处理:HRESULT ddrval;ddrval=lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);if (ddrval=DDERR_SURFACELOST)lpDD->RestoreAllSurfaces( );ReloadResourceImages( ); /自定义的函数,将资源图片装载到页面中 这次笔记至此讲解了Ddraw编程的最基础的知识,在后续的笔记中我将实现一个DDraw的俄罗斯方块的游戏。很吸引人吧,下次再见哦_