Использование преимущества перемещения мыши с высоким определением

Стандартная мышь компьютера возвращает данные в 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 могут более точно управлять пользователями.