(第一次写博客,还望大家批评指正!!!)
法拓士/Thrustmaster Hotas Warthog 猪肝/猪杆/A10C/疣猪飞行操作杆(最顶尖的飞行摇杆)
猪杆是美国空军授权按照美国A10C攻击机的操作杆1:1的比例仿制,全金属双手飞行控制器,总重3公斤。
更具体的信息请移步官网:猪杆官网
现在,进入正题,看看怎么对 猪杆 进行开发。首先要强调的是,对操作杆的开发无非就是将其各种按钮、轴的状态读取回来,以作后续的控制使用。
本文重点分析如何在Windows下对猪杆进行开发(其他游戏操作杆或设备类似)。在Windows下对标准设备进行开发有多种方法,这里重点分析在Windows下通过Directinput来开发。
Directinput是微软专门为了Windows下的各种输入设备开发的一组API,DirectInput同时属于DirectX的组件之一,是一些COM对象的集合,主要针对各种键盘、鼠标、操作杆(joystick)、触摸板、力反馈设备、方向盘以及第一人称射击设备(DI8DEVTYPE_*)等。
接下来,就进入主题。
一、介绍一下Directinput的整体结构:
DirectInput主要由IDirectInput8、IDirectInputDevice8,IDirectInputEffect这三个接口组成,这三个接口中又分别含有各自的方法(Methods)。
DirectInputAPI中总共有三个接口,四十七个方法。
1.IDirectInput8接口函数(8个):
IDirectInput8::ConfigureDevices Method
IDirectInput8::CreateDevice Method
IDirectInput8::EnumDevices Method
IDirectInput8::EnumDevicesBySemantics Method
IDirectInput8::FindDevice Method
IDirectInput8::GetDeviceStatus Method
IDirectInput8::Initialize Method
IDirectInput8::RunControlPanel Method
2.IDirectInputDevice8接口函数(29个):
IDirectInputDevice8::Acquire Method
IDirectInputDevice8::BuildActionMap Method
IDirectInputDevice8::CreateEffect Method
IDirectInputDevice8::EnumCreatedEffectObjects Method
IDirectInputDevice8::EnumEffects Method
IDirectInputDevice8::EnumEffectsInFile Method
IDirectInputDevice8::EnumObjects Method
IDirectInputDevice8::Escape Method
IDirectInputDevice8::GetCapabilities Method
IDirectInputDevice8::GetDeviceData Method
IDirectInputDevice8::GetDeviceInfo Method
IDirectInputDevice8::GetDeviceState Method
IDirectInputDevice8::GetEffectInfo Method
IDirectInputDevice8::GetForceFeedbackState Method
IDirectInputDevice8::GetImageInfo Method
IDirectInputDevice8::GetObjectInfo Method
IDirectInputDevice8::GetProperty Method
IDirectInputDevice8::Initialize Method
IDirectInputDevice8::Poll Method
IDirectInputDevice8::RunControlPanel Method
IDirectInputDevice8::SendDeviceData Method
IDirectInputDevice8::SendForceFeedbackCommand Method
IDirectInputDevice8::SetActionMap Method
IDirectInputDevice8::SetCooperativeLevel Method
IDirectInputDevice8::SetDataFormat Method
IDirectInputDevice8::SetEventNotification Method
IDirectInputDevice8::SetProperty Method
IDirectInputDevice8::Unacquire Method
IDirectInputDevice8::WriteEffectToFile Method
3.IDirectInputEffect接口函数(10个):
IDirectInputEffect::Download Method
IDirectInputEffect::Escape Method
IDirectInputEffect::GetEffectGuid Method
IDirectInputEffect::GetEffectStatus Method
IDirectInputEffect::GetParameters Method
IDirectInputEffect::Initialize Method
IDirectInputEffect::SetParameters Method
IDirectInputEffect::Start Method
IDirectInputEffect::Stop Method
IDirectInputEffect::Unload Method
其中,IDirectInput8是DirectInput API中最主要的接口,用于初始化系统以及创建输入设备接口,DirectInput中其他的所有的接口都需要依赖于IDirectInput8,都是通过这个接口进行查询的。而DirectInputDevice8接口用于表示各种输入设备(如键盘、鼠标和游戏杆),并提供了相同的访问和控制方法(相当于对所有设备增加了一个通用抽象层)。对于某些输入设备(如游戏杆和鼠标),都能通过查询各自的IDirectInputDevice8接口对象,得到另一个接口IDirectInputEffect8。而IDirectInputEffect8接口则用于控制设备的力反馈效果。
二、DirectInput使用步骤详解(引自 浅墨_毛星云 http://blog.csdn.net/poem_qianmo):
1.头文件和库文件的包含
我们首先需要注意的是,在使用DirectInput时,需要保证我们包含了DInput.h头文件,并且在项目中已经链接了DInput8.lib库文件。
当然,库文件我们也可以动态添加:
#pragma comment(lib, "dinput8.lib") // 使用DirectInput必须包含的头文件,注意这里有8
2.创建DirectInput接口和设备
在DirectInput中我通过们调用DirectInputCreate函数创建并初始化IDirectInput接口,我们可以在MSDN中查到该函数的声明如下:
HRESULT DirectInput8Create(
HINSTANCE hinst,
DWORD dwVersion,
REFIID riidltf,
LPVOID * ppvOut,
LPUNKNOWN punkOuter
)
■ 第一个参数,HINSTANCE类型的hinst,表示我们当前创建的DirectInput的Windows程序句柄,这个值填我们在WinMain函数的参数中的实例句柄就可以了。
■ 第二个参数,DWORD类型的dwVersion,表示我们当前使用的DirectInput版本号,通常可以取DIRECTINPUT_VERSION或者DIRECTINPUT_HEADER_VERSION,这两个值对应的是同一个值,为0x0800。所以我们在这里还可以直接填0x0800。
归根揭底的话,可以通过【转到定义】大法在dinput.h中查到有如下代码:
#define DIRECTINPUT_HEADER_VERSION 0x0800
#ifndef DIRECTINPUT_VERSION
#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接口对象是否创建成功了。
下面是一个调用的例子:
// 创建DirectInput设备
LPDIRECTINPUT8 g_pDirectInput = NULL;
if(FAILED(DirectInput8Create(hInstance,0x0800, IID_IDirectInput8,(void**)&g_pDirectInput, NULL)))
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中查到这个方法有如下声明:
HRESULTEnumDevices(
DWORD dwDevType,
LPDIENUMDEVICESCALLBACKlpCallback,
LPVOID pvRef,
DWORD dwFlags
)
■ 第一个参数,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方法的声明如下:
HRESULTCreateDevice(
REFGUID rguid,
LPDIRECTINPUTDEVICE*lplpDirectInputDevice,
LPUNKNOWN pUnkOuter
)
■ 第一个参数,REFGUID类型的rguid,就是填我们上面讲到的输出设备的GUID。系统中当前使用的键盘对应GUID_SysKeyboard,当前使用的鼠标对应GUID_SysMouse。其他设备的话,就用我们刚刚讲过的EnumDevices获取一下就行了。
■ 第二个参数,LPDIRECTINPUTDEVICE类型的*lplpDirectInputDevice,表示我们所创建的输入设备对象的指针地址,可以说调用这个CreateDevice参数就是在初始化这个参数。
■ 第三个参数,LPUNKNOWN类型的pUnkOuter,、和COM对象的IUnknown接口相关的一个参数,一般我们不去管它,设为NULL就可以了。
讲解完了,当然得看一个调用实例。下面的代码中CreateDevice方法的第二个参数我们填的是GUID_SysMouse,所以我们在为系统鼠标创建一个DirectInput设备接口对象:
LPDIRECTINPUTDEVICE8 g_pMouseDevice = NULL;
if(FAILED (g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice,NULL)))
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);
设备的特殊属性包含设备的数据模式、缓冲区大小、以及设备的最小最大范围等等。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);
对此,我们可以抽象出一个函数,专门对付疑难杂症,应对各种类型的设备的数据读取,而且还考虑到了设备如果丢失掉了,在合适的时间自动重新获取该设备:
//*****************************************************************************************
// Name: Device_Read();
// Desc: 智能读取设备的输入数据
//*****************************************************************************************
BOOL Device_Read(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize)
{
HRESULThr;
while(true)
{
pDIDevice->Poll(); // 轮询设备
pDIDevice->Acquire(); // 获取设备的控制权,可以防止窗口重新到前台后对设备的丢失
if(SUCCEEDED(hr = pDIDevice->GetDeviceState(lSize, pBuffer))) break;
if(hr !=DIERR_INPUTLOST || hr != DIERR_NOTACQUIRED) return FALSE;
if(FAILED(pDIDevice->Acquire())) return FALSE;
}
returnTRUE;
}
到这一步之后,就是调用一下Device_Read来读取数据了。调用之后,我们的键位数据其实就存在了g_pKeyStateBuffer之中,我们接下来要做的就是用if语句对g_pKeyStateBuffer数组中对应的键位进行试探,看看这个键是否被按下了。如果按下,就进行相关的处理就可以了,比如:
Device_Read(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer,sizeof(g_pKeyStateBuffer));
if (g_pKeyStateBuffer[DIK_A] & 0x80)
fPosX -= 0.005f;
当然,在最后,使用完输入设备后,必须调用IDirectInputDevice8接口的Unacquire方法释放设备的控制权,所谓的杯酒释兵权,且需要接着调用Release方法释放掉设备接口对象。
g_pMouseDevice->Unacquire();
g_pMouseDevice->Release();
在这里引用浅墨总结的DirectInput使用五步曲(创设备,设格式,拿权力,取按键,释对象):
1、创键DirectInput接口和设备,简称创设备
2、设置数据格式和协作级别,简称设格式
3、获取设备控制权,简称拿权力
4、获取按键情况并做响应,简称取按键
5、释放控制权和接口对象,简称释对象
注:对于鼠标或者键盘设备在第一步调用CreateDevice方法时GUID对应的应该是GUID _Mouse或GUID_SysKeyboard,然后在第二步SetDataFormat中填c_dfDIMouse或c_dfDIKeyboard就可以了。对于其他设备。依然是改这两个地方,其他设备的GUID用EnumDevices枚举一下就知道了(后面介绍枚举)。
三、DirectInput在猪杆开发中的使用:
1、本例程没有选择MFC开发,而是直接利用图形设备接口函数
#define DIRECTINPUT_VERSION 0x0800 //库版本选择
#include
#include
#include
#include "resource.h"
#pragma comment(lib, "dxguid.lib") //动态添加库
#pragma comment(lib, "dinput8.lib")
#pragma warning(disable : 4996) //将某个警报置为失效
#define Safe_Release(p) if((p)) (p)->Release();//释放宏定义
接下来声明要用到的DirectInput各个组件或接口
IDirectInput8* g_di; // directinput 组件
IDirectInputDevice8* g_enum_joystick; // enum joystick device 枚举设备接口
IDirectInputDevice8* g_joystick; // joystick device 设备接口
创建用来显示数据的窗口之后,就调用函数DirectInput8Create来创建DirectInput接口:
hr = DirectInput8Create(inst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void **) &g_di, NULL);
//其中hr是函数DirectInput8Create返回来的句柄
接口创建好之后就是对其初始化(枚举设备、设置数据格式、配置协作级别以及设备的特殊属性包含设备的数据模式、缓冲区大小、还有设备的最小最大范围等等),同时,将枚举到并且初始化好了的设备接口传递给提前声明好了的设备接口对象。
g_joystick = init_joystick(g_hwnd, g_di);
下面是init_joystick函数的定义,封装好了对接口的所有配置。
//--------------------------------------------------------------------------------
// Initialize joystick interface, return a joystick interface pointer.
//--------------------------------------------------------------------------------
IDirectInputDevice8* init_joystick(HWND hwnd, IDirectInput8* di)
{
HRESULT hr;
g_di->EnumDevices(DI8DEVTYPE_JOYSTICK, enum_joysticks, NULL,DIEDFL_ALLDEVICES);
//DI8DEVTYPE_JOYSTICK是要匹配的设备类型,包括方向盘、操作杆、脚踏板、第一人称射击设备还有基于模型飞机遥控飞行装置等
//匹配成功则调用回调函数enum_joysticks;第三个参数是返回我们当前匹配设备的GUID值;第四个参数是指定我们枚举设备的方式
//if(hr == DI_OK)
// MessageBox(g_hwnd, "g_di->EnumDevices_OK!", "OK", MB_OK);
// everything was a success, return the pointer.
return g_enum_joystick;
}
下面是回调函数enum_joysticks的定义,当枚举到对应的设备之后,就调用对应的回调函数来对其进行初始化配置。
//--------------------------------------------------------------------------------
// Enumerate all attached joysticks, return first enumerated joystick.
//--------------------------------------------------------------------------------
//LPCDIDEVICEINSTANCE device_inst此结构体包含了此次调用时当前枚举设备上的信息
//其中DWORD dwDevType是设备类型包括HID(DIDEVTYPE_HID)方向盘、操作杆、脚踏板、第一人称射击设备等
//还有基于模型飞机遥控飞行装置(DI8DEVTYPEFLIGHT_RC)
BOOL CALLBACK enum_joysticks(LPCDIDEVICEINSTANCE device_inst, LPVOID ref)
{
DIPROPRANGE prop_range;
DIPROPDWORD prop_dword;
g_enum_joystick = NULL;
// create the device object using global directinput object
if(FAILED(g_di->CreateDevice(device_inst->guidInstance, &g_enum_joystick, NULL)))
//调用IDirectInput8::CreateDevice 创建全局的设备COM对象
return DIENUM_CONTINUE;
// set the data format
if(FAILED(g_enum_joystick->SetDataFormat(&c_dfDIJoystick2)))//设置数据格式
goto fail;
// set the cooperative mode
if(FAILED(g_enum_joystick->SetCooperativeLevel(g_hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
//设置协作等级
goto fail;
// clear out the structure first
ZeroMemory(&prop_range, sizeof(DIPROPRANGE));//清除缓冲区
//属性结构体填充
prop_range.diph.dwSize = sizeof(DIPROPRANGE);
prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop_range.diph.dwHow = DIJOFS_X;
prop_range.diph.dwHow = DIPH_BYOFFSET; // offset into data format
prop_range.lMin = -1024;
prop_range.lMax = 1024;
// Sets properties that define the device behavior.
// These properties include input buffer size and axis mode.
HRESULT rv = g_enum_joystick->SetProperty(DIPROP_RANGE, &prop_range.diph);
//设置其他属性
if(FAILED(rv))
{
if(rv == DIERR_INVALIDPARAM)
MessageBox(NULL, "invalid param", NULL, MB_OK);
else if(rv == DIERR_NOTINITIALIZED)
MessageBox(NULL, "not initialize", NULL, MB_OK);
else if(rv == DIERR_OBJECTNOTFOUND)
MessageBox(NULL, "object not found", NULL, MB_OK);
else if(rv == DIERR_UNSUPPORTED)
MessageBox(NULL, "unsopported", NULL, MB_OK);
goto fail;
}
prop_range.diph.dwObj = DIJOFS_Y;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_RANGE, &prop_range.diph)))
goto fail;
// set x deadzone to 15%
prop_dword.diph.dwSize = sizeof(DIPROPDWORD);
prop_dword.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop_dword.diph.dwHow = DIPH_BYOFFSET;
prop_dword.diph.dwObj = DIJOFS_X;
prop_dword.dwData = 1500;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_DEADZONE, &prop_dword.diph)))
goto fail;
// set Y deadzone
prop_dword.diph.dwObj = DIJOFS_Y;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_DEADZONE, &prop_dword.diph)))
goto fail;
// acquire the device for use
if(FAILED(g_enum_joystick->Acquire()))//获取控制权
goto fail;
// stop enumeration
return DIENUM_STOP;
fail:
g_enum_joystick->Release();
g_enum_joystick = NULL;
return DIENUM_CONTINUE;
}
到此,已经完成了设备接口的初始化,接下来就是重点了,那就是读取设备状态。
read_joystick(&joy_state, sizeof(DIJOYSTATE2));
其定义如下:
//--------------------------------------------------------------------------------
// Read joystick buffer.
//--------------------------------------------------------------------------------
BOOL read_joystick(void* buffer, long buffer_size)
{
HRESULT rv;
while(1)
{
// poll device
g_joystick->Poll();
g_joystick->Acquire();//保证窗口切换之后依然可以连接设备
// read in state
if(SUCCEEDED(rv = g_joystick->GetDeviceState(buffer_size, buffer)))
break;
// return when an unknown error
if(rv != DIERR_INPUTLOST || rv != DIERR_NOTACQUIRED)
return FALSE;
// re-acquire and try again
if(FAILED(g_joystick->Acquire()))
return FALSE;
}
return TRUE;
}
将读到的设备状态信息显示出来的代码如下:
if(g_joystick != NULL)//判断初始化返回的设备接口指针是否为空
{
// start message pump, waiting for signal to quit.
ZeroMemory(&msg, sizeof(MSG));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// read in mouse and display coordinates
read_joystick(&joy_state, sizeof(DIJOYSTATE2));
if(is_first_render || (joystick_x != joy_state.lX || joystick_y != joy_state.lY))
{
is_first_render = FALSE;
sprintf(text, "%ld, %ld", joy_state.lX, joy_state.lY);
SetWindowText(GetDlgItem(g_hwnd, IDC_COORDINATES), text);
}
joystick_x = joy_state.lX;
joystick_y = joy_state.lY;
}
}
else
{
MessageBox(g_hwnd, "No Joysticks!", "Error", MB_OK);
return 0;
}
一定不能忘了这最后的一步,也是很重要的,那就是释放设备COM对象接口。
// release directinput objects
g_joystick->Unacquire();
g_joystick->Release();
g_di->Release();
至此,就完成了对猪杆的简单开发。但是本例程只显示了X、Y轴的状态信息,而所有的状态信息都在DIJOYSTATE2 类型的joy_state结构体中。可以任意的蹂躏她了。。。。。。
对于更多状态的信息显示后面有时间再作补充,因为图形设备接口函数’windows.h’的开发方法与MFC有一定的区别,尤其是在窗口中添加控件的方法完全不同(虽然原理相通)。
第一次写Blog,还望大家海涵。。。