🎮

[Unity] Windows API を利用してキー判定をやってみた

2023/01/31に公開

概要

WindowsAPIを利用したキー判定のやり方をまとめた.

背景

Input.GetKeyDownではなくて, なぜWindowsAPIを利用したのか

Windows用のデスクトップアプリをUnityで開発していた. Unityのキー判定といえばInput.GetKeyDownを利用するのが一般的だと思いますが, 今回はWindowsAPIを利用して実装した.
デスクトップアプリのバックグラウンドで別アプリを立ち上げて動かしていたが, そのバックグラウンドのアプリでキー判定を行わなければならなくなった. バックグラウンドでは, アクティブウィンドウとして扱えない為, UnityのInput.GetKeyDownを使うことができなかった. そこで代替案としてWindowsAPIを利用したキー判定を実装することになった.

登場するキーワード

キーワード 概要 参考サイト
Windows API Windows の機能を呼び出すための関数群のこと. WindowsAPI本体は, DLL拡張子ファイルの中に保存されている. Windows APIとは
DLL(ダイナミックリンクライブラリ) 様々なプログラムから利用される汎用性の高い機能を収録した, 部品化されたプログラムのこと. 拡張子は.dll DLLファイルとは
仮想キーコード キーやマウスのボタンに設定されている, OSやアプリがキー入力を識別するための16進数の数値のこと. 仮想キーコード(Microsoft)
GetAsyncKeyState関数 (C#) 関数の呼び出し時にキーが押されているかどうかのステータスを, short型の値で返す. GetAsyncKeyState 関数 (winuser.h)
short型 (C#) 符号付き16ビット整数. 範囲は -32,768 ~ 32,767.ビット表記した例, 「0000 0000 0000 1000」. 整数数値型 (C#リファレンス)
最上位ビット
(Most Significant Bit. 略MSB)
コンピュータにおいて二進数で最も大きな値を意味するビット位置. 左端ビットとも言われる. 最上位ビット(Wikipedia)
ビット演算と論理積 ビット演算はある数を2進数で計算する処理のこと. 論理積では, それぞれの桁を比較して,どちらも1の時だけ演算結果のその桁が1になる. 例) 1000 & 1001 の結果は, 1000となる. 【C#入門】何のためにビット演算をするの?必要性とやり方を紹介

コード

今回は, Enter(Return)キーの判定を行うためのコードになります.

KeyPress.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
using System;

public class KeyPress : MonoBehaviour
{
    private bool returnKeyWasDown;

    [DllImport("user32.dll")]
    static extern short GetAsyncKeyState(Int32 vKey);
    private int VK_RETURN = 0x0D; // Return key.

    void Update()
    {
        if (KeyIsPressed())
        {
            // Keyが押された時の処理をここに書く
            Debug.Log("Key is Pressed.");
        }
    }

    private bool KeyIsPressed()
    {
        bool isPressed = false;

        short returnKeyStatus = GetAsyncKeyState(VK_RETURN);
        bool returnKeyIsDown = (returnKeyStatus & 0x8000) != 0;

        if (returnKeyIsDown && !returnKeyWasDown)
            isPressed = true;

        returnKeyWasDown = returnKeyIsDown;

        return isPressed;
    }
}

コード解説

DLLImport で WindowsAPI を呼び出す

    [DllImport("user32.dll")]
    static extern short GetAsyncKeyState(Int32 vKey);
    private int VK_RETURN = 0x0D;

C#からDLL関数を呼び出すには, DLLImportを行います. 関数を呼び出すにはコンパイラにAPIの位置を知らせる必要があります. そこで, System.Runtime.InteropServices.DllImport属性をメソッドに付けます. using System.Runtime.InteropServices;を用意しておきます.

実際にwindowsPC上で, "user32.dll"のファイルの位置を確認してみました.
C: > Windows > System32 > user32.dll にありました.

キーが押された時, 一体どのような値が返ってくるのか

GetAsyncKeyState関数の戻り値であるshort型の値と, その値を2進数に変換したものを表示するために, KeyIsPressed()の中に次のようなコードを追加する.

Debug.Log("short型の値: " + returnKeyStatus.ToString() + ", 2進数: " + Convert.ToString(returnKeyStatus, 2));

結果


上から順番に, キーが押されていない状態, キーが押された瞬間, キーが押されている状態と, 3段階回に分かれて値を受け取ることができた.
今回は2進数の最上位ビットに着目しキー判定を考えていく. キーが押されていない時の最上位ビットが0で, キーが押されている時は1, となっていることが分かる.

ビット演算の論理積でキー判定を行う

bool returnKeyIsDown = (returnKeyStatus & 0x8000) != 0;

ビット論理演算の論理積(&)を行う. 最上位ビットが1であるときtrue, 0である時がfalseとなるような条件式を作り, 結果をreturnKeyIsDownに入れる.
0x80000x は16進数で表記するときに頭につける. 0x8000 を2進数に変換すると, 1000 0000 0000 0000 になる.
returnKeyStatusには, キーが押されていない時は値が 1000 0000 0000 00001000 0000 0000 0001となり, キーが離されている時は値が 0000 0000 0000 0000 となる. 論理積で最上位ビットを比較し判定を行う.

キーが押された瞬間をどのように拾うのか

Update関数で, 前フレームと今のフレームのキーの状態の違いに注目する.
前フレームの状態を保持するためにreturnKeyWasDownというクラス変数を用意. KeyIsPressed関数の終盤でreturnKeyIsDownの値を代入してフレーム毎に更新していく.

if (returnKeyIsDown && !returnKeyWasDown)
    isPressed = true;

上の条件式は,「今のフレームではキーが押されている状態で, 且つ, 前フレームではキーが押されていなかった状態」をしめすので, これを満たした時が "キーが押された瞬間" ということになります.

逆に, キーが離された瞬間を拾いたい場合は, このように条件式を変えれば大丈夫です.

if (!returnKeyIsDown && returnKeyWasDown)
    isPressed = true;

あとは, Update関数の中からKeyIsPressed関数を呼び出すようにし, その中にキー判定に連動させたい処理を書きます.

以上になります.

感想

Unityを使った開発でWindowsAPIを利用する機会はなかなか少なそうですが, こんな方法もあるんだと, 初学者の自分にはいい勉強の機会となりました.
WindowsAPIがどのように呼び出されるのか, ビット論理演算のやり方など, 自分なりに理解が深まりました.

間違っている箇所や, お気づきになられた点がございましたらご指摘いただけますと幸いです.
よろしくお願いいたします.

Discussion