其中,IDirectInput8作为DirectInput API中最主要的接口,用于初始化系统以及创建输入设备接口,DirectInput中其他的所有的接口都需要依赖于我们的IDirectInput8之上,都是通过这个接口进行查询的。而DirectInputDevice8接口用于表示各种输入设备(如键盘、鼠标和游戏杆),并提供了相同的访问和控制方法。对于某些输入设备(如游戏杆和鼠标),都能通过查询各自的IDirectInputDevice8接口对象,得到另一个接口IDirectInputEffect8。而IDirectInputEffect8接口则用于控制设备的力反馈效果。

三、DirectInput使用步骤详解

1.头文件和库文件的包含

我们首先需要注意的是,在使用DirectInput时,需要保证我们包含了DInput.h头文件,并且在项目中已经链接了DInput8.lib库文件。

当然,库文件我们也可以动态添加:

#pragma comment(lib, "dinput8.lib") // 使用DirectInput必须包含的头文件,注意这里有8

2.创建DirectInput接口和设备

在DirectInput中我通过们调用DirectInputCreate函数创建并初始化IDirectInput接口,我们可以在MSDN中查到该函数的声明如下:

1 HRESULT DirectInput8Create(

2

3 HINSTANCE hinst,

4

5 DWORD dwVersion,

6

7 REFIID riidltf,

8

9 LPVOID * ppvOut,

10

11 LPUNKNOWN punkOuter

12

13 )

■ 第一个参数,HINSTANCE类型的hinst,表示我们当前创建的DirectInput的Windows程序句柄,这个值填我们在WinMain函数的参数中的实例句柄就可以了。

■ 第二个参数,DWORD类型的dwVersion,表示我们当前使用的DirectInput版本号,通常可以取DIRECTINPUT_VERSION或者DIRECTINPUT_HEADER_VERSION,这两个值对应的是同一个值,为0x0800。所以我们在这里还可以直接填0x0800。

归根揭底的话,可以通过【转到定义】大法在dinput.h中查到有如下代码:

1 #define DIRECTINPUT_HEADER_VERSION 0x0800

2

3 #ifndef DIRECTINPUT_VERSION

4

5 #define DIRECTINPUT_VERSION DIRECTINPUT_HEADER_VERSION

大体意思很清楚了吧,先定义一下DIRECTINPUT_HEADER_VERSION=0x0800,然后再说如果没有定义DIRECTINPUT_VERSION的话,就定义一个DIRECTINPUT_VERSION= DIRECTINPUT_HEADER_VERSION。

■ 第三个参数,REFIID类型的riidltf,表示接口的标志,通常取IID_IDirectInput8就可以了。

■ 第四个参数,LPVOID 类型的* ppvOut,用于返回我们新创建的IDirectInput8接口对象的指针。

■ 第五个参数,LPUNKNOWN类型的punkOuter,一个和COM对象接口相关的参数,通常我们设为NULL就可以了。

这个函数执行成功的话TINPUTVER会返回HRESULT类型的DI_OK,而失败的话根据不同的调用失败原因,会返回DIERR_BETADIRECSION,DIERR_INVALIDPARAM,DIERR_OLDDIRECTINPUTVERSION, DIERR_OUTOFMEMORY中的一个。所以我们可以根据FAILED宏来判断我们IDirectInput8接口对象是否创建成功了。

下面是一个调用的例子:

1 // 创建DirectInput设备

2

3 LPDIRECTINPUT8 g_pDirectInput = NULL;

4

5 if(FAILED(DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8,( void**)&g_pDirectInput, NULL)))

6

7 return E_FAIL;

这步完成之后,咱们的定义的DIRECTINPUT8接口对象g_pDirectInput就有了权利,新官上任了。

在IDirectInput8接口中包含了很多用于初始化输入设备及获得设备接口的方法。其中,常用的方法为EnumDevices和CreateDevices。前者EnumDevices用于获得输入设备的类型,而后者CreateDevices用于为输入设备创建IDirectInputDevice8接口对象。

