Использование преимущества перемещения мыши с высоким определением
Стандартная мышь компьютера возвращает данные в 400 точек на дюйм (DPI), в то время как мышь с высоким определением создает данные в 800 DPI или больше. Это делает входные данные из мыши высокого определения гораздо точнее, чем из стандартной мыши. Однако данные высокого определения нельзя получить с помощью стандартных WM_MOUSEMOVE сообщений. Как правило, игры будут использовать устройства с мышью высокого определения, но игры, которые получают данные мыши, используя только WM_MOUSEMOVE не смогут получить доступ к полному, нефильтрованном разрешению мыши.
Ряд компаний производят устройства с высоким уровнем определения мыши, такие как Microsoft и Хеш. Благодаря растущей популярности устройств с мышью с высоким разрешением важно понимать, как оптимально использовать информацию, созданную этими устройствами. В этой статье рассматривается лучший способ оптимизации производительности ввода мыши с высоким определением в игре, такой как стрелок первого человека.
Получение данных перемещения мыши
Ниже приведены три основных метода для получения данных мыши:
Существуют преимущества и недостатки каждого метода в зависимости от того, как будут использоваться данные.
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]));
Следующий код обрабатывает сообщения WM_INPUT в обработчике WinProc приложения:
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 данных, и использование API DirectInput будет добавлять больше затрат, чем просто чтение 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 могут более точно управлять пользователями.