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.
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.
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.
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 é 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;
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.