Поделиться через


Воспользование возможностями 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]));

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