系统中每一个已安装的设备都有一个系统分配的全局唯一标示符(GUID,Global Unique Identification),从英文单词意义上就可以知道,系统中的每个设备都有着独一无二的GUID,这个GUID又唯一的标志了系统中的某某设备。就像我们每个人都有着独一无二的的身份证号码。

要使用某个设备的话,首先我们就需要知道他的GUID。

鼠标和键盘作为我们电脑中最为重要的外设,DirectInput对他们做了特殊对待,给了后门,定义了他们的GUID分别为GUID_Keyboard和GUID_SysMouse。而对于其他的输入设备,我们就用上面提到过的EnumDevices方法枚举出这些设备,以得到他们的GUID,我们可以在MSDN中查到这个方法有如下声明:

1 HRESULTEnumDevices(

2

3 DWORD dwDevType,

4

5 LPDIENUMDEVICESCALLBACKlpCallback,

6

7 LPVOID pvRef,

8

9 DWORD dwFlags

10

11 )

■ 第一个参数,DWORD类型的dwDevType,指定我们需要枚举的设备类型。

可取的值为DI8DEVCLASS_ALL,DI8DEVCLASS_DEVICE,DI8DEVCLASS_GAMECTRL,DI8DEVCLASS_KEYBOARD,DI8DEVCLASS_POINTER中的一个。

■ 第二个参数,LPDIENUMDEVICESCALLBACK类型的lpCallback,用于指定一个回调函数的地址,当系统中每找到一个匹配的设备时,就会自动调用这个回调函数。

■ 第三个参数,LPVOID类型的pvRef,返回我们当前匹配设备的GUID值。

■ 第四个参数,DWORD类型的dwFlags,指定我们枚举设备的方式。取值可以下面的一个或者多个值:DIEDFL_ALLDEVICES,DIEDFL_ATTACHEDONLY,DIEDFL_FORCEFEEDBACK,DIEDFL_INCLUDEALIASES,DIEDFL_INCLUDEHIDDEN,DIEDFL_INCLUDEPHANTOMS。

取得我们需要使用的设备的GUID后,就可以根据这个GUID来调用IDirectInput8接口的CreateDevice方法,进而来创建设备的IDirectInputDevice8接口对象了。

我们可以在MSDN中查到IDirectInput8::CreateDevice方法的声明如下:

1 HRESULTCreateDevice(

2

3 REFGUID rguid,

4

5 LPDIRECTINPUTDEVICE*lplpDirectInputDevice,

6

7 LPUNKNOWN pUnkOuter

8

9 )

■ 第一个参数,REFGUID类型的rguid,就是填我们上面讲到的输出设备的GUID。系统中当前使用的键盘对应GUID_SysKeyboard,当前使用的鼠标对应GUID_SysMouse。其他设备的话,就用我们刚刚讲过的EnumDevices获取一下就行了。

■ 第二个参数,LPDIRECTINPUTDEVICE类型的*lplpDirectInputDevice,表示我们所创建的输入设备对象的指针地址,可以说调用这个CreateDevice参数就是在初始化这个参数。

■ 第三个参数,LPUNKNOWN类型的pUnkOuter,、和COM对象的IUnknown接口相关的一个参数,一般我们不去管它,设为NULL就可以了。

讲解完了,当然得看一个调用实例。下面的代码中CreateDevice方法的第二个参数我们填的是GUID_SysMouse,所以我们在为系统鼠标创建一个DirectInput设备接口对象:

1 LPDIRECTINPUTDEVICE8 g_pMouseDevice = NULL;

2

3 if(FAILED (g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice, NULL)))

4

5 return E_FAIL;

3.设置数据格式

数据格式用于表示设备状态信息的存储方式,每种设备都有一种用于读取对应数据的特定数据格式,所以对每种设备都要区别对待。所以要使程序从设备读入数据的话,首先我们需要告诉DirectInput读取这种数据所采用的格式。

