Compartilhar via


Aproveitando o movimento do mouse High-Definition

Um mouse de computador padrão retorna dados a 400 pontos por polegada (DPI), enquanto um mouse de alta definição gera dados a 800 DPI ou superior. Isso torna a entrada 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 das mensagens de WM_MOUSEMOVE padrão. Em geral, os jogos se beneficiarão de dispositivos de mouse de alta definição, mas os jogos que obtêm dados do mouse usando apenas WM_MOUSEMOVE não poderão acessar a resolução completa e não filtrada do mouse.

Várias empresas estão fabricando dispositivos de mouse de alta definição, como Microsoft e 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 maneira ideal. Este artigo se concentra na melhor maneira de otimizar o desempenho da entrada de mouse de alta definição em um jogo como um atirador em primeira pessoa.

Recuperando dados de movimentação do mouse

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

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

WM_MOUSEMOVE

O método mais simples de ler 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 da mensagem WM_MOUSEMOVE:

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

A principal desvantagem dos dados provenientes de WM_MOUSEMOVE é que eles estão limitados à resolução da tela. Isso significa que, se você mover 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 nega os benefícios da entrada de alta definição.

A vantagem do WM_MOUSEMOVE, no entanto, é que o Windows aplica aceleração de ponteiro (também conhecida como balística) aos dados brutos do mouse, o que faz o ponteiro do mouse se comportar conforme as expectativas dos clientes. Isso torna WM_MOUSEMOVE a opção preferencial de controle de ponteiro (sobre WM_INPUT ou DirectInput), pois resulta em um comportamento mais natural para os usuários. Embora WM_MOUSEMOVE seja ideal para mover cursores de mouse, não é tão bom para mover uma câmera em primeira pessoa, pois perde-se a precisão de alta definição.

Para obter mais informações sobre WM_MOUSEMOVE, consulte 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 HID (Dispositivo de Interface Humana) e refletem resultados de alta definição.

Para ler os dados de movimentação do mouse da mensagem de WM_INPUT, o dispositivo deve 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 manipula 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 o 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, portanto, se você quiser conduzir um cursor com esses dados, será necessário esforço extra para fazer com que o cursor se comporte como faz no Windows. Para obter mais informações sobre como aplicar balística de ponteiro, consulte Balística de ponteiro para o Windows XP.

Para obter mais informações sobre WM_INPUT, consulte Sobre entrada de dados bruta.

DirectInput

DirectInput é um conjunto de chamadas de API que abstrai dispositivos de entrada no sistema. Internamente, o DirectInput cria um segundo thread para ler dados WM_INPUT e usar as APIs directInput adicionará mais sobrecarga do que simplesmente ler WM_INPUT diretamente. O DirectInput só é útil para ler dados de joysticks do DirectInput; no entanto, se você precisar dar suporte somente a controladores do Windows, use XInput. No geral, o uso do DirectInput não oferece vantagens ao ler dados de dispositivos de mouse ou teclado e o uso do DirectInput nesses cenários é desencorajado.

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

E, em seguida, o dispositivo do mouse 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

No geral, o melhor método para receber dados de movimentação de mouse de alta definição é WM_INPUT. Se os usuários estiverem apenas movendo um ponteiro do mouse, considere usar 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 a alta definição, os jogos do Windows podem oferecer controle mais preciso aos usuários.