Nutzen der High-Definition-Mausbewegung

Eine Standard-Computermaus liefert Daten mit 400 Punkten pro Zoll (DPI), während eine hochauflösende Maus Daten mit 800 DPI oder mehr erzeugt. Dadurch wird die Eingabe mit einer hochauflösenden Maus viel präziser als mit einer Standardmaus. Hochauflösende Daten können jedoch nicht über die Standardmeldungen WM_MOUSEMOVE abgerufen werden. Im Allgemeinen profitieren Spiele von hochauflösenden Mausgeräten, aber Spiele, die Mausdaten nur über WM_MOUSEMOVE beziehen, können nicht auf die volle, ungefilterte Auflösung der Maus zugreifen.

Eine Reihe von Unternehmen wie Microsoft und Logitech stellen hochauflösende Mausgeräte her. Angesichts der zunehmenden Beliebtheit von hochauflösenden Mausgeräten ist es wichtig, dass die Entwickler wissen, wie sie die von diesen Geräten erzeugten Informationen optimal nutzen können. In diesem Artikel wird beschrieben, wie man die Leistung der hochauflösenden Mauseingabe in einem Spiel wie einem Ego-Shooter am besten optimieren kann.

Abrufen von Mausbewegungsdaten

Das sind die drei primären Methoden zum Abrufen von Mausdaten:

Jede Methode hat ihre Vor- und Nachteile, je nachdem, wie die Daten verwendet werden sollen.

WM_MOUSEMOVE

Die einfachste Methode zum Lesen von Mausbewegungsdaten sind WM_MOUSEMOVE-Nachrichten. Nachfolgend ein Beispiel für das Lesen von Mausbewegungsdaten aus der Nachricht WM_MOUSEMOVE:

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

Der Hauptnachteil der Daten aus WM_MOUSEMOVE ist, dass sie auf die Bildschirmauflösung beschränkt sind. Das bedeutet, dass keine WM_MOUSEMOVE-Meldung erzeugt wird, wenn Sie die Maus leicht bewegen, aber nicht genug, um den Zeiger zum nächsten Pixel zu bewegen. Die Verwendung dieser Methode zum Lesen von Mausbewegungen macht also die Vorteile der hochauflösenden Eingabe zunichte.

Der Vorteil von WM_MOUSEMOVE ist jedoch, dass Windows die Zeigerbeschleunigung (auch bekannt als Ballistik) auf die rohen Mausdaten anwendet, wodurch sich der Mauszeiger so verhält, wie es die Kunden erwarten. Dies macht WM_MOUSEMOVE zur bevorzugten Option für die Zeigersteuerung (gegenüber WM_INPUT oder DirectInput), da es zu einem natürlicheren Verhalten für die Benutzer führt. Während WM_MOUSEMOVE ideal für das Bewegen von Mauszeigern ist, eignet es sich nicht so gut für das Bewegen einer First-Person-Kamera, da die hochauflösende Präzision verloren geht.

Weitere Informationen zu WM_MOUSEMOVE finden Sie unter WM_MOUSEMOVE.

WM_INPUT

Die zweite Methode zum Abrufen von Mausdaten besteht darin, WM_INPUT-Nachrichten zu lesen. Die Verarbeitung von WM_INPUT-Meldungen ist komplizierter als die von WM_MOUSEMOVE-Meldungen, aber WM_INPUT-Meldungen werden direkt aus dem HID-Stack (Human Interface Device) gelesen und spiegeln hochauflösende Ergebnisse wider.

Um Mausbewegungsdaten aus der WM_INPUT-Nachricht zu lesen, muss das Gerät zunächst registriert werden; der folgende Code liefert ein Beispiel dafür:

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

Der folgende Code behandelt WM_INPUT-Meldungen im WinProc-Handler der Anwendung:

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

Der Vorteil der Verwendung von WM_INPUT besteht darin, dass Ihr Spiel Rohdaten von der Maus auf der niedrigstmöglichen Ebene erhält.

Der Nachteil ist, dass WM_INPUT keine Ballistik auf seine Daten anwendet. Wenn Sie also einen Cursor mit diesen Daten steuern wollen, ist zusätzlicher Aufwand erforderlich, damit sich der Cursor wie unter Windows verhält. Weitere Informationen zur Anwendung der Zeigerballistik finden Sie unter Zeigerballistik für Windows XP.

Weitere Informationen zu WM_INPUT finden Sie unter Informationen zur unformatierten Eingabe.

DirectInput

DirectInput ist eine Reihe von API-Aufrufen, die Eingabegeräte im System abstrahiert. Intern erstellt DirectInput einen zweiten Thread, um WM_INPUT-Daten zu lesen, und die Verwendung der DirectInput-APIs fügt mehr Aufwand hinzu, als einfach WM_INPUT direkt zu lesen. DirectInput ist nur zum Lesen von Daten aus DirectInput-Joysticks nützlich; Wenn Sie jedoch nur Controller für Windows unterstützen müssen, verwenden Sie stattdessen XInput. Insgesamt bietet die Verwendung von DirectInput keine Vorteile beim Lesen von Daten von Maus- oder Tastaturgeräten, und die Verwendung von DirectInput in diesen Szenarien wird abgeraten.

Vergleichen Sie die Komplexität der Verwendung von DirectInput, wie im folgenden Code dargestellt, mit den zuvor beschriebenen Methoden. Zum Erstellen einer DirectInput-Maus sind die folgenden Aufrufe erforderlich:

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

Und dann kann das DirectInput-Mausgerät jeden Frame lesen:

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;

Zusammenfassung

Insgesamt ist WM_INPUT die beste Methode, um hochauflösende Mausbewegungsdaten zu empfangen. Wenn Ihre Benutzer einfach einen Mauszeiger bewegen, sollten Sie WM_MOUSEMOVE verwenden, um zu vermeiden, dass Zeigerballistiken ausgeführt werden müssen. Diese beiden Fenstermeldungen funktionieren auch dann, wenn die Maus keine hochauflösende Maus ist. Durch die Unterstützung von Hochauflösung können Windows-Spiele den Benutzern eine präzisere Steuerung bieten.