使用Raw Input API处理操纵杆输入(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/using-the-raw-input-api-to-process-joystick-input-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 12 分钟阅读 - 5889 个词 阅读量 0使用Raw Input API处理操纵杆输入(译文)
原文地址:https://www.codeproject.com/Articles/185522/Using-the-Raw-Input-API-to-Process-Joystick-Input
原文作者:Alexander Böcken
译文由本站 robot-v1.0 翻译
前言
A basic way to interpret joystick data received from Raw Input API
解释从Raw Input API接收到的操纵杆数据的基本方法
介绍(Introduction)
微软不鼓励使用(Microsoft discourages the use of) 直接输入(DirectInput) 用于游戏中的键盘和鼠标输入,但仍建议处理来自旧游戏杆或其他游戏控制器的数据.新应用程序应使用(for keyboard and mouse input in games, but it is still recommended to process data from a legacy joystick, or other game controller. New applications should use the) 原始输入API(Raw Input API) 即利用计算机鼠标以800 DPI甚至更高的速度生成数据.此外,微软推出了(, i.e., to take advantage of computer mice generating data at 800 DPI or even more. Additionally, Microsoft introduced) X输入(XInput) 允许应用程序从Xbox 360控制器接收输入.因此,游戏开发人员在设计输入系统时必须应对多达三种不同的API.(to allow applications to receive input from the Xbox 360 Controller. Hence, game developers have to cope with up to three different APIs when designing their input system.)
在本文中,我将展示如何使用Raw Input API处理操纵杆/游戏手柄数据,至少使用(In this article, I will show how to use the Raw Input API to process joystick/gamepad data, making at least the use of) DirectInput
过时的(obsolete.)
背景(Background)
在Windows XP中,Microsoft引入了Raw Input API,以支持除传统键盘和鼠标之外的其他人机接口设备(HID).应用程序可以使用此API从可以想象的任何HID获取数据.但是,除了键盘和鼠标输入外,该API没有公开任何结构或函数来解释来自设备的数据,因此对于希望利用大量可用的不同游戏杆和游戏手柄的游戏应用程序而言,该API毫无用处. Raw Input API文档没有提及如何获取有价值的信息,例如按钮数量,轴数,帽子开关的存在等. (我想这就是为什么它被称为"原始" ;-))(With Windows XP, Microsoft introduced the Raw Input API to support other Human Interface Devices (HIDs) than the traditional keyboard and mouse. An application can use this API to get data from any HID one can imagine. But aside from keyboard and mouse input, the API exposes no structures or functions to interpret the data coming from the devices, thus making it useless to game applications that want to make use of the vast number of different joysticks and gamepads available. The Raw Input API documentation does not mention how to retrieve such valuable information as the number of buttons, the number of axes, the existence of a hat switch, and so on. (I guess this is why it’s called “raw” ;-) ))
在开发游戏引擎的过程中,我想增加对Logitech RumblePad 2的支持.对于鼠标和键盘输入,我已经使用过Raw Input API,因此在尝试实施第二个应用程序之前先考虑一下它似乎合乎逻辑.管理代码的分支(In the effort of developing a game engine, I wanted to add support for my Logitech RumblePad 2. For mouse and keyboard input, I have already used the Raw Input API, so it seemed logical to look at it first before trying to implement a second branch of code that manages) DirectInput
及其设备对象.最后,我想出了一个使用Raw Input API和(and its device objects. In the end, I came up with a solution using the Raw Input API and the) HIDClass
驱动程序(请参阅(driver (see) Windows驱动程序工具包中的人工输入设备(Human Input Devices in the Windows Driver Kit) ).().)
本文的以下几节将展示检索原始操纵杆数据以及如何解释它的基本方法.(The next few sections of this article will show a basic way of retrieving raw joystick data and how to interpret it.)
使用代码之前(Before Using the Code)
如前所述,示例应用程序与(As already mentioned, the sample application works closely with the) HIDClass
司机.要编译代码,必须从Microsoft网站下载Windows Driver Kit(WDK),并相应地设置项目路径.您将需要链接到(driver. To compile the code, you must download the Windows Driver Kit (WDK) from the Microsoft website and set the project paths accordingly. You will need to link to)**隐藏库(hid.lib)并且如果您遇到很多编译错误,则可能忘记了包含"(and if you get a whole lot of compile errors, you may have forgotten to include the “)\ inc \ crt(\inc\crt)**路径.(” path.)
步骤1:注册游戏杆设备(Step 1: Register for Joystick Devices)
每个使用Raw Input API的应用程序要做的第一件事是注册其有兴趣从中获取数据的设备类型.(The first thing every application using Raw Input API does is to register for the kind of devices it is interested in getting data from.)
case WM_CREATE:
{
RAWINPUTDEVICE rid;
rid.usUsagePage = 1;
rid.usUsage = 4; // Joystick
rid.dwFlags = 0;
rid.hwndTarget = hWnd;
if(!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)))
return -1;
}
return 0;
这是最简单的部分.对于操纵杆设备,我们只需要设置(This is the most simple part. For joystick devices, we only need to set) usUsagePage = 1
和(and) usUsage = 4
.术语"用法页面"和"用法"起源于(. The terms Usage Page and Usage have their origin in the) HID使用表(HID Usage Tables) USB实施者论坛.它们指定了各种类型的输入设备及其控件,即1是"通用桌面控件"页面的ID,而4是"操纵杆使用名"的ID.另外,要接收(of the USB Implementers' Forum. They specify the various types of input devices and their controls, i.e., 1 is the id of the Generic Desktop Controls Page and 4 is the id of the Joystick Usage Name. In addition, to receive the) WM_INPUT
消息,我们设置(message, we set) hwndTarget
到我们窗户的把手.(to the handle of our window.)
步骤2:检索设备数据(Step 2: Retrieve the Device Data)
在里面(In the) WM_INPUT
的情况下,我们获得一个指向(case, we obtain a pointer to the) RAWINPUT
包含操纵杆数据的结构.(structure containing the joystick’s data.)
case WM_INPUT:
{
PRAWINPUT pRawInput;
UINT bufferSize;
HANDLE hHeap;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL,
&bufferSize, sizeof(RAWINPUTHEADER));
hHeap = GetProcessHeap();
pRawInput = (PRAWINPUT)HeapAlloc(hHeap, 0, bufferSize);
if(!pRawInput)
return 0;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT,
pRawInput, &bufferSize, sizeof(RAWINPUTHEADER));
ParseRawInput(pRawInput);
HeapFree(hHeap, 0, pRawInput);
}
return 0;
GetRawInputData
转换由(converts the handle given by the) WM_INPUT
给一个消息(message to a) RAWINPUT
指针.然后,我们将此指针传递给(pointer. We then pass this pointer to) ParseRawInput
这需要大量的处理.(which does the bulk of the processing.)
的(The) RAWINPUT
结构为我们提供了设备句柄,输入类型和输入数据本身.对于鼠标和键盘输入,我们可以使用(structure provides us with the device handle, type of input and the input data itself. For mouse and keyboard input, we can use the) RAWMOUSE
和(and) RAWKEYBOARD
结构,但对于任何其他设备,我们必须使用(structures, but for any other device we have to use the) RAWHID
仅包含原始输入数据(字节数组)的结构.现在由我们来解释这个数组.(structure which just contains the raw input data, as an array of bytes. It is now up to us to interpret this array.)
步骤3:解释设备数据(Step 3: Interpret the Device Data)
在这一点上,第一个重要的观察结果是该数组实际上包含了操纵杆的状态(即我的RumblePad).如果我们按下按钮或移动其中一个轴,就会得到一个新的(At this point, the first important observation is that this array actually contains the state of our joystick (i.e., my RumblePad). If we press a button or move one of the axes, we get a new) WM_INPUT
消息和一个反映操纵杆新状态的数组.这样,我们可以分解数组并确定对应于按钮的位和对应于轴的字节.但这仅适用于我们的特殊操纵杆,而不适用于现有的大多数设备.那么,我们如何在运行时解释数据?(message and an array that reflects the new state of the joystick. That way, we could disassemble the array and determine the bits corresponding to the buttons and bytes corresponding to the axes. But this works only for our special joystick and not for most of the devices out there. So, how do we interpret the data at runtime?)
Raw Input API使我们可以使用(The Raw Input API gives us access to some device information using the) GetRawInputDeviceInfo
功能.给定原始输入设备的手柄和(function. Given the handle to the raw input device and the) RIDI_PREPARSEDDATA
命令,我们可以获得所谓的"准备好的数据". WDK文档只是说:"(command we can obtain so called “preparsed data”. The WDK documentation just says, “The internal structure of a) _HIDP_PREPARSED_DATA
结构保留供内部系统使用.“这是获取此数据块的代码:(structure is reserved for internal system use”. Here is the code to obtain this data block:)
CHECK( GetRawInputDeviceInfo(pRawInput->header.hDevice,
RIDI_PREPARSEDDATA, NULL, &bufferSize) == 0 );
CHECK( pPreparsedData = (PHIDP_PREPARSED_DATA)HeapAlloc(hHeap, 0, bufferSize) );
CHECK( (int)GetRawInputDeviceInfo(pRawInput->header.hDevice,
RIDI_PREPARSEDDATA, pPreparsedData, &bufferSize) >= 0 );
首次致电(The first call to) GetRawInputDeviceInfo
只是给了我们准备好的数据的大小.我们用它来分配一个适合数据的块,并在第二次调用中检索它(just gives us the size of the preparsed data. We use that to allocate a block that fits the data and retrieve it in a second call to) GetRawInputDeviceInfo
.(.) CHECK
是我用来处理错误的小宏.如果参数中的表达式的计算结果为(is a small macro that I use to handle errors. If the expression in the argument evaluates to) FALSE
要么(or) 0
,宏将释放所有已分配的内存并从函数返回.(, the macro frees any allocated memory and returns from the function.)
现在是全局.我们每一次获得的设备数据(Now here is the big picture. The device data we get with every) WM_INPUT
消息实际上是一系列(message is actually a series of) HID报告(HID reports) 来自(from the) HIDClass
驱动程序,我们可以使用准备好的数据来解压缩HID报告并确定按下的按钮和轴值. (我想这是(driver and we can use the preparsed data to unpack HID reports and determine the pressed buttons and axis values. (I guess, this is the way) DirectInput
已实施.)(has been implemented.))
首先,我们确定游戏杆具有的按钮数量.为此,我们使用(First, we determine the number of buttons the joystick has. For that, we use the) HidP_*
来自的功能(functions from)隐藏文件(hid.dll).明显,(. Obviously,)**隐藏文件(hid.dll)**是用于与(is the user space library used to communicate with the) HIDClass
驱动程序和Windows为此类I/O提供的最低级别的库.(driver and the lowest level library Windows offers for that kind of I/O.)
CHECK( HidP_GetCaps(pPreparsedData, &Caps) == HIDP_STATUS_SUCCESS )
CHECK( pButtonCaps = (PHIDP_BUTTON_CAPS)HeapAlloc
(hHeap, 0, sizeof(HIDP_BUTTON_CAPS) * Caps.NumberInputButtonCaps) );
capsLength = Caps.NumberInputButtonCaps;
CHECK( HidP_GetButtonCaps(HidP_Input, pButtonCaps,
&capsLength, pPreparsedData) == HIDP_STATUS_SUCCESS )
g_NumberOfButtons = pButtonCaps->Range.UsageMax - pButtonCaps->Range.UsageMin + 1;
HidP_GetCaps
获取我们可以查询的不同功能的数量,设备的"使用情况"和"使用情况"页面以及HID报告的字节长度.要查询输入按钮的数量,我们分配了一个适合多个输入的缓冲区(gets us the number of the different capabilities we can query, the Usage and Usage Page of the device and the length in bytes of HID reports. To query the number of input buttons, we allocate a buffer that fits multiple) HIDP_BUTTON_CAPS
结构和调用(structures and call) HidP_GetButtonCaps
填补它.的第一个参数(to fill it. The first parameter of) HidP_GetButtonCaps
指定报告类型. HID可以具有输入报告(即用于按钮,旋钮,推子,触摸屏等)和输出报告(即用于LED,显示器,力反馈等).接下来的三个参数是指向我们分配的缓冲区的指针,缓冲区的长度以及指向设备预准备数据的指针.(specifies the report type. HIDs can have input reports (i.e. for buttons, knobs, faders, touch screens, etc.) and output reports (i.e. for LEDs, displays, force feedback, etc). The next three parameters are the pointer to our allocated buffer, its length and the pointer to the device’s preparsed data.)
HidP_GetButtonCaps
返回每种类型的功能(returns the capabilities for every type of) HID控制(HID control) 操纵杆,即它的用法.(of our joystick, that is, its Usage.) pButtonCaps[0].UsagePage
指示第一组HID控件的用法(请参阅表1).(indicates the usage of the first set of HID controls (see Table 1 of) HID使用表(HID Usage Tables) ).().) UsageMin
和(and) UsageMax
指示使用范围的下限和上限索引返回的索引范围(indicate the lower and upper bound of usage range, i.e. the range of indicies that is returned by the) HIDClass
操纵杆按钮的驱动程序.那么按钮的数量就是(driver for the buttons of the joystick. The number of buttons is then the difference of) UsageMin
和(and) UsageMax
加一.(plus one.)
之所以(The reason why) HidP_GetButtonCaps
返回一个数组(returns an array of) HIDP_BUTTON_CAPS
结构是操纵杆可以为不同的按钮分配不同的用途.例如,我的RumblePad有一个标有"振动"的按钮,可以启用或禁用力反馈效果.的(structures is that a joystick can assign different usages for different buttons. For example, my RumblePad has a button labeled “Vibration” that enables or disables force feedback effects. The) UsagePage
第一个数组元素的成员指示十二个操作按钮的"按钮页”,而第二个数组元素指示"模式"和"振动"按钮的供应商定义的用法.(member of the first array element indicates Button Page for the twelve action buttons, whereas the second array elements indicates vendor-defined usage for the “Mode” and “Vibration” buttons.)
现在,我们获得了价值能力数组.此数组指定可以具有两个以上状态(即按下或释放)的HID控件的功能.这些控件通常具有一系列值.例如,我的RumblePad有两个模拟摇杆,即四个轴,每个轴的范围为(We now get the value capability array. This array specifies the capabilities of HID controls that can have more than two states (i.e. pressed or released). These controls often have a range of values. For example, my RumblePad has two analog sticks, that is, four axes, each of which has a range of) 0x00
至(to) 0xFF
哪里(where) 0x80
等于杆的中心位置.(equals the centered position of the stick.)
CHECK( pValueCaps = (PHIDP_VALUE_CAPS)HeapAlloc
(hHeap, 0, sizeof(HIDP_VALUE_CAPS) * Caps.NumberInputValueCaps) );
capsLength = Caps.NumberInputValueCaps;
CHECK( HidP_GetValueCaps(HidP_Input, pValueCaps,
&capsLength, pPreparsedData) == HIDP_STATUS_SUCCESS )
会员(The member) UsagePage
的(of the) HIDP_VALUE_CAPS
结构再次指示该值的使用(即哪个轴).(structure again indicates the usage of the value (i.e. which axis).) PhysicalMin
和(and) PhysicalMax
指示值的范围.请注意,本文提供的示例代码未使用这些功能.该示例代码假定范围在(indicate the range of the value. Note that these are not used by the sample code provides with this article. The sample code assumes the range to be between) 0x00
和(and) 0xFF
.(.)
现在,我们从HID输入报告中获取操作按钮的状态(Now we obtain the state of the action buttons from the HID input report in) pRawInput->data.hid.bRawData
.(.)
usageLength = g_NumberOfButtons;
CHECK(
HidP_GetUsages(
HidP_Input, pButtonCaps->UsagePage, 0, usage,
&usageLength, pPreparsedData,
(PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid
) == HIDP_STATUS_SUCCESS );
ZeroMemory(bButtonStates, sizeof(bButtonStates));
for(i = 0; i < usageLength; i++)
bButtonStates[usage[i] - pButtonCaps->Range.UsageMin] = TRUE;
HidP_GetUsages
仅返回那些按钮(returns only those buttons in) usage
实际被按下.我猜这个代码片段是不言自明的.(which are actually pressed. I guess that this code snippet is self-explaining.)
最后,我们从HID输入报告中获取有价控件的状态.(Finally, we obtain the state of the valued controls from the HID input report.)
for(i = 0; i < Caps.NumberInputValueCaps; i++)
{
CHECK(
HidP_GetUsageValue(
HidP_Input, pValueCaps[i].UsagePage, 0,
pValueCaps[i].Range.UsageMin, &value, pPreparsedData,
(PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid
) == HIDP_STATUS_SUCCESS );
switch(pValueCaps[i].Range.UsageMin)
{
case 0x30: // X-axis
lAxisX = (LONG)value - 128;
break;
case 0x31: // Y-axis
lAxisY = (LONG)value - 128;
break;
case 0x32: // Z-axis
lAxisZ = (LONG)value - 128;
break;
case 0x35: // Rotate-Z
lAxisRz = (LONG)value - 128;
break;
case 0x39: // Hat Switch
lHat = value;
break;
}
}
HidP_GetUsageValue
以相同的方式工作(works the same way) HidP_GetUsages
做.提取中的用法(does. It extracts the usage in) UsageMin
和(and) UsageMax
和当前(and the current) value
HID输入报告中的HID控件.对于我的小RumblePad,我只检查了(of the HID control from the HID input report. For my little RumblePad, I have examined only) UsageMin
区分轴.(to distinguish between the axes.)
我没有解释的其余示例代码以适当的方式绘制了从HID输入报告中提取的值.而已!(The rest of the sample code that I did not explain draws the values extracted from the HID input report in an appropriate manner. That’s it!)
进一步考虑(Further Considerations)
我在本文中表明,在以下方面的一些帮助下,可以使用Raw Input API进行操纵杆输入.(I showed in this article that it is possible to use the Raw Input API for joystick input with a little help from the) HIDClass
司机.我想这个API只是HID用户模式库之上和之下的一层(driver. I suppose that this API is just a layer on top of the HID user mode library and below) DirectInput
.实际上,也可以仅使用HID用户库来绕过Raw Input API并进行所有HID通信.只是看看(. In fact, it is also possible to bypass the Raw Input API and do all HID communication by using only the HID user library. Just take a look at the) HClient示例(HClient sample) WDK的.它在不使用Raw Input的情况下解释HID输入报告的功能与我所做的相同.开发游戏输入系统时,您无法获得任何较低级别的界面,因为HID用户模式库是将您的应用程序与内核模式分开的唯一界面,尽管我怀疑直接使用HID用户库会提高速度.(of the WDK. It does the same things I do to interpret HID input reports without using Raw Input. You can’t get any lower-level interface when developing your game input system as the HID user mode library is the only interface that separates your application from kernel mode, although I doubt that using the HID user library directly will gain much speed.)
最后但并非最不重要的一点是,我为您简要概述了直接使用HID用户模式库时必须做的事情:(Last but not least, I provide you with a small outline of the things you have to do when using the HID user mode library directly:)
- 枚举(Enumerate the devices of the)
HIDClass
设备设置类使用(Device Setup Class using the) 安装程序API(SetupAPI) .(.) - 通过获取特定设备的实例路径(Get the instance path of a specific device via)
SetupDiGetDeviceInterfaceDetail
.(.) - 通过打开设备的手柄(Open a handle to the device via)
CreateFile
使用实例路径.(using the instance path.) - 用(Use)
ReadFile
,(,)WriteFile
和(and the)HidP_*
与HID通信的功能.(functions to communicate with the HID.) - 合上手柄.(Close the handle.) 请注意,您不能在鼠标或键盘设备上使用此方法,因为Windows仅使用这些设备(请参见(Note that you cannot use this method on mouse or keyboard devices, because Windows uses these devices exclusively (see) Windows打开供系统使用的顶级集合(Top-Level Collections Opened by Windows for System Use) ).().)
参考文献(References)
- MSDN上的原始输入API(Raw Input API at MSDN)
- MSDN上的人工输入设备(Human Input Devices at MSDN)
- HID使用表(HID Usage Tables)
历史(History)
- 23(23)rd(rd)2011年4月:初始职位(April, 2011: Initial post)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C Windows Win32 game 新闻 翻译