掌握DirectX和DirectInput—力反馈游戏杆 (2)

 

使用DirectInput对象

     一旦拥有了DirectInput对象,就可以用它来创建DirectInputDevice对象,来管理系统中特定的设备。创建DirectInputDevice对象要使用CreateDevice函数,它是作为IdirectInput接口一部分的五个函数之一。CreateDevice需要所请求设备的GUID,返回新DirectInputDevice对象的IdirectInputDevice接口指针。

 

     这些内容看起来很熟悉,因为它与CoCreateInstanceDirectInputCreate类似。但是,现在还没有完全准备好开始DirectInputDevice对象,原因是在创建DirectInputDevice对象前需要该设备的GUID

     DirectInput库为创建DirectInputDevice对象预定义了两个GUIDGUID_SysKeyboardGUID_SysMouse。将两者之一直接传递给CreateDevice函数,就会得到相应设备的DirectInputDevice对象。

     注意,令人感到奇怪的是缺少对游戏杆的预定义GUID。在Windows中,通常都有系统键盘和系统鼠标,另一方面,系统本身并不使用游戏杆。可以安装一个或者多个游戏杆,但系统管理的范围只限于驱动程序级。系统并为这些设备指定特殊的系统状态,也不会在日常事务中使用这些设备。因此,为游戏杆定义GUIDDirectInput来说是不合理的。

     那么,如何才能找到与系统连接的游戏杆的GUID呢得到它们,必须要列举设备。列举系统设备和性能在DirectX中相当普遍。要列举系统中的输入设备,需要使用EnumDevices函数。EnumDevicesIdirectInput接口的一部分,如下定义:

 

     注意此函数与Windows中其它列举API相同,例如EnumWindows。第二个参数是一个回调函数。第三个参数是程序中定义的32位值。第一个参数是想要列举的设备类型,对游戏杆来说,是DIDEVTYPE_JOYSTICK(全部的设备类型列在表4中)。最后一个参数是详细描述想要列举的设备的标志。现在支持的标志是DIEDFL_ATTACHEDONLYDIEDFL_ALLDEVICES(这两个标志是互斥独占的),此外还有DIEDFL_FORCEFEEDBACK,此标志表示力反馈设备,能够和另两个标志位或操作。

4:定义列举的输入设备

     以下定义的值可以传递给EnumDevices来选择列举哪种类型的输入设备。另外也支持子类型,见SDKDIDEVICEINSTANCE结构的文档。

说明

DIDEVTYPE_MOUSE

列举鼠标设备 (标准、轨迹球等)

DIDEVTYPE_KEYBOARD

列举键盘设备 (标准、键区等)

DIDEVTYPE_JOYSTICK

列举游戏杆设备 (操纵杆、操纵轮、方向舵等)

DIDEVTYPE_DEVICE

列举其它设备

     EnumDevices列举系统中的输入设备时,反复地调用回调函数。回调函数定义如下:

BOOL CALLBACK EnumProc(LPCDIDEVICEINSTANCE lpddi,LPVOID pvRef) ;

     因为回调函数是由用户程序定义并传递给EnumDevices的,所以是调用CreateDevice的最合适地方,直到创建了满足需要的足够DirectInputDevice对象为止。但是回调函数并非一定要如此实现,可以简单的将列举设备的所有GUID保存在一个表中,在以后的代码中使用。

     回调函数接受两个参数。第二个参数是程序定义的传递给EnumDevices32位值。更重要的是,第一个参数传递指向一个结构的指针,该结构包含关于能够与列举标准匹配的单个设备的许多信息。这是一个DIDEVICEINSTANCE结构。此结构中最重要的一条信息是设备的GUID,保存在结构的guidInstance成员中。

     当程序中完全完成DirectInput有关的工作后,就应该调用IdirectInput接口的Release成员。这就告诉DirectInput对象可以释放自己了。在DirectX中,最好养成释放对象的习惯,从低层对象开始,到高层对象结束。正常情况下程序会作为清除或者关闭的例行公事的一部分调用Release。这是使用每个DirectX组件的必要步骤,也是使用每个COM组件的必要步骤。

     现在已经用CreateDevice成员函数获得了DirectInputDevice对象的一个接口,为开始处理与系统连接的实际物理设备做好了准备。

