为了实现利用体感进行方向盘模拟,我们首先要明白,方向盘是一个线性的虚拟手柄,因为玩家在进行不同的旋转操作的时候,会有不同的角度,在这种情况下,我们需要反映成游戏中不同的拐弯角度。而不是像按键手柄,一个按钮实现一个功能。在这里我们使用线性手柄的轴度,模拟线性方向盘的感觉,但是在这里我们只实现与虚拟手柄的通信,而不是创建虚拟手柄。
在这里我们使用四轴虚拟线性手柄,但是因为只需要两个轴,所以我们只用两个。一个控制转角,一个控制油门。
这里的难点其实在于如何去与虚拟线性手柄进行通讯,这里,我们使用了USBHID,也就是一种USB通讯协议,我们常用的鼠标,键盘,手柄之类的外设一般通过这个来进行驱动,从而进行控制。USB HID类是比较大的一个类,HID类设备属于人机交互操作的设备。用于控制计算机操作的一些方面,如USB鼠标,USB键盘,USB游戏操纵杆,USB触摸板,USB轨迹球、电话拨号设备、VCR遥控等等设备。另外,使用HID设备的一个好处就是,操作系统自带了HID类的驱动程序,而用户无需去开发很麻烦的驱动程序,只要直接使用API调用即可完成通信。所以很多简单的USB设备,喜欢枚举成HID设备,这样就可以不用安装驱动而直接使用。
具体关于USBHID的详细信息,可以参考博文http://blog.youkuaiyun.com/leo_wonty/article/details/6721214。
但是我们如何进行USBHID的通讯,在这里我们使用的是DDK,win DDK是微软官方出的专门用于驱动编写的sdk。下载与安装都很简单,https://www.microsoft.com/en-us/download/details.aspx?id=11800在这里下载,下载之后点击setup的那个就可以了。然后就安装好了。当然,由于我们要实现的功能不需要那么多库文件,所以我们也可以单独下载所需要的头文件和链接库。
接下来是代码部分:
HANDLE OpenContorlDevice()
{
HDEVINFO hardwareDeviceInfo;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
SP_DEVICE_INTERFACE_DATA devInfoData;
GUID hidguid;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData=NULL;
DWORD predictedLength=0;
DWORD requiredLength=0;
HANDLE file=INVALID_HANDLE_VALUE;
PHIDP_PREPARSED_DATA Ppd=NULL;
HIDD_ATTRIBUTES Attributes;
HIDP_CAPS Caps;
int i;
HidD_GetHidGuid(&hidguid);
hardwareDeviceInfo=SetupDiGetClassDevs((LPGUID)&hidguid,NULL,NULL,(DIGCF_PRESENT|DIGCF_INTERFACEDEVICE));
if (INVALID_HANDLE_VALUE==hardwareDeviceInfo)
{
printf("SertupDiGetClassDevs failed:",GetLastError());
goto cleanup;
}
deviceInterfaceData.cbSize=sizeof(SP_DEVICE_INTERFACE_DATA);
devInfoData.cbSize=sizeof(SP_DEVINFO_DATA);
for (i=0;SetupDiEnumDeviceInterfaces(hardwareDeviceInfo,0,(LPGUID)&hidguid,i,&deviceInterfaceData);i++)
{
SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo,&deviceInterfaceData,NULL,0,&requiredLength,NULL);
predictedLength=requiredLength;
deviceInterfaceDetailData=(PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(predictedLength);
if (!deviceInterfaceDetailData)
{
printf("Error: OpenDeviceInterface:malloc faild\n");
goto cleanup;
}
deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo,&deviceInterfaceData,deviceInterfaceDetailData,predictedLength,&requiredLength,NULL))
{
printf("Error: SetupDiGetInterfaceDeviceDetail failed: %x\r\n",GetLastError());
goto cleanup;
}
file=CreateFile(deviceInterfaceDetailData->DevicePath,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
if (INVALID_HANDLE_VALUE==file)
{
printf("Error:Creat file failed:%d\r\n",GetLastError());
CloseHandle(file);
}
else
{
if (!HidD_GetPreparsedData(file,&Ppd))
{
printf("Error: HidD_GetPreparsedData failed\r\n");
file=INVALID_HANDLE_VALUE;
}
else
{
if (!HidD_GetAttributes(file,&Attributes))
{
printf("Error: HidD_GetAttributes failed\r\n");
file=INVALID_HANDLE_VALUE;
}
else
{
if (Attributes.VendorID==0x045E&&Attributes.ProductID==0x02AE)
{
if (!HidP_GetCaps(Ppd,&Caps))
{
printf("Error:HidP_GetCaps failed\r\n");
file=INVALID_HANDLE_VALUE;
}
else
{
if ((Caps.UsagePage==0x09)&&(Caps.Usage==0x00))
{
printf("Success:Found my device..\r\n");
goto cleanup;
}
}
}
else
{
printf("Error: VendorID or ProductID is not what we want\r\n");
file=INVALID_HANDLE_VALUE;
}
}
}
}
free(deviceInterfaceDetailData);
}
cleanup:
if (Ppd!=NULL)
{
HidD_FreePreparsedData(Ppd);
}
SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
return file;
}
在这里,我大概说一下这些代码所进行的流程,总的来说就是枚举设备,再打开设备,由于在这个过程中涉及到设备的描述符等参数,大家可以稍微了解一下。要与USB设备通信,就要先获取你的USB设备类的 GUID标识,然后再根据GUID来枚举该类的设备,然后从中找到你的设备。
首先,获取本系统中HID类的GUID标识,准备查找符合HID规范的USB设备,若找到了一个USB设备,则找到设备后则以读写的方式打开以进行操作,获取该设备的细节信息,然后根据VendorID 和 ProductID判断是否是目标设备。在这里我们需要注意的就是
VendorID 和 ProductID以及USAGE和USAGEPAPER这四个参数,一定要对应好虚拟手柄上的设备参数。
所需要的头文件为:
#include <stdio.h>
#include <windows.h>
extern "C" {
#include "hidsdi.h"
#include "setupapi.h"
}
#pragma comment (lib,"hid.lib")
#pragma comment (lib,"setupapi.lib")
#pragma comment (lib,"kernel32.lib")