设置数据格式通常我们都是通过IDirectInputDevice8接口的SetDataFormat方法来做到的,这个方法可以把设备的数据格式填充到一个DIDATAFORMAT接口类型的对象。该方法的声明如下:

HRESULT SetDataFormat(

LPCDIDATAFORMAT lpdf

)

SetDataFormat方法唯一的变量就是LPCDIDATAFORMAT类型的lpdf,DirectInput已经为我们准备好了一些备选的参数,下面是一个列表:

数据格式

精析

c_dfDIkeyboard

标准键盘结构,包含256个字符,每个字符对应着每个键

c_dfDIMouse

标准鼠标结构,带有3个轴和4个按钮

c_dfDIMouse2

扩展鼠标结构,带有3个轴和8个按钮

c_dfDIJoystick

标准游戏杆,带有三个定位轴,3个旋转轴,两个滑块,1个POV hat和32个按钮

c_dfDIJoystick2

扩展的游戏杆

依然是一个调用实例,设置鼠标的数据格式:

g_pMouseDevice->SetDataFormat(&c_dfDIMouse);

4.设置协作级别

在Windows操作系统中,系统中的每个应用程序都通常会使用多个输入设备,并且同一输入设备也可能被多个应用程序同时使用。因此,需要一种方式来共享和协调应用程序对设备的访问。在DirectInput中,祭出的是协作级别(Cooperative Level)这套处理方式。

协作级别定义了进程与其他应用程序和操作系统共享设备的方式。设备一旦创建就需要设置它的协作级别,协作级别表示了应用程序对设备的控制权。

DirectInput的协作级别可以以两套方案来分类:前台、后台模式和共享、独占模式。

Ⅰ.前台模式与后台模式

其中,前台模式表示只有当窗口处于激活状态时,才能获得设备的控制权。而当处于非激活状态时,会自动失去设备的控制权;后台模式表示可以在任何状态下获取设备,即使是在窗口处于非激活状态时。后天模式可以被任何应用程序在任何时候使用并获取设备数据。

Ⅱ.共享模式与独占模式

共享模式表示多个应用程序可以共同使用该设备,而独占模式表示应用程序是唯一使用该设备的应用程序。这里需要注意一下,独占模式并非意味着其他应用程序不能获取输入设备状态,如果进程同时使用了后台模式与独占模式的话,当其他进程申请了独占模式的话,这个进程就会失去设备的控制权。

我们平常都是通过IDirectInputDevice8接口的SetCooperativeLevel方法来设置设备的协作级别的,我们可以在MSDN中查到SetCooperativeLevel的声明如下:

HRESULT SetCooperativeLevel(

HWND hwnd,

DWORD dwFlags

)

■ 第一个参数,HWND类型的hwnd,显然就是填想要与当前设备相关联的窗口句柄了,且这个窗口需要属于当前进程的顶级窗口。

■ 第二个参数,DWORD类型的dwFlags,描述了当前设备的协作级别类型,也就是填我们上面讲到的前台、后台模式和共享、独占模式等一些模式的标识符,可取一个值到多个值,浅墨把取值在下表中出来了:

协作级别类型

精析

DISCL_BACKGROUND

后台模式,一般我们让他与DISCL_NONEXCLUSIVE(非独占模式)配合使用

DISCL_FOREGROUND

前台模式,一般我们让他与DISCL_EXCLUSIVE(独占模式)配合使用

DISCL_EXCLUSIVE

独占模式

DISCL_NONEXCLUSIVE

非独占(共享)模式

DISCL_NOWINKEY

让键盘上烦人的Windows键失效

注意,后台模式和独占模式不能同时选择,用脚丫子来想都知道他们两个组合起来不符合逻辑,既然都是在后台了,还谈什么独占呢?

下面依旧是一个调用实例,将鼠标设备的协作级别设为前台、独占模式:

g_pMouseDevice->SetCooperativeLevel(hwnd,DISCL_FOREGROUND |DISCL_EXCLUSIVE);

5.设置特殊属性

设备的特殊属性包含设备的数据模式、缓冲区大小、以及设备的最小最大范围等等。DirectInput为我们提供了SetProperty方法来设置设备的特殊属性,我们可以在MSDN中查到这个方法有如下原型:

HRESULT SetProperty(

REFGUID rguidProp,

LPCDIPROPHEADER pdiph

)

这个方法平常用得不算多,因为篇幅原因暂且先不详细讲了,需要用的时候大家去查一下文档就可以了。

6.获取和轮询设备

首先是一个常识,在访问和使用任何输入设备之前,首先必须获得该输入设备的控制权。权力这东西,人人都喜欢,对其趋之若鹜,在我们的计算机中也不例外。其他的程序随时都可能勾心斗角,争夺并抢走对输入设备的控制权。所以我们在使用之前,往往都要重新获取一下设备的控制权,以确保权力在我们手中。

在DirectInput中,权力的敲门砖为IDirectInput8接口的Acquire方法,我们可以在MSDN中查到这个“权力权杖”有如下的原型:

HRESULT Acquire()

我们可以发现他简简单单清清白白,没有参数,返回值为HRESULT。调用起来当然是非常简单:

g_pMouseDevice->Acquire();

为了大家看起来简明扼要,咱们这里没有用if和FAILD宏给他括起来,进行错误处理。

另外需要注意的是,在获得输入设备的控制权之前,必须先调用IDirectInputDevice8接口的SetDataFormat或者SetActionMap方法来设置一下数据格式,不然我们调用Acquire方法的话,将直接给我们返回DIERR_INVALIDPARAM错误的。

另外需要讲到的是轮询。

轮询可以准备在合适的情况下读取设备数据。因为数据可能具有临界时间的。这个轮询的原型也是非常非常的简单:

HRESULT Poll()

轮询用起来当然也是非常简单的:

g_pMouseDevice ->Poll();

7.读取设备信息

在Direct3D应用程序中,拿到对输入设备的控制权之后,就可调用IDirectInputDevice8接口的GetDeviceState方法来读取设备的数据。而为了存储设备的数据信息,在调用该方法时,须传递一个数据缓冲区给GetDeviceState方法,这个GetDeviceState方法的原型我们可以在MSDN中查到是如下:

HRESULT GetDeviceState(

DWORD cbData,

LPVOID lpvData

)

■ 第一个参数,DWORD类型的cbData,指定了我们缓冲区的大小(具体是哪个缓冲区在第二个参数中)。

■ 第二个参数,LPVOID类型的lpvData,表示一个获取当前设备状态的结构体的地址值。

他的数据格式和我们之前调用的IDirectInputDevice8::SetDataFormat方法有着前后呼应的密切联系。下面我们通过一个表格来看看是如何联系的:

SetDataFormat中指定的数据格式

GetDeviceState中对应的缓冲区结构体

c_dfDIMouse

DIMOUSESTATE结构体

c_dfDIMouse2

DIMOUSESTATE2结构体

c_dfDIKeyboard

大小为256个字节的数组

c_dfDIJoystick

DIJOYSTATE结构体

c_dfDIJoystick2

DIJOYSTATE2结构体

比如,我们先调用了SetDataFormat设置了设备的数据格式为c_dfDIMouse:

g_pMouseDevice->SetDataFormat(&c_dfDIMouse);

那么我们在读取设备信息的时候调用GetDeviceState就需要把第二个参数填与dfDIMouse对应的DIMOUSESTATE结构体的一个实例:

DIMOUSESTATE dimouse

g_pMouseDevice-> GetDeviceState( sizeof(dimouse),(LPVOID)&dimouse);

对此,我们可以抽象出一个函数,专门对付疑难杂症,应对各种类型的设备的数据读取,而且还考虑到了设备如果丢失掉了,在合适的时间自动重新获取该设备:

1 //*****************************************************************************************

2

3 // Name: Device_Read();

4

5 // Desc: 智能读取设备的输入数据

6

7 //*****************************************************************************************

8

9 BOOL Device_Read(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize)

10

11 {

12

13 HRESULThr;

14

15 while( true)

16

17 {

18

19 pDIDevice->Poll(); // 轮询设备

20

21 pDIDevice->Acquire(); // 获取设备的控制权

22

23 if(SUCCEEDED(hr = pDIDevice->GetDeviceState(lSize, pBuffer))) break;

24

25 if(hr !=DIERR_INPUTLOST || hr != DIERR_NOTACQUIRED) return FALSE;

26

27 if(FAILED(pDIDevice->Acquire())) return FALSE;

28

29 }

30

31 returnTRUE;

32

33 }

到这一步之后,就是调用一下Device_Read来读取数据了。调用之后,我们的键位数据其实就存在了g_pKeyStateBuffer之中,我们接下来要做的就是用if语句对g_pKeyStateBuffer数组中对应的键位进行试探,看看这个键是否被按下了。如果按下,就进行相关的处理就可以了,比如:

1 Device_Read(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer));

2

3

4

5 if (g_pKeyStateBuffer[DIK_A] & 0x80)

6

7 fPosX -= 0.005f;

当然,在最后,使用完输入设备后,必须调用IDirectInputDevice8接口的Unacquire方法释放设备的控制权,所谓的杯酒释兵权,且需要接着调用Release方法释放掉设备接口对象。

g_pMouseDevice->Unacquire();

g_pMouseDevice->Release();

四、精炼:DirectInput使用五步曲

上面讲解了洋洋洒洒七千字,信息量有些大,为了突出下重点,落实到一个字“用”上,让大家有的放矢,快速掌握DirectInput的使用方法。浅墨在这里依旧是来一个使用几步曲的归纳,主要以代码为载体,把上面讲得知识归纳一下。这回的DirectInput同样是五步曲。需要说明下的是,下面的代码是关于处理键盘消息的,而对于鼠标设备,需要改的地方非常少,也就是在第一步调用CreateDevice方法时GUID填GUID_SysKeyboard,然后在第二步SetDataFormat中填c_dfDIKeyboard就可以了(相关知识上面我们有详细讲到)。对于其他设备。依然是改这两个地方,其他设备的GUID用EnumDevices枚举一下就知道了,废话也不多说,下面就开始DirectInput使用五步曲的讲解:

这五步曲分别是:

一、创键DirectInput接口和设备,简称创设备

二、设置数据格式和协作级别,简称设格式

三、获取设备控制权,简称拿权力

四、获取按键情况并做响应,简称取按键

五、释放控制权和接口对象,简称释对象

DirectInput使用五步曲载体代码:

1 //首先是全局变量的定义

2

3 LPDIRECTINPUTDEVICE8 g_pKeyboardDevice = NULL;

4

5 char g_pKeyStateBuffer[ 256] ={ 0};

6

7 //--------------------------------------------------------------------------------------—----------------

8

9 //【DirectInput使用五步曲之一】,创键DirectInput接口和设备,简称创设备

10

11 //--------------------------------------------------------------------------------------—----------------

12

13

14

15 //创建DirectInput设备

16

17 DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8,( void**)&g_pDirectInput, NULL);

18

19 g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice, NULL);

20

21 //--------------------------------------------------------------------------------------—----------------

22

23 //【DirectInput使用五步曲之二】,设置数据格式和协作级别,简称设格式

24

25 //--------------------------------------------------------------------------------------—----------------

26

27 //设置数据格式和协作级别

28

29 g_pKeyboardDevice->SetDataFormat(&c_dfDIKeyboard);

30

31 g_pKeyboardDevice->SetCooperativeLevel(hwnd,DISCL_FOREGROUND |DISCL_NONEXCLUSIVE);

