Como tirar proveito do movimento do mouse de alta definição

Um mouse padrão de computador retorna dados a 400 pontos por polegada (DPI), enquanto um mouse de alta definição gera dados a 800 DPI ou mais. Isso torna a inserção de um mouse de alta definição muito mais precisa do que a de um mouse padrão. No entanto, os dados de alta definição não podem ser obtidos por meio de mensagens de WM_MOUSEMOVE padrão. De modo geral, os jogos se beneficiarão de dispositivos de mouse de alta definição, mas os jogos que obtêm dados do mouse usando somente WM_MOUSEMOVE não serão capazes de acessar a resolução total não filtrada do mouse.

Várias empresas estão fabricando dispositivos de mouse de alta definição, como, por exemplo, a Microsoft e a Logitech. Com a crescente popularidade dos dispositivos de mouse de alta resolução, é importante que os desenvolvedores entendam como usar as informações geradas por esses dispositivos de forma otimizada. Este artigo se concentra na melhor maneira de otimizar o desempenho das inserções do mouse de alta definição em um jogo como um jogos de tiros em primeira pessoa.

Como recuperar dados de movimentação do mouse

Aqui estão os três métodos principais para recuperar dados do mouse:

Existem vantagens e desvantagens em cada método, dependendo de como os dados serão usados.

WM_MOUSEMOVE

O método mais simples de leitura de dados de movimentação do mouse é por meio de mensagens WM_MOUSEMOVE. Veja a seguir um exemplo de como ler dados de movimentação do mouse na mensagem WM_MOUSEMOVE:

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

A principal desvantagem dos dados de WM_MOUSEMOVE é que estão limitados à resolução da tela. Isso significa que, se você movimentar o mouse ligeiramente, mas não o suficiente para fazer com que o ponteiro se mova para o próximo pixel, nenhuma mensagem WM_MOUSEMOVE será gerada. Portanto, usar esse método para ler o movimento do mouse anula os benefícios da inserção de alta definição.

A vantagem do WM_MOUSEMOVE, no entanto, é que o Windows aplica a aceleração de ponteiro (também conhecida como balística) aos dados brutos do mouse, o que faz com que o ponteiro do mouse se comporte como os clientes esperam. Isso faz do WM_MOUSEMOVE a opção preferencial de controle de ponteiro (por meio do WM_INPUT ou DirectInput), já que resulta em um comportamento mais natural para os usuários. Embora o WM_MOUSEMOVE seja ideal para movimentar ponteiros de mouse, não é tão bom para movimentar uma câmera em primeira pessoa, já que a precisão da alta definição será perdida.

Para obter mais informações sobre WM_MOUSEMOVE, confira WM_MOUSEMOVE.

WM_INPUT

O segundo método de obtenção de dados do mouse é ler mensagens WM_INPUT. Processar mensagens WM_INPUT é mais complicado do que processar mensagens WM_MOUSEMOVE, mas as mensagens WM_INPUT são lidas diretamente da pilha do Dispositivo de Interface Humana (HID) e refletem os resultados de alta definição.

Para ler os dados de movimentação do mouse na mensagem WM_INPUT, o dispositivo precisa primeiro ser registrado; o código a seguir fornece um exemplo disso:

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

O código a seguir manuseia mensagens WM_INPUT no manipulador WinProc do aplicativo:

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

A vantagem de usar WM_INPUT é que seu jogo recebe dados brutos do mouse no nível mais baixo possível.

A desvantagem é que WM_INPUT não tem balística aplicada aos seus dados e, portanto, se você quiser impulsionar um cursor com esses dados, um esforço extra será necessário para fazer com que o cursor se comporte como no Windows. Para obter mais informações sobre como aplicar a balística de ponteiros, confira Balística de ponteiros para Windows XP.

Para obter mais informações sobre WM_INPUT, confira Sobre a entrada de dados brutos.

DirectInput

O DirectInput é um conjunto de chamadas à API que abstrai os dispositivos de inserção do sistema. Internamente, o DirectInput cria um segundo thread para ler dados WM_INPUT e usar as APIs do DirectInput irá adicionar mais sobrecarga do que a simples leitura direta do WM_INPUT. O DirectInput só é útil para ler dados de joysticks do DirectInput; no entanto, se você precisar dar suporte apenas a controladores para Windows, use o XInput. De modo geral, o uso do DirectInput não oferece vantagens na leitura de dados de dispositivos de mouse ou teclado e o uso do DirectInput nessas situações não é incentivado.

Compare a complexidade de uso do DirectInput, mostrada no código a seguir, com os métodos descritos anteriormente. O seguinte conjunto de chamadas é necessário para criar um mouse do 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();

Em seguida, o dispositivo de mouse do DirectInput pode ser lido em cada quadro:

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;

Resumo

De modo geral, o melhor método para receber dados de movimentação de mouse de alta definição é o WM_INPUT. Se os usuários estiverem apenas movimentando um ponteiro do mouse, pense em usar o WM_MOUSEMOVE para evitar a necessidade de executar balística de ponteiro. Essas duas mensagens de janela funcionarão bem mesmo se o mouse não for um mouse de alta definição. Ao dar suporte à alta definição, os jogos do Windows podem oferecer um controle mais preciso aos usuários.