Aprovechar movimiento del mouse de alta definición

Un mouse de equipo estándar devuelve datos a 400 puntos por pulgada (PPP), mientras que un mouse de alta definición genera datos a 800 PPP o superior. Esto hace que la entrada de un mouse de alta definición sea mucho más precisa que la de un mouse estándar. Sin embargo, los datos de alta definición no se pueden obtener a través de los mensajes estándar WM_MOUSEMOVE. En general, los juegos se beneficiarán de dispositivos de mouse de alta definición, pero los juegos que obtienen datos del mouse con solo WM_MOUSEMOVE no podrán acceder a la resolución completa y sin filtrar del mouse.

Numerosas empresas fabrican dispositivos de mouse de alta definición, como Microsoft y Logitech. Con la creciente popularidad de los dispositivos de mouse de alta resolución, es importante que los desarrolladores comprendan cómo usar la información generada por estos dispositivos de forma óptima. Este artículo se concentra en la mejor manera de optimizar el rendimiento de la entrada de mouse de alta definición en un juego como un tirador de primera persona.

Recuperación de datos de movimiento del mouse

Estos son los tres métodos principales para recuperar datos del mouse:

Cada uno de estos métodos tiene sus ventajas y desventajas, en función de cómo se vayan a usar los datos.

WM_MOUSEMOVE

El método más sencillo de leer los datos de movimiento del mouse es a través de mensajes WM_MOUSEMOVE. A continuación se muestra un ejemplo de cómo leer los datos de movimiento del mouse desde el mensaje WM_MOUSEMOVE:

case WM_MOUSEMOVE:
{
    int xPosAbsolute = GET_X_PARAM(lParam); 
    int yPosAbsolute = GET_Y_PARAM(lParam);
    // ...
    break;
}

La desventaja principal de los datos de WM_MOUSEMOVE es que se limita a la resolución de pantalla. Esto significa que si mueve el mouse ligeramente (pero no lo suficiente para hacer que el puntero se mueva al siguiente píxel) no se genera ningún mensaje WM_MOUSEMOVE. Por consiguiente, si se usa este método para leer el movimiento del mouse no se logran las ventajas de la entrada de alta definición.

Sin embargo, la ventaja de WM_MOUSEMOVE es que Windows aplica la aceleración del puntero (también conocida como balística) a los datos sin procesar del mouse, lo que hace que el puntero del mouse se comporte como esperan los clientes. Esto hace que WM_MOUSEMOVE sea la opción preferida para el control de puntero (pro encima de WM_INPUT o DirectInput), ya que con ello el comportamiento es más natural para los usuarios. Aunque WM_MOUSEMOVE es ideal para mover punteros del mouse, no lo es tanto para mover una cámara en primera persona, ya que se perderá la precisión de la alta definición.

Para más información sobre WM_MOUSEMOVE, consulte WM_MOUSEMOVE.

WM_INPUT

El segundo método para obtener datos del mouse es leer mensajes WM_INPUT. El procesamiento de mensajes WM_INPUT es más complicado que el de mensajes WM_MOUSEMOVE, pero los primeros se leen directamente desde la pila del dispositivo de interfaz humana (HID) y reflejan los resultados de la alta definición.

Para poder leer los datos de movimiento del mouse del mensaje WM_INPUT, antes hay que registrar el dispositivo; el código siguiente proporciona un ejemplo de cómo hacerlo:

// 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]));

El siguiente código controla los mensajes WM_INPUT que hay en el controlador WinProc de la aplicación:

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;
}

La ventaja de usar WM_INPUT es que el juego recibe datos sin procesar del mouse en el nivel más bajo posible.

La desventaja es que en el caso de WM_INPUT no se aplica balística a sus datos, por lo que si desea usar un cursor con estos datos, será preciso hacer un esfuerzo adicional para que el cursor se comporte como lo hace en Windows. Para más información sobre cómo aplicar balística de puntero, consulte Balística de puntero para Windows XP.

Para más información sobre WM_INPUT, consulte Acerca de la entrada sin procesar.

DirectInput

DirectInput es un conjunto de llamadas API que abstrae los dispositivos de entrada en el sistema. Internamente, DirectInput crea un segundo subproceso para leer datos de WM_INPUT datos y el uso de las API de DirectInput agregará más sobrecarga que la mera lectura directa de WM_INPUT. DirectInput solo es útil para leer datos de joysticks con DirectInput; sin embargo, si solo necesita poder usar controladores para Windows, use XInput. En general, el uso de DirectInput no supone ninguna ventaja a la hora de leer datos desde mouse o teclado, por lo que no se recomienda su uso en estos escenarios.

Compare la complejidad del uso de DirectInput, que se muestra en el siguiente código, con los métodos descritos anteriormente. Para crear un mouse con DirectInput se necesita el siguiente conjunto de llamadas:

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();

Y, a continuación, el mouse con DirectInput se puede leer en cada marco:

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;

Resumen

En general, el mejor método para recibir datos de movimiento del mouse de alta definición es WM_INPUT. Si los usuarios simplemente mueven un puntero del mouse, considere la posibilidad de usar WM_MOUSEMOVE para evitar tener que realizar la balística del puntero. Ambos mensajes funcionarán bien, incluso si el mouse no es de alta definición. Al admitir la alta definición, los juegos de Windows pueden ofrecer un control más preciso a los usuarios.