32

33

34

35 //--------------------------------------------------------------------------------------—----------------

36

37 //【DirectInput使用五步曲之三】,.获取设备控制权,简称拿权力

38

39 //--------------------------------------------------------------------------------------—----------------

40

41 g_pKeyboardDevice->Acquire();

42

43

44

45 //--------------------------------------------------------------------------------------—----------------

46

47 //【DirectInput使用五步曲之四】,.获取按键情况并做响应,简称取按键

48

49 //--------------------------------------------------------------------------------------—----------------

50

51

52

53

54

55 // 读取键盘输入

56

57 ::ZeroMemory(g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer));

58

59 Device_Read(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer));

60

61 //定义的全局函数

62

63 BOOL Device_Read(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize)

64

65 {

66

67 HRESULThr;

68

69 while( true)

70

71 {

72

73 pDIDevice->Poll(); // 轮询设备

74

75 pDIDevice->Acquire(); // 获取设备的控制权

76

77 if(SUCCEEDED(hr = pDIDevice->GetDeviceState(lSize, pBuffer))) break;

78

79 if(hr !=DIERR_INPUTLOST || hr != DIERR_NOTACQUIRED) return FALSE;

80

81 if(FAILED(pDIDevice->Acquire())) return FALSE;

82

83 }

84

85 returnTRUE;

86

87 }

88

89 //然后就是用if判断并做响应了,如下面一句代码

90

91 if (g_pKeyStateBuffer[DIK_A] & 0x80)fPosX -= 0.005f;

92

93

94

95 //--------------------------------------------------------------------------------------—----------------

96

97 //【DirectInput使用五步曲之五】,.释放控制权和接口对象,简称释对象

98

99 //--------------------------------------------------------------------------------------—----------------

100

101 g_pKeyboardDevice->Unacquire();

102

103 SAFE_RELEASE(g_pKeyboardDevice)

所以,上述DirectInput使用五步曲精炼总结起来就十五个字:

创设备,设格式,拿权力,取按键,释对象

五、DirectInput键盘按键键值总结

与一般的Windows应用程序相比,DirectInput处理键盘事件的方式是有很多独特之处的。首先,在我们写的游戏程序中,键盘主要并不是用于文字输入的,而是用于控制3D世界中人物,对象的运动,或者视角的变换等等。且在游戏程序中我们常常只需要知道具体是哪个键被按下,而忽略了该键所对应的字符。所以我们只需读取已按下键的扫描码就可以了。

另外,为了提高程序运行的效率,DirectInput并非使用Windows中的消息机制来读取键盘的状态,而是直接读取硬件的状态获取按键的扫描码的。

我们在按照流程创建好和打理好DirectInput之后,就能在程序中不断获取从键盘输入的那些键盘数据。而在程序中,我们需要定义一个大小为256字节的数组,其中的每一个字节都存储一个按键的状态,这样就可以保存256个按键的状态信息了。微软在DirectInput中为每个键都设置了一个对应的宏,这些宏都是以DIK_为前缀的。例如C键就定义为DIK_C,主键盘数字键8就对应DIK_8等等,下面就是浅墨对DirectInput键码做的一个总结表格,查起来非常方便:

DirectInput键码

说明

DIK_0~ DIK_9

主键盘上数字键0~9

DIK_A~ DIK_Z

字母键A~Z

DIK_F1~ DIK_F15

功能键F1~F15

DIK_NUMPAD0~ DIK_NUMPAD9

小键盘0~9

DIK_ESCAPE,DIK_TAB, DIK_BACK

Esc键,Tab键,退格键

DIK_RETURN,DIK_SPACE,DIK_NUMBERENTER

回车键、空格键、小键盘回车键,

DIK_LSHIFT, DIK_RSHIFT

左右Shift键

DIK_LMENU, DIK_RMENU

左右菜单键

DIK_LALT, DIK_RALT

