Tirer parti du mouvement de la souris en haute définition

Une souris d’ordinateur standard retourne des données à 400 points par pouce (PPP), tandis qu’une souris haute définition génère des données à 800 PPP ou plus. Cela rend l’entrée d’une souris haute définition beaucoup plus précise que celle d’une souris standard. Toutefois, les données haute définition ne peuvent pas être obtenues par le biais des messages standard WM_MOUSEMOVE. En règle générale, les jeux bénéficieront d’appareils de souris haute définition, mais les jeux qui obtiennent des données de souris à l’aide de seulement WM_MOUSEMOVE ne pourront pas accéder à la résolution complète et non filtrée de la souris.

Un certain nombre d’entreprises fabriquent des dispositifs de souris haute définition, tels que Microsoft et Logitech. Avec la popularité croissante des appareils de souris haute résolution, il est important que les développeurs comprennent comment utiliser les informations générées par ces appareils de manière optimale. Cet article se concentre sur la meilleure façon d’optimiser le niveau de performance des entrées de souris haute définition dans un jeu comme un tireur de première personne.

Récupération des données de déplacement de la souris

Voici les trois méthodes principales pour récupérer les données de la souris :

Il existe des avantages et des inconvénients pour chaque méthode, selon la façon dont les données seront utilisées.

WM_MOUSEMOVE

La méthode la plus simple de lecture des données de déplacement de la souris consiste à passer par WM_MOUSEMOVE messages. Voici un exemple de lecture des données de déplacement de la souris à partir du message WM_MOUSEMOVE :

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

L’inconvénient principal pour les données de WM_MOUSEMOVE est qu’il est limité à la résolution d’écran. Cela signifie que si vous déplacez légèrement la souris, mais pas assez pour que le pointeur passe au pixel suivant, aucun message WM_MOUSEMOVE n’est généré. Ainsi, l’utilisation de cette méthode pour lire le mouvement de la souris annule les avantages de l’entrée haute définition.

Toutefois, l’avantage de WM_MOUSEMOVE est que Windows applique l’accélération des pointeurs (également appelées balistiques) aux données brutes de la souris, ce qui fait que le pointeur de la souris se comporte comme prévu par les clients. Cela rend WM_MOUSEMOVE l’option préférée pour le contrôle de pointeur (sur WM_INPUT ou DirectInput), car elle entraîne un comportement plus naturel pour les utilisateurs. Bien que WM_MOUSEMOVE soit idéal pour déplacer des pointeurs de souris, il n’est pas si bon de déplacer une caméra de première personne, car la précision haute définition sera perdue.

Pour plus d’informations sur WM_MOUSEMOVE, consultez WM_MOUSEMOVE.

WM_INPUT

La deuxième méthode d’obtention des données de la souris consiste à lire WM_INPUT messages. Le traitement des messages WM_INPUT est plus compliqué que le traitement des messages WM_MOUSEMOVE, mais WM_INPUT messages sont lus directement à partir de la pile IHM (Interface Homme-machine) et reflètent les résultats haute définition.

Pour lire les données de déplacement de la souris à partir du message WM_INPUT, l’appareil doit d’abord être inscrit; le code suivant fournit un exemple de ceci :

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

Le code suivant gère les messages WM_INPUT dans le gestionnaire WinProc de l’application :

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

L’avantage d’utiliser WM_INPUT est que votre jeu reçoit des données brutes de la souris au niveau le plus bas possible.

L’inconvénient est que WM_INPUT n’a pas de missiles balistiques appliqués à ses données. Par conséquent, si vous souhaitez piloter un curseur avec ces données, des efforts supplémentaires seront nécessaires pour que le curseur se comporte comme dans Windows. Pour plus d’informations sur l’application d’une balistique de pointeur, consultez La rubrique Sur la balistique de pointeur pour Windows XP.

Pour plus d’informations sur WM_INPUT, consultez À propos de l’entrée brute.

DirectInput

DirectInput est un ensemble d’appels d’API qui extrait les périphériques d’entrée sur le système. En interne, DirectInput crée un deuxième thread pour lire des données WM_INPUT, et l’utilisation des API DirectInput ajoute plus de surcharge que simplement la lecture WM_INPUT directement. DirectInput est utile uniquement pour lire des données à partir de manettes de jeu DirectInput ; toutefois, si vous devez uniquement prendre en charge les contrôleurs pour Windows, utilisez XInput à la place. Dans l’ensemble, l’utilisation de DirectInput n’offre aucun avantage lors de la lecture de données à partir d’appareils de souris ou de clavier, et l’utilisation de DirectInput dans ces scénarios est déconseillée.

Comparez la complexité de l’utilisation de DirectInput, illustrée dans le code suivant, aux méthodes décrites précédemment. L’ensemble d’appels suivant est nécessaire pour créer une souris 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();

Ensuite, l’appareil de la souris DirectInput peut être lu chaque frame :

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;

Résumé

Dans l’ensemble, la meilleure méthode pour recevoir des données de déplacement de souris haute définition est WM_INPUT. Si vos utilisateurs déplacent simplement un pointeur de souris, envisagez d’utiliser WM_MOUSEMOVE pour éviter d’avoir à effectuer des missiles de pointeur. Ces deux messages de fenêtre fonctionnent bien même si la souris n’est pas une souris haute définition. En prenant en charge la haute définition, les jeux Windows peuvent offrir un contrôle plus précis aux utilisateurs.