使用DirectInputDevice对象

     DirectInputDevice对象的每个实例都与系统中的特定设备相关。此对象提供了对系统硬件更多的控制和能力,从而使DirectX的允诺实现。下面讨论拥有了DirectInputDevice对象后下一步干什么。

     拥有了IdirectInputDevice接口的一个接口指针,现在干什么先,设置设备的数据格式。通过调用SetDataFormat来完成,该函数是一个接口成员函数。设置数据格式包括无数可能的决定,包括轴信息、相对或绝对坐标信息、等等。所有这些细节通过一个叫作DIDATAFORMAT的结构传递给此函数。实际上,SetDataFormat唯一的参数就是指向此结构的指针。

     填写这个结构的细节会使人发憷。值得感谢的是这一工作并不是必须的,因为DirectInput已经定义了几个DIDATAFORMAT结构变量,可以用于比较普通的输入设备:c_dfDIKeyboard, c_dfDIMouse, c_dfDIJoystick, c_dfDIJoystick2。为普通的力反馈游戏杆设置数据格式,可以使用下面的调用形式:

lpdid->SetDataFormat( &c_dfDIJoystick ) ;

     在此例中,lpdid是指向IdirectInputDevice接口的指针。

     设置完设备对象的数据格式后,就需要设置设备的协作级别。因为协作级别在整个DirectX中很常见,所以这里要做一下说明。大多数直接处理系统硬件的DirectX对象在接口的成员中都有一个叫作SetCooperativeLevel函数。这个函数很重要,因为它定义了程序操纵与系统中其它进程有关的硬件的控制级别。同其它DirectX对象一样,只有设置了协作级别才能使DirectInputDevice对象工作。要理解协作级别,就需要熟悉Acquire函数。调用此函数是为了获得对物理设备的实际访问(不要和逻辑上的DirectInputDevice对象混了)。相反的,Unacquire函数释放对物理设备的访问。

     下面是函数SetCooperativeLevel的定义:

 

     hwnd是程序的主窗口。标志是下面一些值的或操作的结合: DISCL_BACKGROUND, DISCL_FOREGROUND, DISCL_EXCLUSIVE, DISCL_ NONEXCLUSIVE

     如果标志参数中或上了DISCL_EXCLUSIVE,则当获得设备后本程序就成为唯一允许访问该物理设备的进程。另一方面,如果选择了DISCL_NONEXCLUSIVE,那么系统中可以有多个进程同时协作获得和使用该设备。如果或上了DISCL_BACKGROUND,程序将不会失去物理设备。然而,象Ctrl+Alt+Del组合键被按下这样的系统事件仍然能够隐含地“unacquire”程序中的设备。如果使用了DISCL_ FOREGROUND,当不是活动窗口时,程序将会自动释放物理设备。这就是将程序主窗口句柄传递给SetCooperativeLevel的意义。DirectX根据窗口是否是系统当前活动窗口自动调整设备共享。

     那么所有这些值的意义是什么呢面举个例子说明。如果力反馈游戏杆的协作模式是DISCL_FOREGROUND | DISCL_EXCLUSIVE,那么只要程序处于活动状态,就能够从游戏杆读数据并播放力反馈效果(力反馈需要exclusive-level协作)。只要用户一选择其它程序,程序就失去对物理设备的控制,新激活的程序就能够访问该设备。这意味着在调试程序时,如果切换到调试器窗口,程序就会因为窗口变为非活动的而失去对游戏杆的控制。

     如果将同一游戏杆的协作级别设为DISCL_BACKGROUND | DISCL_EXCLUSIVE将会是什么情况呢序将会所有时间都能访问游戏杆,不管窗口的状态。但是现在系统中其它进程就不能获得游戏杆,除非程序释放了游戏杆,不管用户在做什么!

    非常明显,在正式发布的产品中应该使用DISCL_FOREGROUND | DISCL_EXCLUSIVE,而在调试版本中应该使用DISCL_BACKGROUND|DISCL_EXCLUSIVE。但是也不总是这样选择。例如,如果设备是系统键盘,那么DirectInputDevice想独占使用而调用SetCooperativeLevel将会失败。这是因为操作系统想要允许用户自由地从一个程序切换到另一个程序。类似的,DirectInputDevice不会允许以协作级别DISCL_BACKGROUND|DISCL_EXCLUSIVE请求系统鼠标。Windows不希望一个程序能够完全将用户与操作系统的联系切断。

     在能够从物理设备读取信息或向物理设备发送信息之前,必须要用Acquire获得设备。在临时或永久结束设备使用时要明确地使用Unacquire函数释放设备。但Unacquire并不是失去设备控制的唯一方法。

     如果设置协作级别时使用DISCL_FOREGROUND标志,那么程序的主窗口不再是系统中的活动窗口时设备将被明确释放。这就是说,在程序调用Acquire和实际试图从设备读取信息之间,能够失去对设备的占有。所以需要检查返回值来捕捉这样的错误,并准备好在任何时间重新获得该设备。

     关于AcquireUnacquire的决定性要点:当程序获得独占协作级别的设备时,DirectX拥有该设备。例如,如果鼠标被DirectX(独占)获得,那么程序窗口中的按钮就不会对鼠标做出响应。这就是说,如果想让Windows对设备响应,就应该释放该设备。换句话说,如果不想让DirectInput从设备中读取数据,就调用Unacquire

     设置完设备的协作级别后,接着应该为设备配置其它设置。获得了设备后,接着就应该开始使用GetDeviceState函数轮流检测输入的数据。当完成与设备对象的操作后,调用Unacquire释放DirectInputDevice对象。设备与设备之间存在细节上的差别;下面讲解游戏杆和键盘,应该能为从其它设备读取输入提供足够的基础知识。

键盘

     键盘是到目前为止最容易读取的设备。实际上,设置完数据格式、协作级别、获得设备以后,就可以读取键盘状态了。读取键盘状态要使用IdirectInputDevice接口的GetDeviceState成员。GetDeviceState用关于物理设备的状态信息组装一个结构,所组装结构的类型由前面对SetDataFormat的调用决定。对键盘来说,此数据结构是一个简单的256个字节组成的数组。每个字节对应于键盘上的一个键,如果某个键按下,相应字节的高位就被设置。

     DirectInput定义了一套以DIK_XXX为前缀的常量,这些常量可以用来索引字节数组以找到关于特定键的数据。例如,如果要检查右Shif键当前是否按下,可以使用DIK_RSHIFT定义:

 

     CKeyboardData256个字节的缓冲区。几乎就是这么简单,但是要记住,不管GetDeviceState在何时返回DIERR_INPUTLOST,就必须使用Acquire获得设备。这种情况发生在每次用户从程序切换离开的时候。

游戏杆

    游戏杆非常好玩。与其好听的名称(Joystick——原意为欢乐杆)相符,这种设备为游戏体验添加了许多乐趣,同时也为程序员的体验添加了一些东西。正常情况下,通过调用IdirectInput接口的CreateDevice成员得到IdirectInputDevice接口(和对象),这对游戏杆也适用。

     但是开发人员都希望立即将接口升级到IDirectInputDevice2,那么可以象下面这样使用QueryInterface调用请求CreateDevice返回新的接口:

     如果成功,就可以释放原来的接口,开始使用漂亮的新IDirectInputDevice2接口。但是为什么要这么做呢span>IDirectInputDevice2接口提供IdirectInputDevice的所有功能,而且还有另外两个重要特性:支持查询设备和支持力反馈设备。

     其次,需要设置上的一些考虑。还记得SetDataFormat定义了GetDeviceState返回的数据的类型。对于游戏杆设备,使用c_dfDIJoystickc_dfDIJoystick2两个预定义变量之一,将返回数据的类型设置为DIJOYSTATEDIJOYSTATE2结构。选择哪种主要取决于要使用游戏杆哪种类型的特性。浏览这些结构中的成员应该对弄清这个问题有帮助。

     同所有输入设备一样,要为游戏杆设置数据格式和协作级别。游戏杆往往比键盘需要更多一点注意。这是因为现在还几乎没有功能完美的游戏杆,所以程序应该检查以确保控制的设备能满足要求。如果不能,就调整要求或者提醒用户游戏杆太落后!设备的能力可以并且应该调用IdirectInputDevice接口的成员函数GetCapabilities探测。

     这就引出了适用于所有DirectX组件的另一个讨论点。DirectX为多种设备提供广泛的支持。软件开发环境和使用环境可能有很大差别,不同的计算机支持不同水平的DirectX功能。编写好使用DirectX的软件,需要检查硬件的能力。最差的情况下,如果某个功能不支持,可以退出程序。最好的情况当然是程序能够聪明地根据缺少的特性调整本身的需求。

     在开始从设备得到输入之前,需要设置设备的特性。这些特性包括象返回值的范围、游戏杆的中心点等此类的细节。这一工作由函数SetProperty完成,相当复杂。

     SetProperty设置设备的一个特性。首先,必须使用关于要改变的设置的一些信息填写一个数据结构。请参考Platform SDK中的文档,得到所有数据结构。每个结构都以一个DIPROPHEADER结构开始,此结构中填写描述要改变的设置的信息。然后,用特定于所改变的设置的数据填写结构中剩余的部分。最后,调用SetProperty,参数是GUID和指向结构中DIPROPHEADER部分的指针。下面的代码片段将游戏杆的垂直范围设置为–100100

     此结构中最难懂的部分是diph.dwObjdiph.dwHowdiph.dwHow描述diph.dwObj中保存何种信息。diph.dwObj实际描述哪个属性被设置。大多数情况下,diph.dwHow的值是DIPH_BYOFFSETdiph.dwObj的值是传递给SetDataFormat的结构中一个预定义的偏移。

     应该指出能够列举设备的对象,包括按钮和其它特点。这一工作由EnumObjects函数完成。这样做时,应该提供一个对象标志符。将此标志符传递给diph.dwObj成员,将diph.dwHow成员填写为DIPH_BYID

     在从设备读取数据之前,至少要为设备的XY坐标轴设置最小和最大值。设置好设备属性后,就可以获得设备并开始从设备获得数据。从游戏杆获取数据与从键盘或鼠标获取数据不同,因为游戏杆是查询设备。

     键盘和鼠标会引发硬件中断,由系统中的驱动程序处理,并用来更新通过调用GetDeviceStateDirectInput返回的数据。查询设备(如大多数游戏杆)不产生硬件中断,因此,DirectInput必须被告知从设备获取状态信息。这一工作通过调用IDirectInputDevice2接口的Poll成员函数完成。此时也是检查     设备是否需要重新获得的适当时机。设备被成功查询后,就可以调用GetDeviceState获取状态信息。

     如果调用SetDataFormat时使用c_dfDIJoystick变量,那么GetDeviceState将用游戏杆当前的状态信息填充一个DIJOYSTATE结构。此结构的内容主要取决于物理设备的特性和SetProperty的设置。例如,如果结构中的lY成员等于-50,并且Y轴的范围设置为-100100,那么就是说游戏杆在垂直方向上处于中心和最顶端的中间。程序中应该确保设备的范围设置为能合理满足需求的值。为了从游戏杆设备中获取数据,程序应该定期查询设备。