左右Alt键

DIK_LCONTROL, DIK_RCONTROL

左右Ctrl键

DIK_UPARROW, DIK_DOWNARROW

DIK_LEFTARROW, DIK_RIGHTARROW

上下左右方向键

DIK_HOME, DIK_DELETE, DIK_INSERT

Home键,Delete键,Insert键

DIK_PRIOR, DIK_NEXT, DIK_END

PageUp键,PageDown键,End键

比如我们要检测左Alt键是否按下,按下的话就做出响应,就可以在上表中找到左Alt键的键码为DIK_LALT,然后就是一句if( ){ }语句:

if (g_pKeyStateBuffer[DIK_A] & 0x80) fPosX -= 0.005f;

六、DirectInput鼠标按键键值总结

在通常的Windows应用程序中,系统检测鼠标的移动并通过消息处理函数将鼠标的移动作为消息报告给用户,然而这样做的效率非常低下,因为传递给消息处理函数的每个消息首先都要走消息队列这条“官道”,需要慢悠悠地在消息队列中排队,排队完全满足不了我们对游戏即时处理消息的要求。而在Direct3D中,咱们就可以屌丝逆袭走后门了,我们可以直接同鼠标的驱动程序进行交互,而不用走消息队列这条慢悠悠的“官道”。

另外,我们有两种方式来跟踪鼠标的移动为:绝对模式和相对模式。在绝对模式下,鼠标是基于某个固定点的,这个点通常是屏幕左上角,而此时返回的鼠标坐标是鼠标指针所处位置在屏幕坐标系中的坐标。

而另外一种模式,也就是相对模式下,鼠标坐标则是根据上一个已知位置到当前位置所发生的移动量来得到鼠标的坐标值的。在相对模式下得到的鼠标坐标是一个相对位置,而非绝对位置,大家需要注意。

好了,回到正题上来。在DirectInput中,鼠标的移动信息我们通常都是通过一个名叫DIMOUSESTATE结构体来记录的,我们可以在MSDN中查到这个结构体定义如下:

1 typedef struct DIMOUSESTATE {

2

3 LONG lX;

4

5 LONG lY;

6

7 LONG lZ;

8

9 BYTE rgbButtons[ 4];

10

11 } DIMOUSESTATE, *LPDIMOUSESTATE;

这个结构体中,lX,lY,lZ分别记录了X轴,Y轴和Z轴(鼠标滚轮的相对移动量,鼠标没移动的话,他们的值就是0.)。而结构体中的第四个参数rgbButtons[4]记录了四个按钮的状态信息,其中rgbButtons[0]代表鼠标左键,rgbButtons[1]对应鼠标右键。如果需要处理支持更多按钮的鼠标的话,就去用DIMOUSESTATE2结构体吧。

下面我们来看看实例:

1 DIMOUSESTATE g_diMouseState = { 0};

2

3 ::ZeroMemory(&g_diMouseState, sizeof(g_diMouseState));

4

5 Device_Read(g_pMouseDevice,(LPVOID)&g_diMouseState, sizeof(g_diMouseState));

6

7

8

9 //按住鼠标左键并拖动,为平移操作

10

11 staticFLOAT fPosX = 0.0f, fPosY = 30.0f, fPosZ = 0.0f;

12

13 if(g_diMouseState.rgbButtons[ 0] & 0x80)

14

15 {

16

17 fPosX+=g_diMouseState.lX * 0.08f;

18

19 fPosY+=g_diMouseState.lY * -0.08f;

20

21 }

七、详细注释的源代码欣赏

首先需要说明的是,本篇文章配套的源代码中用到了我们目前还未讲到的一点技术,就是X模型的载入。源代码中X模型的载入相关的代码大家如果看不懂没关系,请锁定浅墨的博客,后面一定会有相关技术精彩的讲解的。

然后这篇文章中的demo我们对细节部分做了升级,新加了三个功能,他们分别是: