マウスの状態取得
「マウスの状態を取得する」のはWindowsプログラムでは必須となりますが、通常は「マウスでクリックされた」ところから扱うことがほとんどです。
今回もDirectInputを使った実装方法ついて書きます。
DirectInputでマウスから取得できる情報
DirectInputを使ったマウスデバイスから取得できる情報には
- マウスポインタの移動量X
- マウスポインタの移動量Y
- マウスポインタの移動量Z(2016時点でZはまだ出番はなさそうですね)
- マウス左ボタンの押下状態
- マウス右ボタンの押下状態
- マウス中ボタンの押下状態(最近のホイールマウスのホイールを押してもここは反応しません。3ボタンマウスというマウスのときに使用されるものです)
があります。
実装してみる
実装したコード
//----------------------------------------------------------------------------- // // This program created from 'Win32 project'. // //----------------------------------------------------------------------------- #include <windows.h> #include <stdio.h> // ここから、DirectInputで必要なコード --> #define DIRECTINPUT_VERSION 0x0800 // DirectInputのバージョン情報 #include <dinput.h> #pragma comment(lib, "dinput8.lib") #pragma comment(lib, "dxguid.lib") // --> ここまで、DirectInputで必要なコード //----------------------------------------------------------------------------- // 定数 //----------------------------------------------------------------------------- #define APP_NAME "DInputMouseTest" // このプログラムの名前 #define APP_TITLE "DirectInput Mouse test" // このプログラムのタイトル #define SCREEN_WIDTH (640) #define SCREEN_HEIGHT (480) //----------------------------------------------------------------------------- // グローバル変数 //----------------------------------------------------------------------------- // ここから、DirectInputで必要なコード --> LPDIRECTINPUT8 g_pDInput = NULL; // DirectInputオブジェクト // マウス用 LPDIRECTINPUTDEVICE8 g_pDIMouse = NULL; // マウスデバイス DIMOUSESTATE g_zdiMouseState; // マウス状態 // --> ここまで、DirectInputで必要なコード BOOL g_bAppActive = FALSE; // アプリケーションアクティブフラグ // ここから、DirectInputで必要なコード --> //----------------------------------------------------------------------------- // // DirectInput用関数 // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // DirectInputの初期化処理 //----------------------------------------------------------------------------- bool InitDInput(HINSTANCE hInstApp, HWND hWnd) { HRESULT ret = DirectInput8Create(hInstApp, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&g_pDInput, NULL); if (FAILED(ret)) { return false; // 作成に失敗 } return true; } //----------------------------------------------------------------------------- // DirectInputの終了処理 //----------------------------------------------------------------------------- bool ReleaseDInput(void) { // DirectInputのデバイスを開放 if (g_pDInput) { g_pDInput->Release(); g_pDInput = NULL; } return true; } //----------------------------------------------------------------------------- // // DirectInput(Mouse)用関数 // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // DirectInputのマウスデバイス用の初期化処理 //----------------------------------------------------------------------------- bool InitDInputMouse(HWND hWnd) { HRESULT ret = S_FALSE; if (g_pDInput == NULL) { return false; } // マウス用にデバイスオブジェクトを作成 ret = g_pDInput->CreateDevice(GUID_SysMouse, &g_pDIMouse, NULL); if (FAILED(ret)) { // デバイスの作成に失敗 return false; } // データフォーマットを設定 ret = g_pDIMouse->SetDataFormat(&c_dfDIMouse); // マウス用のデータ・フォーマットを設定 if (FAILED(ret)) { // データフォーマットに失敗 return false; } // モードを設定(フォアグラウンド&非排他モード) ret = g_pDIMouse->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); if (FAILED(ret)) { // モードの設定に失敗 return false; } // デバイスの設定 DIPROPDWORD diprop; diprop.diph.dwSize = sizeof(diprop); diprop.diph.dwHeaderSize = sizeof(diprop.diph); diprop.diph.dwObj = 0; diprop.diph.dwHow = DIPH_DEVICE; diprop.dwData = DIPROPAXISMODE_REL; // 相対値モードで設定(絶対値はDIPROPAXISMODE_ABS) ret = g_pDIMouse->SetProperty(DIPROP_AXISMODE, &diprop.diph); if (FAILED(ret)) { // デバイスの設定に失敗 return false; } // 入力制御開始 g_pDIMouse->Acquire(); return true; } //----------------------------------------------------------------------------- // DirectInputのマウスデバイス用の解放処理 //----------------------------------------------------------------------------- bool ReleaseDInputMouse() { // DirectInputのデバイスを開放 if (g_pDIMouse) { g_pDIMouse->Release(); g_pDIMouse = NULL; } return true; } // --> ここまで、DirectInputで必要なコード //----------------------------------------------------------------------------- // DirectInputのマウスデバイス状態取得処理 //----------------------------------------------------------------------------- void GetMouseState(HWND hWnd) { if (g_pDIMouse == NULL) { // オブジェクト生成前に呼ばれたときはここで生成させる InitDInputMouse(hWnd); } // 読取前の値を保持します DIMOUSESTATE g_zdiMouseState_bak; // マウス情報(変化検知用) memcpy(&g_zdiMouseState_bak, &g_zdiMouseState, sizeof(g_zdiMouseState_bak)); // ここから、DirectInputで必要なコード --> // マウスの状態を取得します HRESULT hr = g_pDIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &g_zdiMouseState); if (hr == DIERR_INPUTLOST) { g_pDIMouse->Acquire(); hr = g_pDIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &g_zdiMouseState); } // --> ここまで、DirectInputで必要なコード if (memcmp(&g_zdiMouseState_bak, &g_zdiMouseState, sizeof(g_zdiMouseState_bak)) != 0) { // 確認用の処理、ここから --> // 値が変わったら表示します char buf[128]; wsprintf(buf, "(%5d, %5d, %5d) %s %s %s\n", g_zdiMouseState.lX, g_zdiMouseState.lY, g_zdiMouseState.lZ, (g_zdiMouseState.rgbButtons[0] & 0x80) ? "Left" : "--", (g_zdiMouseState.rgbButtons[1] & 0x80) ? "Right" : "--", (g_zdiMouseState.rgbButtons[2] & 0x80) ? "Center" : "--"); OutputDebugString(buf); // --> ここまで、確認用の処理 } } //----------------------------------------------------------------------------- // // Windowsアプリケーション関数 // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // メッセージハンドラ //----------------------------------------------------------------------------- LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_ACTIVATE: // アクティブ時:1 非アクティブ時:0 if (wParam == 0) { // 非アクティブになった場合 ReleaseDInputMouse(); } return 0L; case WM_DESTROY: // アプリケーション終了時の処理 // ここから、DirectInputで必要なコード --> ReleaseDInputMouse(); // DirectInput(Mouse)オブジェクトの開放 ReleaseDInput(); // DirectInputオブジェクトの開放 // --> ここまで、DirectInputで必要なコード // プログラムを終了します PostQuitMessage(0); return 0L; case WM_SETCURSOR: // カーソルの設定 SetCursor(NULL); return TRUE; } return DefWindowProc(hWnd, message, wParam, lParam); } //----------------------------------------------------------------------------- // ウィンドウ初期化(生成)処理 //----------------------------------------------------------------------------- HWND InitializeWindow(HINSTANCE hThisInst, int nWinMode) { WNDCLASS wc; HWND hWnd; // Window Handle // ウィンドウクラスを定義する wc.hInstance = hThisInst; // このインスタンスへのハンドル wc.lpszClassName = APP_NAME; // ウィンドウクラス名 wc.lpfnWndProc = WinProc; // ウィンドウ関数 wc.style = CS_HREDRAW | CS_VREDRAW; // ウィンドウスタイル wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // アイコン wc.hCursor = LoadCursor(NULL, IDC_ARROW); // カーソルスタイル wc.lpszMenuName = APP_NAME; // メニュー(なし) wc.cbClsExtra = 0; // エキストラ(なし) wc.cbWndExtra = 0; // 必要な情報(なし) wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // ウィンドウの背景(黒) // ウィンドウクラスを登録する if (!RegisterClass(&wc)) return NULL; // ウィンドウクラスの登録ができたので、ウィンドウを生成する hWnd = CreateWindowEx(WS_EX_TOPMOST, APP_NAME, // ウィンドウクラスの名前 APP_TITLE, // ウィンドウタイトル WS_OVERLAPPEDWINDOW, // ウィンドウスタイル(ノーマル) 0, // ウィンドウ左角X座標 0, // ウィンドウ左角Y座標 SCREEN_WIDTH, // ウィンドウの幅 SCREEN_HEIGHT, // ウィンドウの高さ NULL, // 親ウィンドウ(なし) NULL, // メニュー(なし) hThisInst, // このプログラムのインスタンスのハンドル NULL // 追加引数(なし) ); if (!hWnd) { return NULL; } // ウィンドウを表示する ShowWindow(hWnd, nWinMode); UpdateWindow(hWnd); SetFocus(hWnd); return hWnd; } //----------------------------------------------------------------------------- // プログラムエントリーポイント(WinMain) //----------------------------------------------------------------------------- int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { MSG msg; HWND hWnd; /* 表示するウィンドウの定義、登録、表示 */ if (!(hWnd = InitializeWindow(hThisInst, nWinMode))) { return FALSE; } // ここから、DirectInputで必要なコード --> InitDInput(hThisInst, hWnd); InitDInputMouse(hWnd); // --> ここまで、DirectInputで必要なコード while (TRUE) { if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage(&msg, NULL, 0, 0)) { break; } TranslateMessage(&msg); DispatchMessage(&msg); } // Check and get mouse state // ここから、DirectInputで必要なコード --> GetMouseState(hWnd); // --> ここまで、DirectInputで必要なコード } return msg.wParam; }
コードの説明
9行目から15行目
// ここから、DirectInputで必要なコード --> #define DIRECTINPUT_VERSION 0x0800 // DirectInputのバージョン情報 #include <dinput.h> #pragma comment(lib, "dinput8.lib") #pragma comment(lib, "dxguid.lib") // --> ここまで、DirectInputで必要なコード
ヘッダファイルのインクルード文と使用するライブラリファイルの指定です。
(キーボードの時にも書いたところですが、”DIRECTINPUT_VERSION”の#define文がある行は、#include <dinput.h>より”前”に書く必要があります)
29行目から35行目
// ここから、DirectInputで必要なコード --> LPDIRECTINPUT8 g_pDInput = NULL; // DirectInputオブジェクト // マウス用 LPDIRECTINPUTDEVICE8 g_pDIMouse = NULL; // マウスデバイス DIMOUSESTATE g_zdiMouseState; // マウス状態 // --> ここまで、DirectInputで必要なコード
ここは、使用するDirectInputのオブジェクト(g_pDInput)と、DirectInputのマウスデバイスオブジェクト(g_pDIMouse)の宣言を行っています。
また、マウスデバイスから取得したマウス状態を保持するグローバル変数(g_zdiMouseState)も宣言しています。
39行目から139行目
// ここから、DirectInputで必要なコード --> //----------------------------------------------------------------------------- // // DirectInput用関数 // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // DirectInputの初期化処理 //----------------------------------------------------------------------------- bool InitDInput(HINSTANCE hInstApp, HWND hWnd) { HRESULT ret = DirectInput8Create(hInstApp, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&g_pDInput, NULL); if (FAILED(ret)) { return false; // 作成に失敗 } return true; } //----------------------------------------------------------------------------- // DirectInputの終了処理 //----------------------------------------------------------------------------- bool ReleaseDInput(void) { // DirectInputのデバイスを開放 if (g_pDInput) { g_pDInput->Release(); g_pDInput = NULL; } return true; } //----------------------------------------------------------------------------- // // DirectInput(Mouse)用関数 // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // DirectInputのマウスデバイス用の初期化処理 //----------------------------------------------------------------------------- bool InitDInputMouse(HWND hWnd) { HRESULT ret = S_FALSE; if (g_pDInput == NULL) { return false; } // マウス用にデバイスオブジェクトを作成 ret = g_pDInput->CreateDevice(GUID_SysMouse, &g_pDIMouse, NULL); if (FAILED(ret)) { // デバイスの作成に失敗 return false; } // データフォーマットを設定 ret = g_pDIMouse->SetDataFormat(&c_dfDIMouse); // マウス用のデータ・フォーマットを設定 if (FAILED(ret)) { // データフォーマットに失敗 return false; } // モードを設定(フォアグラウンド&非排他モード) ret = g_pDIMouse->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); if (FAILED(ret)) { // モードの設定に失敗 return false; } // デバイスの設定 DIPROPDWORD diprop; diprop.diph.dwSize = sizeof(diprop); diprop.diph.dwHeaderSize = sizeof(diprop.diph); diprop.diph.dwObj = 0; diprop.diph.dwHow = DIPH_DEVICE; diprop.dwData = DIPROPAXISMODE_REL; // 相対値モードで設定(絶対値はDIPROPAXISMODE_ABS) ret = g_pDIMouse->SetProperty(DIPROP_AXISMODE, &diprop.diph); if (FAILED(ret)) { // デバイスの設定に失敗 return false; } // 入力制御開始 g_pDIMouse->Acquire(); return true; } //----------------------------------------------------------------------------- // DirectInputのマウスデバイス用の解放処理 //----------------------------------------------------------------------------- bool ReleaseDInputMouse() { // DirectInputのデバイスを開放 if (g_pDIMouse) { g_pDIMouse->Release(); g_pDIMouse = NULL; } return true; } // --> ここまで、DirectInputで必要なコード
ここはちょっと長いです。
関数が4つ実装されています。
- bool InitDInput(HINSTANCE hInstApp, HWND hWnd)
→ DirectInputの初期化処理呼び出し用の関数 - bool ReleaseDInput(void)
→ DirectInputの終了処理呼び出し用の関数 - bool InitDInputMouse(HWND hWnd)
→ DirectInputのマウスデバイス用の初期化処理呼び出し用関数 - bool ReleaseDInputMouse()
→ DirectInputのマウスデバイス用の解放処理呼び出し用関数
これらの関数はそれぞれ、下記の追加部分から呼び出されます。
155行目から162行目
// ここから、DirectInputで必要なコード --> // マウスの状態を取得します HRESULT hr = g_pDIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &g_zdiMouseState); if (hr == DIERR_INPUTLOST) { g_pDIMouse->Acquire(); hr = g_pDIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &g_zdiMouseState); } // --> ここまで、DirectInputで必要なコード
ここが、DirectInputでマウス状態を取得する「実体」になります。
DirectInputのGetDeviceState()を呼び出すことで、マウスデバイスが現在持っている状態を取得します。
198行目から201行目
// ここから、DirectInputで必要なコード --> ReleaseDInputMouse(); // DirectInput(Mouse)オブジェクトの開放 ReleaseDInput(); // DirectInputオブジェクトの開放 // --> ここまで、DirectInputで必要なコード
ここは、ウィンドウメッセージの’WM_DESTROY’から呼び出されたときの処理です。
プログラム自体が終了する箇所なので、DirectInput関係のオブジェクトも破棄して、プログラムが終了する準備をします。
277行目から280行目
// ここから、DirectInputで必要なコード --> InitDInput(hThisInst, hWnd); InitDInputMouse(hWnd); // --> ここまで、DirectInputで必要なコード
ここは、WinMain関数(Win32プログラムのエントリポイント)が呼び出され、メッセージループに入るまでの処理です。
「プログラムの初期化処理」で、ここで
- DirectInputオブジェクトの生成と初期化
(InitDInput()関数の呼び出し) - DirectInputマウスデバイスオブジェクトの生成と初期化
(InitDInputMouse()関数の呼び出し)
を行っておきます。
293行目から295行目
// ここから、DirectInputで必要なコード --> GetMouseState(hWnd); // --> ここまで、DirectInputで必要なコード
ここはメッセージループ本体(ほんとにループしている個所です)にGetMouseState関数を呼び出すことで、155行目から162行目の処理が実行されます。
DirectInputのGetDeviceState()が呼び出されるので、ここでマウス状態の取得処理が行っていることになります。
まとめ
このプログラムを動かしてみると、なんとなく「マウスの情報をデバッグ出力(Visual Studioの[出力]タブ)に出力しているだけ」に見えます。
ただ、このプログラムを実行させて、マウスの左ボタンと右ボタンを同時に押してみてください。
通常のWindowsプログラムでは、マウスの左ボタンと右ボタンを同時に押すと、たいてい「左ボタンが押された」「右ボタンが押された」は判りますが、「左ボタンと右ボタンが両方押されている」は判りません。
DirectInputを使ったこのプログラムの場合は、マウスの状態をそのまま取得するだけのようですが、「そのまま取得している」ということは、扱い方次第で
- どちらのボタンを先に押して
- どのくらい両方のボタンが押されて
- どちらのボタンを先に離したか
までを知ることができます。