DirectX 9.0游戏开发简明教程

暑期特刊

  DirectX开发工具包是微软公司提供的一套Windows系统平台下开发高性能图形、声音、输入输出和网络游戏的接口,其高效的直接硬件访问、程序与硬件设备之间的相对独立等特性,几乎使DirectX成为唯一可以在Windows系统下开发游戏程序的基本工具软件。

  本文结合VC++ 6.0和DirectX 9.0向你详细介绍DirectX的编程技术。

  一、什么是DirectX

  (一)什么是DirectX

  DirectX是一个用于多媒体应用程序和硬件增强的编程环境,是微软为了将Windows建设成适应各种多媒体的最好平台而开发设计的。DirectX实际上是一个API(Application Programming Interface,应用程序接口),是一座连接硬件、程序员和软件用户的桥梁。每个DirectX部件都是用户可调用的API的总和,通过它应用程序可以直接访问计算机的硬件。这样,应用程序就可以利用硬件加速器(Hardware Accelerator)。如果硬件加速器不能使用,DirectX还可以仿真加速器以提供强大的多媒体环境。

  为了理解DirectX,我们可以把系统分为4层:

  (1)硬件/网络层:放置有多媒体设备,包括图形加速器、声卡、输入设备以及网络通信设备等。

  (2)DirectX基础层:为图像、声音和设备提供多媒体基本服务。

  (3)DirectX媒体层:为动画制作、音频和视频等提供API功能。

  (4)组件层:包括ActiveX控制和应用,它利用DirectX的API功能的优势为用户提供多媒体服务。

  微软开发了DirectX标准平台,并且根据硬件制造厂商和游戏厂商合作共同更新升级DirectX的标准。硬件制造商按照此标准研发制造更好的产品,游戏程序员根据这套标准开发游戏。也就是说,无论硬件是否支持某特殊效果,只要DirectX标准中有,写游戏的程序员就可以把它写到游戏中,当这个游戏在硬件上运行,如果此硬件根据DirectX标准把这个效果做到了此硬件驱动程序中,驱动程序驾驭其硬件算出此效果,用户就可以欣赏到此效果。这就是“硬件设备无关性”,是DirectX真正意义所在。

  (二)DirextX的组成

  (1)DirectDraw:通过直接访问显示硬件,提供高级的图像处理能力。

  (2)Direct3D:为计算机和互联网用户提供了实时、交互的3D技术。

  (3)DirectSound:提供了软、硬件的低延迟声音混频、回放、硬件加速,及直接访问音频设备的功能。

  (4)DirectMusic:用于处理数字音频及基于消息的音乐数据,且数据需要通过声卡或其内置软件合成器转换为数字音频。

  (5)DirectInput:简化应用程序访问鼠标、键盘和操纵杆设备的操作。

  (6)DirectPlay:提供通用环境连接能力,简化应用程序之间的通讯服务。

  (7)DirectSetup:提供一套简单的API函数,帮助开发者编制安装DirectX部件的程序。

  (8)DirectShow:使开发者可在网络中传递高质量的音频和视频信号,其支持各类流行的媒体格式,包括AVI、MIDI、MPEG和MOV等。

  (9)DirectAnimation:从DirectDraw、Direct3D和DirectSound中派生,主要用于将多种媒体(如文本、2D和3D图像、声音等)集成至一个线性格式以实现动画制作的功能。

  二、安装DirectX 9.0

  (一)安装SDK

  DirectX的SDK(Software Development Kit,软件开发工具包)可以在某些三维游戏的光盘中找到,也可直接在微软站点下载。直接运行SDK安装程序中的Setup.exe文件即可开始安装,由于其采用了标准的Windows安装界面(图1),安装相对简单,此处不再赘述。

  需要注意的是,在“安装类别选择(DirectX Runtime Support)”界面中会有2个单选项供我们选择。其中“Retail(零售)”选项,建议仅用于单独运行游戏的用户选择,该安装将不会帮助开发者在编译过程中发现错误。“Debug(调试)”选项,建议专业开发者使用,该安装运行虽然较慢,但可帮助开发者调试程序。

  (二)复制文件

  为确保VC++可以调用SDK中的头文件和库文件,我们还需要为其设置路径。首先将“DXSDK”目录的“INCLUDE”子目录中所有“H”文件复制到VC++的“INCLUDE” 目录中并覆盖原有的文件,然后将“DXSDK”目录的“LIB”子目录中的所有“LIB”文件复制到VC++的“LIB”目录中并覆盖原有的文件。

  (三)编译时加入链接

  由于VC++默认的链接库中并不包含DirectX的库,因此在使用DirectDraw、DirectSound等接口时,会出现编译时链接错误,解决该问题必须添加链接。在VC++的LIB设置中添加“DXGUID.LIB”(所有DirectX程序均需使用)、“DDRAW.LIB”(DirectDRAW编译时必需)、“DirectSOUND.LIB”(DirectSOUND编译时必需)文件,且文件名用空格分隔(图2)。

  (四)添加头文件

  在程序编制中,如未添加诸如“#include ”的代码,那么编译时会对引用的DirectX成员显示未命名错误。对于不常用类的头文件,如DirectSound、DirectMusic和DirectPlay等,可使用系统的文件查找功能,在VC++的“INCLUDE”目录中查找所有包含未命名错误文字的文件,即可确定需使用的头文件。

  三、详解DirectX 9.0组件函数

  如前所述,DirectX 9.0包含了众多组件,这里,我们将针对这些组件进行介绍,并给出相关的示例核心代码。

  (一)DirectDraw和基本图形概念

  在DirectDraw组件编程中,有很多常用的图形概念,为确保后续部分的学习,先介绍一部分常用的概念。

  (1)像素和分辨率:像素指显示器在某种分辨率状态下所能表达图像的最小单位。像素大小和当前的显示分辨率有关。一个像素由若干扫描点组成,在某种分辨率下,显示器的水平或垂直像素个数,实际等于一次水平或垂直扫描期间,电子束通短强弱状态可发生变化的次数。

  (2)RGB色彩:现实生活中的所有颜色均可用红、绿、蓝三原色合成,称为RGB色彩模式。DirectDraw编程也采用了标准的24位RGB模式定义一个颜色。当三原色的值全设置为0时,像素显示为黑,当全部置为255时,像素显示为白。

  (3)设备无关位图:位图图像是数据元素的集合,这些数据元素决定了图片某个具体位置的颜色。DirectX中均使用设备无关位图作为最基本的图像文件格式。在DirectDraw编程中所指的图像一般均指位图图像。

  (4)位深度:位深度指用于描述某状态值所使用的位数。在DirectDraw中通常用位深度来代表位图中的颜色值所使用的位数,同时代表了位图中颜色的丰富程度。图像的位深度越高,那么所能表现的颜色也越多,色彩也越丰富。

  (5)抖动处理:低位深度图像中由于颜色总数限制,有些颜色无法显示,为模拟那些颜色以提高显示效果,抖动处理技术得到了广泛的使用,该技术用交替的点图案去模拟在图像中无法使用的颜色。

  (6)调色板:为降低程序对显存的需求,Windows中使用一种间接的色彩表示方法,即调色板表示法。其使用一个颜色索引来代表各个像素点的颜色,而不直接用红、绿、蓝三原色的亮度值来确定。使用调色板的好处在于索引值占用较少的数据位,而真实颜色值占用较长的数据位,因此既提高了图像显示效率,又减少了对显存的需求。

  (7)直接控制显存:DirectDraw为开发者提供了真实显示内存的绘图页面,即只需使用DirectDraw,即可直接控制显内存来快速显示图形,且页面代表了显存中连续的内存块,使在页面中寻址和读写变得非常方便。

  (8)翻页:在多媒体动画和动感游戏中,翻页是一个关键的概念。DirectDraw中的翻页过程由一组DirectDraw页面组成,每个页面均可被轮流翻页至屏幕显示。当前显示的页面称为主页面,其后等待翻页的页面称为后台缓存。应用程序在后台缓存上进行绘图操作,然后翻页并将该过程一直持续直至动画结束。

  (9)矩形:Windows编程中的屏幕对象均以封闭矩形的形状出现,由于一个封闭的矩形可用两个坐标点(左上角和右下角)来确定,因此大部分程序使用RECT结构来定义封闭矩形,VC++中的RECT结构定义如下所示:

  typedef struct tagRECT {

  LONG left; //矩形左上角的X坐标

  LONG top; //矩形左上角的Y坐标

  LONG right; //矩形右下角的X坐标

  LONG bottom; //矩形右下角的Y坐标

  } RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

  注意:该矩形区域排除了边线,即不包括右边线和下边线。因此该矩形的宽度应等于“right-left”,而非“right-left+1”,此外矩形的高度等于“bottom-top”。

  (10)精灵动画:精灵动画广泛用于多媒体及游戏软件中,从最基本意义分析,精灵是一个可在背景屏幕上四处移动的图像,且通常图像形状不规则。其实现方法首先将精灵绘制于可见的背景页面,然后将上一个精灵从页面抹去并将精灵画在页面的下一个位置并依次类推,最终使精灵在屏幕上移动。由于精灵动画的图像形状通常不规则,因此诞生了掩码技术,即精灵图和屏蔽图(Mask,掩码图)共同使用,保持精灵图中需显示部分的颜色不变,而在屏蔽图中屏蔽精灵的颜色。

  (11)范围检查和碰撞检测:范围检查和碰撞检测是编程中与精灵动画相关联的两个重要任务。范围检查用于限制精灵的活动范围,但DirectDraw并未提供范围检查的服务函数,但可通过简单的坐标计算来实现。碰撞检测又称冲突检测,用于判断某一时刻是否有多个精灵处于同一位置,DirectDraw也未提供相关的服务函数,开发者可按需编制。

  (二)DirectDraw相关函数

  1.接口创建函数

  DirectDraw接口即代表了DirectDraw本身,该接口在用于创建其他DirectDraw接口实例时是一个主接口。其提供的接口创建函数具体如下所述:

  (1)CreateClipper函数:该函数用于创建DirectDrawClipper接口实例,其在VC++中的声明如下所示:

  HRESULT CreateClipper(

  DWORD dwFlags,

  LPDIRECTDRAWCLIPPER FAR *lplpDDClipper,

  IUnknown FAR *pUnkOuter

  );

  该函数的参数包括dwFlags表示目前未被使用,但需设置为0;LplpDDClipper表示如函数调用成功,该参数将接收到指向新创建的DirectDrawClipper对象的指针地址;PUnkOuter表示该参数将允许和COM集合特性相兼容。

  如函数调用成功,则返回DD_OK。如函数调用失败,则返回DDERR_INVALIDOBJECT、DDERR_INVALIDPARAMS、DDERR_NOCOOPERATIVELEVELSET、DDERR_OUTOFMEMORY常量中的一个错误值。

  (2)CreatePalette函数:该函数用于创建DirectDrawPalette接口实例,其在VC++中的声明如下所示:

  HRESULT CreatePalette(

  DWORD dwFlags,

  LPPALETTEENTRY lpColorTable,

  LPDIRECTDRAWPALETTE FAR *lplpDDPalette,

  IUnknown FAR *pUnkOuter

  );

  该函数的参数包括dwFlags,表示该参数可使用DDPCAPS_1BIT、DDPCAPS_2BIT、DDPCAPS_4BIT、DDPCAPS_8BIT、DDPCAPS_8BITENTRIES和DDPCAPS_ALLOW256常量的组合;lpColorTable表示该参数用于设置2、4、16或256个PALETTEENTRY结构序列的地址,用于初始化DirectDrawPalette对象;lplpDDPalette表示如函数调用成功,该参数将接收到指向新创建的DirectDrawPalette对象的指针地址;pUnkOuter表示该参数将和COM集合特性相兼容。

  如函数调用成功,则返回DD_OK。如函数调用失败,则返回DDERR_INVALIDOBJECT、DDERR_INVALIDPARAMS、DDERR_NOCOOPERATIVELEVELSET、DDERR_OUTOFMEMORY和DDERR_UNSUPPORTED常量中的一个错误值。

  (3)CreateSurface函数:该函数用于创建DirectDrawSurface接口实例,其在VC++中的声明如下所示:

  HRESULT CreateSurface(

  LPDDSURFACEDESC lpDDSurfaceDesc,

  LPDIRECTDRAWSURFACE FAR *lplpDDSurface,

  IUnknown FAR *pUnkOuter

  );

  该函数的参数包括lpDDSurfaceDesc,表示一个DDSURFACEDESC结构的地址,用于描述所请求创建的页面的页面信息。在调用该函数前应设置DDSURFACEDESC结构所有成员的值为0;lplpDDSurface表示如该函数调用成功,该参数将接收到指向新创建的DirectDrawsurface对象的指针地址;pUnkOuter表示该参数将和COM集合特性相兼容。

  如函数调用成功,则返回DD_OK。

  2.获取软硬件支持函数

  DirectDraw接口允许准确确定软硬件都支持的特征,GetCaps函数可对两个DDCAP结构实例进行初始化。其中一个结构用于表示显示硬件直接支持的特征,另一个结构用于表示由软件仿真支持的特征,其在VC++中的声明如下所示:

  HRESULT GetCaps(

  LPDDCAPS lpDDDriverCaps,

  LPDDCAPS lpDDHELCaps

  );

  该函数的参数包括lpDDDriverCaps,表示该参数代表被填充设备驱动所报告的描述硬件性能的DDCAPS结构地址。如不需获取该描述,可设置为NULL;lpDDHELCaps表示该参数代表被填充描述硬件仿真层性能的DDCAPS结构地址。如不需获取该描述,可设置为NULL。

  如函数调用成功,则返回DD_OK。如函数调用失败,则返回DDERR_INVALIDOBJECT、DDERR_INVALIDPARAMS常量中的一个错误值。

  3.设置硬件控制程度函数

  SetCooperativeLevel函数用于设置应用程序对显示硬件的控制程度。如正常合作度意味着应用程序既无法改变当前显示模式,又无法设置整个系统调色板内容。

  4.显示模式函数

  (1)EnumDisplayModes函数:该函数用于查询DirectDraw使用的显示模式,通过设置参数的默认值可获取所有的显示模式,并可通过显示模式的描述来过滤那些不需使用的模式。

  (2)GetDisplayMode函数:该函数可检索到当前显示模式信息,并在DDSURFACEDESC结构实例中显示当前显示模式的宽度、高度、像素深度及像素格式。

  (3)SetDisplayMode函数:该函数用于激活所支持的显示模式。DirectDraw接口的SetDisplayMode函数仅可设置显示模式宽度、高度和像素深度。

  (4)RestoreDisplayMode函数:该函数用于存储调用SetDisplayMode函数之前的显示模式。

  5.表面支持函数

  (1)DuplicateSurface函数:该函数用于复制当前显示表面,且仅复制表面接口而不复制内存。被复制的表面和源表面共享内存,因此改变内存内容时即改变了两个表面的图像。

  (2)EnumSurfaces函数:该函数用于叠代所有满足指定标准的表面,如未指定标准,那么所有当前表面均会被枚举。

  (3)FlipToGDISurface函数:该函数用于终止页面翻转应用程序前确保主表面得以正确存储。

  (4)GetGDISurface函数:该函数向Windows用于输出的表面返回指针。在进行屏幕捕捉时该函数非常有用,可捕捉到Windows桌面的任一部分。

  (5)GetAvailableVidMem函数:该函数用于检索正在使用中的视频存储器数量,由DirectDraw2接口提供,并可确定应用程序利用显示RAM可创建的表面数量。

  6.监视器刷新函数

  监视器刷新函数和监视器的刷新机制密切相关,确保了生成动画时尽可能不产生闪烁和图像撕裂,但需注意:并非所有显示卡或监视器组合均支持这些函数。

  (1)GetMonitorFrequency函数:该函数用于检索监视器当前的刷新率。

  (2)GetScanLine函数:该函数用于向监视器返回当前正在被刷新的扫描行,如该功能不支持,函数将返回DDERR-UNSUPPORTED。高性能图形应用程序通常要求利用垂直刷新来同步更新屏幕。尤其在显示器刚完成屏幕刷新时必须避免图像撕裂,因此DirectDraw默认利用垂直刷新同步更新屏幕。

  7.返回FourCC代码函数

  GetFourCCCodes函数用于返回显卡所支持的用于描述非RGB或YUV表面(FourCC)代码。

  (三)Direct3D相关函数

  1.枚举Direct3D设备函数

  在开发中应用程序需对所使用的硬件进行查询以确定其所支持的Direct3D设备。EnumDevice函数用于枚举所有硬件可支持的Direct3D设备,该函数使用D3DenumDevicesCallback函数来选择需使用的设备。

  (1)EnumDevices函数:该函数用于枚举所有可支持Direct3D的硬件设备,其在VC++中的声明如下所示:

  HRESULT EnumDevices(DWORD dwDevType,

    LPDIENUMDEVICESCALLBACK lpCallback,

    LPVOID pvRef,

    DWORD dwFlags

  );

  该函数的参数包括:

  A.dwDevType:限制设备枚举范围,可取DI8DEVCLASS_ALL、DI8DEVCLASS_DEVICE、DI8DEVCLASS_GAMECTRL、DI8DEVCLASS_KEYBOARD、DI8DEVCLASS_POINTER常量中的一个值。

  B.lpCallback:回调函数调用的地址。

  C.pvRef:回调函数调用的参数。

  D.dwFlags:指定枚举范围,可在DIEDFL_ALLDEVICES、DIEDFL_ATTACHEDONLY、DIEDFL_FORCEFEEDBACK、DIEDFL_INCLUDEALIASES、DIEDFL_INCLUDEHIDDEN、DIEDFL_INCLUDEPHANTOMS常量中多选。

  如函数调用成功,则返回DI_OK。如函数调用失败,则返回DIERR_INVALIDPARAM 、DIERR_NOTINITIALIZED常量中的一个错误值。

  (2)枚举Direct3D设备:枚举和选择Direct3D设备过程的核心代码如下所示:

  BOOL fDeviceFound = FALSE;

  hRes = lpd3d->EnumDevices(EnumDeviceCallback, &fDeviceFound);

  if (FAILED(hRes))

  {

   //错误处理代码

  }

  if (!fDeviceFound)

  {

   //错误处理代码

  }

  在上述实例中设备枚举回调函数被命名为“EnumDeviceCallback”,一个指向该函数的的指针被传递至EnumDevices方法。

  D3DenumDevicesCallback函数被每一个安装在系统中的Direct3D设备所引用,当其被调用时,第一个参数将作为正在被枚举设备的GUID。第二和第三个参数为字符串,包含了设备名称和相关描述。第四个参数指向一个D3DDEVICEDESC结构体,包含了设备硬件性能相关信息。第五个参数包含一个指向D3DDEVICEDESC结构体的指针,用于描述所使用机器的软件仿真性能。

  2.创建Direct3D设备函数

  创建Direct3D设备时,必须先设置Direct3D程序使用执行缓冲或是DrawPrimitive方法,并决定了创建设备时程序需获取的接口指针类型。如选择使用执行缓冲,那么程序必须获取指向IDirect3Ddevice接口的指针。如选择DrawPrimitive方法,程序必须获取指向IDirect3DDevice3接口的指针。

  DrawPrimitive函数可获取创建设备时的接口指针类型,其在VC++中的声明如下所示:

  HRESULT DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType,

    UINT StartVertex,

    UINT PrimitiveCount

  );

  该函数的参数包括PrimitiveType表示设置描述指向IDirect3DDevice3接口指针的类型,可取D3DPT_LINELIST、D3DPT_LINESTRIP、D3DPT_TRIANGLELIST、D3DPT_TRIANGLESTRIP、D3DPT_TRIANGLEFAN、D3DPT_FORCE_DWORD常量中的的一个;StartVertex表示设置索引顶点值;PrimitiveCount表示设置获取接口的数量。

  如函数调用成功,则返回D3D_OK。如函数调用失败,则返回D3DERR_INVALIDCALL错误值。

  此外使用DrawPrimitive方法时,程序必须先在常规方式中初始化DirectDraw对象,并获取指向IDirect3D3的接口,然后调用CreateDevice方法创建Direct3D设备,并传递一个指向IDirect3DDevice3接口的指针。

  当使用硬件加速渲染设备时所使用的渲染目标表面必须在显存中创建,即通过DDSCAPS_VIDEOMEMORY标志来创建,否则需在系统内存中创建,即通过DDSCAPS_SYSTEMMEMORY标志来创建。

  3.设置变换函数

  SetTransform函数可设置应用变换,其在VC++中的声明如下所示:

  HRESULT SetTransform(D3DTRANSFORMSTATETYPE State,

    CONST D3DMATRIX *pMatrix

  );

  该函数的参数包括State表示设置变换状态,可取D3DTRANSFORMSTATE_WORLD, D3DTRANSFORMSTATE_VIEW 和D3DTRANSFORMSTATE_PROJECTION三个值;pMatrix表示用于将修改后的变换存入结构指针。

  如函数调用成功,则返回D3D_OK。如函数调用失败,则返回D3DERR_INVALIDCALL错误值。

  (四)DirectSound相关函数

  DirectSound的功能包括播放、声音缓冲区、三维音效和音频捕捉等。DirectSound构建于IDirectSound的COM接口中,而IDirectSoundBuffer,IDirectSound3DBuffer和IDirectSound3DListener接口则可实现对声音缓冲区和三维音效的操作。DirectSound音频捕捉构建于IDirectSoundCapture和IdirectSoundCaptureBuffer的COM接口中。

  1.初始化

  对于一些简单的操作,可使用缺省的首选设备,但为适用于更多的硬件设备,应首先列举可用的声音设备,即设置一个回调函数,在每次DirectSound发现新设备后调用该函数。

  (1)DSEnumCallback函数:该函数在VC++中的声明如下所示:

  BOOL CALLBACK DSEnumCallback(

   LPGUID lpGuid,  

   LPCSTR lpcstrDescription, 

   LPCSTR lpcstrModule, 

   LPVOID lpContext  

  );

  该函数的参数包括lpGuid表示为枚举的硬件设备赋予GUID地址,设备空则代表默认首选设备;lpcstrDescription表示DirectSound设备的字符串描述;lpcstrModule表示DirectSound设备相应的驱动描述;lpContext可选用DirectSoundEnumerate或DirectSoundCaptureEnumerate常量。

  如函数调用成功,则返回TRUE继续枚举,如返回FALSE则停止枚举。

  2.播放

  初始化完毕后,DirectSound将自动创建用于混音并传送至输出设备的主缓冲区,而副缓冲区则需开发者自行创建。

  用CreateSoundBuffer函数创建基本副缓冲区,该函数在VC++中的声明如下所示:

  HRESULT CreateSoundBuffer(

   LPCDSBUFFERDESC pcDSBufferDesc,

   LPDIRECTSOUNDBUFFER * ppDSBuffer,

   LPUNKNOWN pUnkOuter

  );

  该函数的参数包括pcDSBufferDesc表示Sound的缓冲地址;ppDSBuffer表示缓冲接口;pUnkOuter表示IUnknown类型的COM接口。

  如函数调用成功,则返回DS_OK或DS_NO_VIRTUALIZATION。如函数调用失败,则返回DSERR_ALLOCATED、DSERR_BADFORMAT、DSERR_BUFFERTOOSMALL、DSERR_CONTROLUNAVAIL、DSERR_DS8_REQUIRED、DSERR_INVALIDCALL、DSERR_INVALIDPARAM、DSERR_NOAGGREGATION、DSERR_OUTOFMEMORY、DSERR_UNINITIALIZED、DSERR_UNSUPPORTED常量中的一个错误值。

  通常使用DirectSound播放声音过程的主要步骤如下所述:

  (1)锁定:通过Lock方法锁定副缓冲区的一部分,并由所设置的偏移量来决定下一步写操作的起始点。

  (2)写数据。

  (3)解锁:通过Unlock方法实现副缓冲区锁定部分的解锁。

  (4)将声音传送至主缓冲区:将声音传送至主缓冲区后,即可通过Play方法来实现输出。

  (五)DirectInput相关函数

  DirectInput的指针在整个应用程序中均会使用,而并不仅在鼠标类中,因此DirectInput的初始化应在主初始化函数中和DirectDraw,DirectSound对象的初始化同时完成。DirectInput指针和DirectInput device指针不同,DirectInput指针用于获取DirectInput device指针。

  我们可以利用DirectInput8Create函数创建DirectInput指针,在VC++中的声明如下所示:

  HRESULT WINAPI DirectInput8Create(HINSTANCE hinst,

    DWORD dwVersion,

    REFIID riidltf,

    LPVOID *ppvOut,

    LPUNKNOWN punkOuter

  );

  该函数的参数包括hinst表示创建DLL句柄;dwVersion表示DirectInput版本;riidltf表示唯一的接口序号;ppvOut表示如调用成功,则返回接口指针地址;punkOuter表示如调用成功,则返回Iunknown类型的COM接口地址。

  如函数调用成功,则返回DI_OK。如函数调用失败,则返回DIERR_BETADIRECTINPUTVERSION、DIERR_INVALIDPARAM、DIERR_OLDDIRECTINPUTVERSION、DIERR_OUTOFMEMORY常量中的一个错误值。

  初始化主DirectInput接口指针的核心示例代码如下所示:

  LPDIRECTINPUT di = NULL;

  hr = DirectInput8Create(hinst, DIRECTINPUT_VERSION, &di, NULL);

  if (FAILED(hr)) {

  //错误

  handle_error ();

  }

  //获取DirectInput接口

  bool CMouse::Init(LPDIRECTINPUT di)

  {

  //获取系统鼠标接口

  hr = di->CreateDevice(GUID_SysMouse, (LPDIRECTINPUTDEVICE*)&m_mousedev, NULL);

  if (FAILED(hr))

  //设置数据格式为“mouse format”

  hr = m_mousedev->SetDataFormat(&c_dfDIMouse);

  if (FAILED(hr))

  //设置协作层

  hr = m_mousedev->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);

  if (FAILED(hr))

  }

  上述代码实现获取了有效的DirectInput鼠标设备接口,随后设置数据格式和设备协作层,即不采用独占的方式使用该设备。

  四、DirectDraw实例编程

  本文结合一个简单的DirectDraw编程实例来巩固学习的效果。首先在VC++中新建一个Win32环境应用程序,然后新建一个C++文件添加至工程中。单击“File”菜单的“New”子菜单,在对话框中选择“Files”栏目(新建文件),并选择“C++ Source File”项,最后选择“Add to project”复选框即可。

  (一)创建DirectDraw对象

  创建一个 DirectDraw 对象实例,首先应使用DirectDrawCreate函数,该函数包含三个参数,第一个参数用于获取代表显示设备的全局唯一标识符(GUID),该 GUID 通常设置为NULL,代表DirectDraw 使用系统缺省的显示驱动;第二个参数代表DirectDraw创建后的指针地址;第三个参数用于扩展之用,一般设置为NULL。

  创建DirectDraw对象并确定是否创建成功的核心代码如下所示:

  if (DirectDrawCreate(NULL,&lpDD,NULL)!=DD_OK) return FALSE;

  (二)确定应用程序的行为

  在改变显示分辨率前必须为SetCooperativeLevel函数的dwFlags参数指定DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN标志,以使应用程序能完全控制显示设备。此外,DDSCL_FULLSCREEN标志用于设置应用程序为独占(全屏)模式,以使应用程序覆盖整个桌面。

  设置为全屏模式的核心代码如下所示:

  if (lpDD->SetCooperativeLevel(GetActiveWindow(),DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN)!=DD_OK) return FALSE;

  (三)改变显示模式

  在设置应用程序的行为后,即可使用SetDisplayMode函数来改变显示分辨率,将显示模式设为800*600,256色的核心代码如下所示:

  if (lpDD->SetDisplayMode(800,600,256)!=DD_OK) return FALSE;

  (四)创建换页页面

  设置显示模式后,即可建页面来显示应用程序了,由于已使用了 SetCooperativeLevel函数设置为独占(全屏)模式,因此可很方便地建换页页面。如使用SetCooperativeLevel将模式设置为DDSCL_NORMAL,那么仅能创建位块传送的页面。

  定义页面参数的核心代码如下所示:

  ddsd.dwSize=sizeof(ddsd);

  ddsd.dwFlags=DDSD_CAPS;

  ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

  创建页面的核心代码如下所示:

  if (lpDD->CreateSurface(&ddsd,&lpDDSPrimary,NULL)!=DD_OK) return FALSE;

  (五)为页面着色

  在主页面和后台缓存创建后,即可通过标准的Windows GDI函数向主页面和后台缓存着色文本,核心代码如下所示:

  char str1[]="Hello. This is a DirectX.";

  char str2[]="按ESC键退出程序";

  if (lpDDSPrimary->GetDC(&hdc)!=DD_OK) return FALSE;

  //设置背景色

  SetBkColor(hdc,RGB(0,0,255));       

  //文字颜色

  SetTextColor(hdc,RGB(255,255,0));    

  //文字输出函数

  TextOut(hdc,250,250,str1,lstrlen(str1)); 

  TextOut(hdc,300,270,str2,lstrlen(str2));

  lpDDSPrimary->ReleaseDC(hdc);

  return TRUE;

  上述代码中首先用GetDC 函数获取设备描述句柄,同时内在锁定了页面。然后用SetBkColor函数来设置背景颜色,而SetTextColor函数由于选择背景上的文本颜色,此外TextOut函数将文本和背景颜色显示于页面。最后用ReleaseDC函数来解锁页面并释放句柄即可。

  (六)释放DirectDraw对象

  程序在退出前需调用UninitDDraw函数来释放DirectDraw对象,核心代码如下所示:

  void UninitDDraw(void)//卸载DirectDraw;

  {

    if(lpDD!=NULL)

    {

      if(lpDDSPrimary!=NULL)

      {

        lpDDSPrimary->Release();

        lpDDSPrimary=NULL;

      }

      lpDD->Release();

      lpDD=NULL;

    }

  }

  (七)全部程序源代码

  //DirectDraw所使用的头文件

  #include

  #include                 

  //变量、声明函数

  //DirectDraw对象

  LPDIRECTDRAW lpDD;                

  //DirectDraw主页面

  LPDIRECTDRAWSURFACE lpDDSPrimary;         

  char str1[]="Hello. This is a DirectX.";

  char str2[]="按ESC键退出程序";

  LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

  //创建主工作窗口

  BOOL InitWindow(HINSTANCE hInstance, int nCmdShow);

  //初始化DirectDraw并打印字幕

  BOOL InitDDraw(void);               

  //卸载DirectDraw函数

  void UninitDDraw(void);              

  BOOL InitWindow(HINSTANCE hInstance, int nCmdShow)

  {

    HWND hwnd;   //窗口句柄

    WNDCLASS wcex; //窗口类结构

    //设置窗口类结构

    wcex.style=0;            //风格

    wcex.lpfnWndProc=WinProc;      //窗口处理程序

    wcex.cbClsExtra=0;         //扩充风格

    wcex.cbWndExtra=0;         //扩充程序

    wcex.hInstance=hInstance;      //应用程序hInstance句柄

    wcex.hIcon=LoadIcon(hInstance,IDI_APPLICATION); //读入默认的图标

    wcex.hCursor=LoadCursor(NULL,IDC_ARROW);    //读入默认鼠标形状

    wcex.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);//窗口背景

    wcex.lpszMenuName=NULL;        //窗口目录

    wcex.lpszClassName= "DirectX--Hello"; //窗口的类名

    //注册窗口类

    RegisterClass(&wcex);

    //创建主窗口

    hwnd=CreateWindowEx(WS_EX_TOPMOST,"DirectX--Hello","",WS_POPUP|WS_VISIBLE,0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN),NULL,NULL,hInstance,NULL);

    if(!hwnd) return FALSE;

    ShowWindow(hwnd,nCmdShow); //显示窗口

    UpdateWindow(hwnd);    //更新窗口

    return TRUE;

  }

  LRESULT CALLBACK WinProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

  {

    switch(message)

    {

    case WM_KEYDOWN:  //如果按键

      switch(wParam)

      {

      case VK_ESCAPE: //Esc键

        PostMessage(hWnd,WM_CLOSE,0,0);

        break;

      }

      break;

    case WM_DESTROY:  //退出消息循环

      UninitDDraw();

      PostQuitMessage(0);

      break;

    }

    //调用缺省消息处理过程

    return DefWindowProc(hWnd,message,wParam,lParam);

  }

  //本程序的最核心内容,即DirectDraw的基本功能和用法

  BOOL InitDDraw(void)

  {

    DDSURFACEDESC ddsd;

    HDC hdc;

    //创建DirectDraw对象

    if (DirectDrawCreate(NULL,&lpDD,NULL)!=DD_OK) return FALSE;

    //设置为全屏模式

    if (lpDD->SetCooperativeLevel(GetActiveWindow(),

    DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN)!=DD_OK)

    return FALSE;

    //设置显示模式

    if (lpDD->SetDisplayMode(800,600,256)!=DD_OK) return FALSE;

    //设置主页面信息

    ddsd.dwSize=sizeof(ddsd);

    ddsd.dwFlags=DDSD_CAPS;

    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    //创建一个表面,类似开辟一块屏幕大小的显示内存

    if (lpDD->CreateSurface(&ddsd,&lpDDSPrimary,NULL)!=DD_OK) return FALSE;

    //输出文字

    if (lpDDSPrimary->GetDC(&hdc)!=DD_OK) return FALSE;

    SetBkColor(hdc,RGB(0,0,255));       //设置背景色

    SetTextColor(hdc,RGB(255,255,0));    //文字颜色

    TextOut(hdc,250,250,str1,lstrlen(str1));  //文字输出函数

    TextOut(hdc,300,270,str2,lstrlen(str2));

    lpDDSPrimary->ReleaseDC(hdc);

    return TRUE;

  }

  //卸载DirectDraw

  void UninitDDraw(void)

  {

    if(lpDD!=NULL)

    {

      if(lpDDSPrimary!=NULL)

      {

        lpDDSPrimary->Release();

        lpDDSPrimary=NULL;

      }

      lpDD->Release();

      lpDD=NULL;

    }

  }

  //Windows的主工作函数

  int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,         

  LPSTR lpCmdLine,int nCmdShow)

  {

    MSG msg;

    //初始化主窗口

    if (!InitWindow(hInstance,nCmdShow)) return FALSE;

    //初始化DirectDraw环境,并实现DirectDraw功能

    if (!InitDDraw())

    {

      MessageBox(GetActiveWindow(),"初始化DirectDraw时出错!","Error",MB_OK);

      UninitDDraw();

      DestroyWindow(GetActiveWindow());

      return FALSE;

    }

    //进入消息循环   

    while (GetMessage(&msg, NULL, 0, 0))

    {

      TranslateMessage(&msg);

      DispatchMessage(&msg);

    }  

    return msg.wParam;

  }