⌨️

GetAsyncKeyState vs WM_KEY どっち使うの?

2022/08/12に公開

はじめに

Windowsでゲーム制作をする際のキ―ボード入力はどうするのか?というお話です。WindowsAPIにはキー入力を検知する手段が複数用意されていますが、どれを使えばよいのかいまいち理解しずらいと思ったので、個人的な意見ですがまとめてみました。

キー入力を検知する手段

私の知る限り、WindowsAPIには以下の4種類の方法があります。

GetKeyboardState

BYTE(unsigned char)[256]型の配列にキーボードの情報を一括で格納する関数
対応する要素(キー)の値の最上位ビットが1であればキーが押されている、それ以外なら押されていないという判定になります。BYTE型なので、0x80でビットマスクをする

BYTE keyState[256] = {};
GetKeyboardState(keyState));

if( keyState[VK_ESCAPE] & 0x80 )
{ 
    // escキーが押されている
}

GetKeyState

GetKeyboardStateがキーボード全体だったのに対して、こちらはキー1つのみを調べたい場合に使います。同様に最上位ビットが1ならキーが押されている、そうでなければ押されていないという判定になりますが、戻り値がSHORT型なのでビットマスクが0x8000になる

if (GetKeyState(VK_ESCAPE) & 0x8000)
{
    // escキーが押されている
}

GetAsyncKeyState

基本的にはGetKeyStateと同じです。異なるのは戻り値のビットに違いがあり、最上位ビットの押下判定に加えて最下位ビットが1であれば、最後にこの関数の呼び出した時からこの関数呼び出しの間にキーが押下されたという判定になる。

if (GetAsyncKeyState(VK_ESCAPE) & 0x8000)
{
    // escキーが押されている
}

if (GetAsyncKeyState(VK_ESCAPE) & 0x0001)
{
    // escキーが以前に押されたことがある
}

WM_KEYDOWN / WM_KEYUP

キーが押された瞬間 / キーが離された瞬間に起きるイベント。wParamには入力されたキーコードが、lParamにはキーの状態が格納される。lParamの30ビット目のフラグがたっていれば繰り返し入力がされています。

int64 CALLBACK WndProc(HWND hWnd, uint32 uMsg, uint64 wParam, int64 lParam)
{
    switch (uMsg)
    {
        case WM_KEYDOWN: 
        {
	    // キーが押された
	    switch (wParam)
	    {
		case VK_ESCAPE: ...; // escキー
		case VK_RETURN: ...; // enterキー
	    }
        }
        case WM_UP:
        { 
	    // キーが離された
        }
    }
    
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// 文字コード
const int32 keyCode = wParam;

// 30ビット目が1ならリピート入力
bool bIsRepeat = ( lParam & 0x40000000 ) != 0;

評価

GetKeyState / GetKeyboardState

関数なのでポーリング(一定間隔でチェックすること)が必要でGetKeyStateGetAsyncKeyStateはキー単体の入力を調べるのには向いている。複数のキーを使うのであれば、1度に全てのキーを取得するとこができるGetKeyboardStateも便利。ただし、入力クラスなどの全てのキー(256個)の状態の取得が求められる場合はGetKeyboardStateを使うべきです。60fpsでポーリングする場合GetKeyboardState60回、GetAsyncKeyState15,360回の呼び出しが必要にまります。

WM_KEYDOWN / WM_KEYUP

ウィンドウメッセージで入力を取得する場合はGetKeyState / GetKeyboardStateとは異なり、ウィンドウメッセージのコールバックを処理するだけで良い。入力クラスなど、全てのキーの状態の取得が求められる場合でもポーリングが必要ないのでパフォーマンスに優れている。

まとめ

ポーリングしなくても良いというパフォーマンスの点からウィンドウメッセージでキー入力を取得することをオススメします。キー配列を2つ(現在と前回の入力を)用意すれば、毎フレームキー配列をクリアしながらメッセージが発行されたタイミングでキーの状態を上書きするだけで、簡易的なキーボードクラスが実装できます。

脚注
  1. WM_INPUT
    様々なデバイスのデータにアクセスできるなど扱いの範囲が大きいので今回は範囲外とした。
    高解像度マウスなどの対応に使われる。 ↩︎

  2. DirectInput
    Microsoftが非推奨としているので除外 ↩︎

Discussion