使用DirectInputEffect

    首先,应该解释一些力反馈技术。力反馈设备是能够产生用户可以感觉到的力的设备,这些力叫作效果,例如颠簸效果或者持续的将操纵杆推向右上方的力。这些效果是“播放”出来的,效果由程序控制播放,或者对函数调用响应,或者对用户按键自动反应。

     DirectInput目前支持大约一打不同的效果类型(见表5)。这些效果的范围从完全由程序控制的低级持续力效果,到由DirectInput或设备自己控制的高级倾斜或波动效果。效果有四种基本类型:持续力、倾斜效果、周期效果和条件。持续力是单一方向上不改变强度的力。倾斜效果是强度随时间线性变化的持续的力。周期效果是沿着给定的轴重复变化,其量级或者力的强度由周期效果定义。条件是对用户与游戏杆的交互作用做出响应的效果。这种效果可能是象一根弹簧,操纵杆向某个方向推得越远,反弹力就越强。

5DirectInput效果的类型

GUID

说明

使用方法注解

GUID_ConstantForce

固定强度、特定方向的持续拉力。

使用DICONSTANT力结构作为DIEFFECT结构的一部分实现持续力。

GUID_CustomForce

一序列持续力下传到设备,按顺序播放。

DICUSTOMFORCE结构被用来定义力。

GUID_Damper

随沿坐标轴的移动增加的条件效果。

实现这种效果的特定类型结构是DICONDITION结构。条件效果通常不支持包。

GUID_Friction

阻碍沿坐标轴移动的条件效果。

实现这种效果的特定类型结构是DICONDITION结构。条件效果通常不支持包。

GUID_Inertia

随沿坐标轴移动的加速度增加的条件效果。

实现这种效果的特定类型结构是DICONDITION结构。条件效果通常不支持包。

GUID_RampForce

特定方向上大小线性增加或减小的拉力。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2011年9月16日
下一篇 2011年9月16日

相关推荐