高解像度マウスの動きを活用する

標準のコンピューター マウスは 400 dpi (1 インチあたりのドット数) のデータを返しますが、高解像度マウスは 800 dpi 以上のデータを生成します。 これにより、高解像度マウスからの入力は、標準マウスからの入力よりはるかに正確になります。 ただし、標準のWM_MOUSEMOVE メッセージでは高解像度データを取得できません。 一般に、ゲームは高解像度のマウス デバイスを使うとメリットがありますが、WM_MOUSEMOVE だけを使ってマウス データを取得するゲームでは、マウスのフィルター処理されていない完全な解像度にはアクセスできません。

Microsoft や Logitech などの多くの企業が、高解像度のマウス デバイスを製造しています。 高解像度マウス デバイスの人気が高まる中、開発者は、これらのデバイスによって生成される情報の最適な使用方法を理解することが重要です。 この記事では、一人称シューティング ゲームのようなゲームでの高解像度マウス入力のパフォーマンスを最適化する最善の方法に焦点を当てます。

マウス移動データの取得

マウス データを取得するには、主に次の 3 つの方法があります。

各方法には、データの使用方法に応じて長所と短所があります。

WM_MOUSEMOVE

マウスの移動データを読み取る最も簡単な方法は、WM_MOUSEMOVE メッセージによるものです。 WM_MOUSEMOVE メッセージからマウス移動データを読み取る方法の例を次に示します。

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

WM_MOUSEMOVEからのデータの主な欠点は、それが画面の解像度に制限されることです。 つまり、マウスを少しだけ動かしても、ポインターが次のピクセルに移動するのに十分でない場合は、WM_MOUSEMOVE メッセージが生成されません。 したがって、この方法を使ってマウスの動きを読み取ると、高解像度入力の利点がなくなります。

ただし、WM_MOUSEMOVE には、Windows が生のマウス データにポインター アクセラレーション (弾道とも呼ばれます) を適用することで、マウス ポインターが顧客の期待どおりに動くという利点があります。 これにより、WM_MOUSEMOVE は、ユーザーにとってより自然な動作になるため、ポインター制御に対する (WM_INPUT または DirectInput より) 推奨されるオプションになります。 WM_MOUSEMOVE はマウス ポインターを動かすには理想的ですが、高解像度の精度が失われるので、一人称カメラを動かすにはあまり適していません。

WM_MOUSEMOVE について詳しくは、WM_MOUSEMOVE に関する記事をご覧ください。

WM_INPUT

マウス データを取得する 2 つ目の方法は、WM_INPUT メッセージを読み取ることです。 WM_INPUT メッセージの処理は WM_MOUSEMOVE メッセージの処理より複雑ですが、WM_INPUT メッセージはヒューマン インターフェイス デバイス (HID) スタックから直接読み取られ、高解像度の結果が反映されます。

WM_INPUT メッセージからマウス移動データを読み取るには、最初にデバイスを登録する必要があります。次に示すコードはその例です。

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

次のコードは、アプリケーションの WinProc ハンドラーで WM_INPUT メッセージを処理します。

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

WM_INPUT を使う利点は、ゲームが可能な限り低いレベルでマウスから生データを受け取ることです。

欠点は、WM_INPUT のデータには弾道が適用されないため、このデータでカーソルを動かそうとした場合、カーソルの動きを Windows と同じようにするには余分な作業が必要になります。 ポインターの弾道の適用について詳しくは、「Windows XP のポインターの弾道」をご覧ください。

WM_INPUT について詳しくは、生入力の概要に関する記事をご覧ください。

DirectInput

DirectInput は、システム上の入力デバイスを抽象化する一連の API 呼び出しです。 内部的には、DirectInput は WM_INPUT データを読み取るために 2 番目のスレッドを作成します。DirectInput API を使うと、WM_INPUT を単に直接読み取るよりオーバーヘッドが増えます。 DirectInput は、DirectInput ジョイスティックからデータを読み取る場合にのみ役立ちます。一方、Windows 用のコントローラーをサポートすることだけが必要な場合は、代わりに XInput 使ってください。 全体として、マウスまたはキーボード デバイスからデータを読み取るときは DirectInput を使ってもメリットがなく、これらのシナリオでの DirectInput の使用はお勧めしません。

次のコードで示す DirectInput を使ったときの複雑さを、前に説明した方法と比べてください。 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();

この場合、DirectInput マウス デバイスをフレームごとに読み取ることができます。

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;

まとめ

全体として、高解像度のマウス移動データを受け取る最善の方法は WM_INPUT です。 ユーザーがマウス ポインターを動かすだけの場合は、ポインターの弾道を実行する必要がない、WM_MOUSEMOVE の使用を検討してください。 これらのウィンドウ メッセージはいずれも、マウスが高解像度のマウスではない場合でも問題なく機能します。 高解像度をサポートすることにより、Windows ゲームでユーザーにいっそう正確な制御を提供できます。