利用 High-Definition 鼠标移动
标准计算机鼠标以每英寸 400 点(DPI)返回数据,而高清鼠标以 800 DPI 或更高版本生成数据。 这使得来自高清鼠标的输入比标准鼠标更精确。 但是,无法通过标准WM_MOUSEMOVE消息获取高清数据。 通常,游戏将受益于高清鼠标设备,但仅使用WM_MOUSEMOVE获取鼠标数据的游戏将无法访问鼠标的完整未筛选分辨率。
许多公司正在制造高清鼠标设备,如Microsoft和 Logitech。 随着高分辨率鼠标设备的日益普及,开发人员必须了解如何以最佳方式使用这些设备生成的信息。 本文重点介绍优化游戏(如第一人称射击器)中高清鼠标输入性能的最佳方法。
检索鼠标移动数据
下面是检索鼠标数据的三个主要方法:
每个方法都有优点和缺点,具体取决于数据的使用方式。
WM_MOUSEMOVE
读取鼠标移动数据的最简单方法是通过WM_MOUSEMOVE消息。 下面是如何从WM_MOUSEMOVE消息读取鼠标移动数据的示例:
case WM_MOUSEMOVE:
{
int xPosAbsolute = GET_X_PARAM(lParam);
int yPosAbsolute = GET_Y_PARAM(lParam);
// ...
break;
}
WM_MOUSEMOVE数据的主要缺点是它仅限于屏幕分辨率。 这意味着,如果鼠标稍微移动,但不足以导致指针移动到下一个像素,则不会生成WM_MOUSEMOVE消息。 因此,使用此方法读取鼠标移动会否定高清输入的优点。
然而,WM_MOUSEMOVE的优势在于,Windows 将指针加速(也称为弹道)应用于原始鼠标数据,这使得鼠标指针的行为与客户预期一样。 这使得WM_MOUSEMOVE成为指针控制的首选选项,相对于WM_INPUT或DirectInput,因为它为用户带来了更自然的操作体验。 虽然WM_MOUSEMOVE非常适合移动鼠标指针,但它并不适合移动第一人称相机,因为高清精度将丢失。
有关WM_MOUSEMOVE的详细信息,请参阅 WM_MOUSEMOVE。
WM_INPUT
获取鼠标数据的第二种方法是读取WM_INPUT消息。 处理WM_INPUT消息比处理WM_MOUSEMOVE消息更为复杂,但WM_INPUT消息直接从人机接口设备(HID)堆栈读取,并反映高分辨率结果。
若要从WM_INPUT消息中读取鼠标移动数据,必须先注册设备;以下代码提供了一个示例:
// you can #include <hidusage.h> for these defines
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02)
#endif
RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE;
Rid[0].dwFlags = RIDEV_INPUTSINK;
Rid[0].hwndTarget = hWnd;
RegisterRawInputDevices(Rid, 1, sizeof(Rid[0]));
以下代码处理应用程序的 WinProc 处理程序中的WM_INPUT消息:
case WM_INPUT:
{
UINT dwSize = sizeof(RAWINPUT);
static BYTE lpb[sizeof(RAWINPUT)];
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));
RAWINPUT* raw = (RAWINPUT*)lpb;
if (raw->header.dwType == RIM_TYPEMOUSE)
{
int xPosRelative = raw->data.mouse.lLastX;
int yPosRelative = raw->data.mouse.lLastY;
}
break;
}
使用WM_INPUT的优点是游戏在可能的最低级别接收来自鼠标的原始数据。
缺点是,WM_INPUT没有对其数据应用加速和减速曲线,因此,如果要使用此数据驱动光标,还需要额外努力才能使光标像在 Windows 中那样动作。 有关应用指针弹道的详细信息,请参阅适用于 Windows XP 的指针弹道。
有关WM_INPUT的详细信息,请参阅 关于原始输入。
DirectInput
DirectInput 是一组 API 调用,用于抽象系统上的输入设备。 在内部,DirectInput 创建另一个线程来读取WM_INPUT数据,使用 DirectInput API 会增加比直接读取WM_INPUT更多的开销。 DirectInput 仅适用于从 DirectInput 游戏杆读取数据;但是,如果只需要支持 Windows 的控制器,请改用 XInput。 总的来说,从鼠标或键盘设备读取数据时,使用 DirectInput 没有优点,因此不建议在这些方案中使用 DirectInput。
将 DirectInput的复杂性(如以下代码所示)与前面所述的方法进行比较。 创建 DirectInput 鼠标需要以下调用集:
LPDIRECTINPUT8 pDI;
LPDIRECTINPUTDEVICE8 pMouse;
hr = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&pDI, NULL);
if(FAILED(hr))
return hr;
hr = pDI->CreateDevice(GUID_SysMouse, &pMouse, NULL);
if(FAILED(hr))
return hr;
hr = pMouse->SetDataFormat(&c_dfDIMouse2);
if(FAILED(hr))
return hr;
hr = pMouse->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
if(FAILED(hr))
return hr;
if(!bImmediate)
{
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = 16; // Arbitrary buffer size
if(FAILED(hr = pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
return hr;
}
pMouse->Acquire();
然后,DirectInput 鼠标设备可以在每个帧中读取。
DIMOUSESTATE2 dims2;
ZeroMemory(&dims2, sizeof(dims2));
hr = pMouse->GetDeviceState(sizeof(DIMOUSESTATE2), &dims2);
if(FAILED(hr))
{
hr = pMouse->Acquire();
while(hr == DIERR_INPUTLOST)
hr = pMouse->Acquire();
return S_OK;
}
int xPosRelative = dims2.lX;
int yPosRelative = dims2.lY;
总结
总的来说,接收高清鼠标移动数据的最佳方法是WM_INPUT。 如果用户只是移动鼠标指针,请考虑使用WM_MOUSEMOVE以避免需要执行指针弹道。 即使鼠标不是高清鼠标,这两条窗口消息也能正常工作。 通过支持高清,Windows 游戏可以为用户提供更精确的控制。