为PC开发游戏从来就没有轻松过。从无数种显示卡和声卡中,开发者学会了在功能性和兼容性之间平衡的艺术。他们不得不处理象页面切换、段内存结构和位操作这样令人讨厌的问题。并且随着多人游戏的流行,开发者必须同时处理象网络和通信等事项。DirectX引入后,游戏开发者变得轻松了。通过为开发者提供的DirectX对象,绝大多数讨厌的工作已经被简化了。
基于DirectX的程序是普通的Windows程序吗?必须懂得COM吗?为简单的程序值得使用DirectX吗?必须使用DirectX的全部组件吗?这样的问题肯定还有更多。
本文将首先介绍DirectX,然后介绍DirectX的一个组件DirectInput的使用。演示程序说明了DirectInput的用法,着重介绍了其强大的反馈功能。
DirectX揭密
DirectX是一套为Windows程序提供对系统硬件更亲密控制的组件。(表1列出了DirectX 5.0的组件及其作用)。那么,亲密控制是什么意思呢?
表1:DirectX 5.0的组件
组件 | 用途 |
DirectDraw | 高速2D图象 |
DirectSound | 短响应时间声音输出 |
Direct3D | 高速3D图象 |
DirectInput | 面向游戏的对游戏杆和其它输入设备的访问 |
DirectSetup | 方便的安装DirectX组件 |
DirectPlay | 面向游戏的通信和网络支持 |
DirectShow | 视频流支持 |
DirectAnimation | 动画录放支持 |
DirectX提供的硬件控制常常被描述成底层控制,这会使人联想起位操作和其它讨厌的事情。实际上,DirectX组件包含许多高层API,使得象复制位图和播放声音等复杂的工作变得相当简单。用“为程序提供比过去更好的对硬件的控制”来形容DirectX更准确。这在Windows中是一个显著的特性,因为在Windows中,资源是共享的,并由操作系统控制。
DirectX组件遵守称为COM的二进制对象的工业标准。
开始DirectX
下面从DirectX的安装开始讲起。大多数情况下,某个好玩的游戏就会为系统安装DirectX。为得到最新的版本,应该从最新的Microsoft Platform SDK中将DirectX安装到系统中。可以在http://www.microsoft.com/msdn站点或者MSDN光盘中找到platform SDK。缺省情况下,Microsoft Platform SDK被安装到缺省驱动器根目录下的\MSSDK目录中。DirectX的头文件安装在\MSSDK\INCLUDE目录中,Lib文件安装在\MSSDK\LIB目录中。
Platform SDK包含了一些非常好的DirectX例子和文档。早期发布的DirectX文档非常粗略而且有些是错误的,现在的版本已经极大地改正了这一问题。最好要熟悉这些文档。
现在已经为安装利用DirectX的程序做好了准备。所幸的是,不必一次就处理DirectX的全部功能。DirectX是一套可以分别使用的组件。实际上,在编程概念中,DirectX的不同部分互相没有联系。它们仅仅是具有相同的设计风格和目标:使Windows的游戏编程变得容易。
使用DirectX组件的程序有什么特殊的地方吗?根本没有。使用DirectX组件的程序是基于Win32的程序,它们使用普通Win32 API集,并且可以访问所有可以获得的操作系统工具。实际上,DirectX既可以用于GUI程序,也可以用于控制台程序。可以直接用Petzold-style SDK编程开发程序,也可以用基本类库,如MFC。总的说,唯一的要求是大多数DirectX组件在程序中需要HWND,所以至少要有一个窗口。
虽然DirectX组件是分离的,但是每个组件的实现风格和使用都是相同的。DirectInput是学习DirectX的非常好的出发点,原因是DirectInput是最简单的组件之一。
用力
以后在游戏中要“用力”,这是电影《星球大战》中的说法,因为DirectInput中加入了相当令人陶醉的力反馈支持。DirectX 5.0以前,DirectInput支持从鼠标和键盘读取输入,这是一个有用但却令人厌烦的特性。DirectX 5.0中,DirectInput被扩充到支持具有以物理力的形式向用户传播反馈的能力的设备。
如果不能立即理解上面的内容,下面就用一个游戏进行解释。假设你刚启动了你最喜欢的超现实3D越野赛车游戏,正手握力反馈游戏杆。在起跑线上,你可以听到赛车引擎的空转声,同时也能够通过游戏杆感觉到赛车引擎的空转!比赛开始后,你可以感觉到引擎高速旋转的嗡嗡震动。当行驶到赛程中崎岖的地段时,你将会不停的感觉到电子碰撞。赛车在整个赛场上撞来撞去,你的游戏杆也会如此。赛车车轮卡在车辙中导致赛车被拉向左边,游戏杆也会被拉向左边!整个过程中你可以感觉到每次颠簸、刮擦、撞击和撞毁。
现在,带有支持DirectInput的Windows驱动程序的唯一的力反馈设备是Microsoft的SideWinder Force Feedback Pro。这一现状不会持续太久,新设备以及现有设备的新驱动程序很快就会进入市场。
剖析DirectInput
DirectInput由三个对象组成:DirectInput, DirectInputDevice, 和DirectInputEffect (见表2)。DirectInput是一个高层的对象,通过DirectInput对象可以对相关的输入设备进行基本的初始化和查找。DirectInput对象最终用来创建低层的DirectInputDevice对象。DirectX中的每个主要组件都采用相同的方法,首先创建高层对象,如DirectInput或DirectSound对象,然后创建低层对象与硬件进行实际的通信。
表2: DirectInput对象
对象 | 说明 |
DirectInput | 封装高层DirectInput功能,列举设备并用来创建DirectInputDevice对象。 |
DirectInputDevice | 与物理输入设备的接口,例如游戏杆,包括收集和设置设备状态信息的接口,并且用来创建DirectInputEffect对象 (对于力反馈设备)。 |
DirectInputEffect | 封装能够在力反馈设备上“播放”的简单效果,提供启动、停止和设置力反馈效果等功能。 |
DirectInput对象是三个对象中最容易理解的。实际上,它在一个接口形式IDirectInput (见表3)中只提供五个函数。这是DirectInput的一个非常重要的部分,因为这是出发点。
表3:IdirectInput接口
成员函数 | 说明 |
CreateDevice | 创建一个DirectInputDevice对象并返回一个指向其IdirectInputDevice接口的指针。 |
EnumDevices | 为找到的与给定标准匹配的每个设备调用一个回调函数,每个回调函数提供一个GUID,可以用在CreateDevice中创建DirectInputDevice对象。 |
GetDeviceStatus | 测试物理设备是否连接到系统。 |
Initialize | 如果DirectInput对象是使用CoCreateInstance创建的,那么在使用前必须调用Initialize成员。如果DirectInput对象是使用DirectInputCreate创建的,那么就已经初始化过了。 |
RunControlPanel | 为设备运行Windows Control Panel程序,让用户安装新设备或者更改已有设备的配置。游戏杆校准可以在此处做。 |
创建DirectInput对象
为了创建DirectInput对象并得到其IdirectInput接口指针,应该在程序初始化阶段使用两种方法之一完成。
第一种方法相当简单。DirectX提供了一个助手函数DirectInputCreate来创建并初始化DirectInput对象。它与所有DirectInput的函数、接口和宏定义都在头文件DINPUT.H中声明。实际的函数体在DINPUT.LIB文件中。
DirectInputCreate如下定义:
HRESULT WINAPI DirectInputCreate(
HINSTANCE hinst,
DWORD dwVersion,
LPDIRECTINPUT * lplpDirectInput,
LPUNKNOWN punkOuter
);
第一个参数是应用程序的实例。第二个参数是程序需要的DirectInput版本,通常使用DIRECTINPUT_VERSION宏,定义为当前版本。第三个参数最重要,如果对COM非常陌生的化就很难理解,它是指向IdirectInput接口的指针的地址。程序中应该定义一个LPDIRECTINPUT类型的变量(可以是全局的)并将其地址作为第三个参数传递给DirectInputCreate。
最后一个参数叫作punkOuter,与COM技术中的聚合有关,可以用NULL安全的忽略。返回值是一个HRESULT,是COM的标准返回类型,可以将返回值与可能的返回值比较,也可以使用COM宏定义SUCCESS或FAILED来检查。
使用DirectInputCreate能够容易地创建高层对象并得到其主接口指针。这是DirectX的又一个设计方法,每个DirectX组件都提供助手函数来创建高层对象,例如DirectInputCreate或DirectDrawCreate。在程序中可以用这些助手函数创建DirectX对象,然而,这些函数实际上创建的是COM对象。这个工作也可以用叫作CoCreateInstance的标准Win32 API函数来完成。这就引出了创建DirectInput对象的第二中方法。
在Win32中用CoCreateInstance创建COM对象非常普遍。如果程序中已经使用CoCreateInstance创建了其他COM对象,开发者可能就会希望也用它来创建DirectX对象。因为COM对象在安装时就在系统中注册过,所以唯一需要知道的就是对象的GUID,用它来创建一个实例。创建DirectX对象需要的全部GUID都在头文件中声明,并在库文件DXGUID.LIB中定义。可以将一个预定义的GUID传递给CoCreateInstance,让Windows为你创建对象。
CoCreateInstance定义如下:
STDAPI CoCreateInstance(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID * ppv
);
第一个参数是要创建对象的GUID,DirectX定义的GUID是叫作CLSID_DirectInput的GUID结构变量。第二个参数是熟悉的pUnkOuter,同样可以用NULL忽略。第三个参数dwClsContext定义COM对象在何处创建,DirectX只支持进程内服务器,所以必须使用CLSCTX_INPROC_SERVER。
第四个参数是两种方法真正的不同之处。记住COM对象对外提供接口,与对象本身一样,接口也用GUID识别。使用第一种方法,不能选择得到的接口,总是得到IdirectInput。使用CoCreateInstance可以请求对象所支持的任何接口,方法是使用为接口预定义的GUID。但是在DirectInput这是没有意义的,因为DirectInput对象的唯一有用的接口就是IdirectInput。其它DirectX组件支持多个有用的接口。(例如,DirectDraw对象可以用IdirectDraw或IDirectDraw2接口操作。)
最后一个参数是程序中接口指针变量的实际地址。
现在就拥有了对象和对象的一个接口。CoCreateInstance方法还需要另外一步:必须要首先调用一个接口函数初始化对象。DirectInputCreate提供的是一个已经初始化过的DirectInput对象,但CoCreateInstance没有特定于DirectInput的认识,因此必须调用IdirectInput接口的初始化成员函数。假设如下定义IdirectInput接口指针变量:
LPDIRECTINPUT g_lpDI
可以如下调用初始化函数:
g_lpDI->Initialize( hInstance, DIRECTINPUT_VERSION);
既然选择采取这种标准方法创建对象,就不得不注意COM需要的其他标准,例如需要调用CoInitialize和CoUninitialize。