高解像度マウス動作の使用

ゲーム ディベロッパー グループ

2006 年 4 月

はじめに

標準のコンピューター マウスは、1 インチあたり 400 ドット (400 DPI) でデータを返しますが、高解像度マウスは、800 DPI 以上でデータを生成します。このため、高解像度マウスからの入力精度は、標準マウスからの入力精度よりも高くなります。ただし、高解像度データは、標準の WM_MOUSEMOVE メッセージを通して取得することができません。通常、ゲームにとって高解像度マウス デバイスは有用ですが、WM_MOUSEMOVE のみを使用してマウス データを取得するゲームでは、マウスのフィルターされていない完全な解像度を利用できなくなります。

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

高解像度入力デバイスの一般情報については、「Microsoft Mouse and Keyboard Hardware: High-Definition Technology」などの、製品の Web サイトを参照してください。

マウス移動データの取得

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

  • WM_MOUSEMOVE
  • WM_INPUT
  • DirectInput

それぞれの方法には長所と短所があり、取得したデータの用途によって異なります。

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 メッセージからマウス移動データを読み取るには、まずデバイスを登録する必要があります。次に、この登録を実行するコードの例を示します。

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

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

    case WM_INPUT: 
    {
        UINT dwSize = 40;
        static BYTE lpb[40];
    
        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 の詳細については、「About Raw Input」を参照してください。

DirectInput

DirectInput は、システムの入力デバイスを抽象化する一連の API 呼び出しです。内部的には、DirectInput によって WM_INPUT データを読み取る 2 つ目のスレッドが作成され、DirectInput API を使用することによって、単に WM_INPUT を直接読み取る場合よりもオーバーヘッドが増加します。DirectInput は、DirectInput ジョイスティックからデータを読み取る場合にのみ有用ですが、Xbox 360 Controller for 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 ゲームではより精度の高い制御をユーザーに提供できます。