キーボードの入力状態を調べる


DxLib Advent Calendar 2019の12日目の記事です。
[1日目:DXアーカイブの暗号化を強引に解除することは法に触れるのか?]6日目:プログラミング教育フリーソフト第4位アプリをDXライブラリでAndroidアプリにした <- | 12日目:これ| ->

皆さんこんにちは。このDXライブラリのアドベントカレンダー活気がないですねぇ... なので記事書きます。

はじめに

DXライブラリでのキーボードの入力状態を取得するにはCheckHitKey()を使用します。ですがこの関数の戻り値は、キーが押されているかどうかだけです。なので、キーがどれだけ押されているのかといったことは取得できません。どれだけ長く押されているかということを取得するために、このサイトに乗っているコードを使います。

int Key[256]; // キーが押されているフレーム数を格納する

// キーの入力状態を更新する
int gpUpdateKey(){
    char tmpKey[256]; // 現在のキーの入力状態を格納する
    GetHitKeyStateAll( tmpKey ); // 全てのキーの入力状態を得る
    for( int i=0; i<256; i++ ){ 
        if( tmpKey[i] != 0 ){ // i番のキーコードに対応するキーが押されていたら
            Key[i]++;     // 加算
        } else {              // 押されていなければ
            Key[i] = 0;   // 0にする
        }
    }
    return 0;
}

そして、次のようにすると、押された瞬間、押されている間の処理が書けるわけです。

int WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
    while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 && gpUpdateKey()==0 ){
        if( Key[KEY_INPUT_RIGHT] == 1 ){ // 右キーが押された瞬間なら
            //押された瞬間の処理
        }
        if( Key[KEY_INPUT_Z] >= 1 ){
            //押されている間の処理
        }
    }

    DxLib_End(); // DXライブラリ終了処理
    return 0;
} 

ただ、一つだけ実装できない処理があります。それは、キーが離された瞬間です。
それを検知するためにgpUpdateKey()を変更していきましょう。

離された瞬間とは

離された瞬間。それはつまり、一つ前までは押されていたが今は押されていない。ということです。なのでそれを書きましょう。また、C++なので、クラスにしちゃいましょう。

class Device {
protected:
    uint32_t mCode;
    uint32_t mPressingCount;
    enum class DeviceStatus {
        Press,
        Release,
        Up,
        Down
    };

public:
    virtual void Update() = 0;

    virtual bool Press() const = 0;

    virtual bool Release()const = 0;

    virtual bool Up() const = 0;

    virtual bool Down() const = 0;

    virtual uint32_t PressingCount() const = 0;
};

class Key : protected Device {
protected:
    DeviceStatus KeyStatus;
public:
    /// <summary>
    /// キーの押下状態の更新関数
    /// </summary>
    virtual void Update() {
        char isKey[256]; // 現在のキーの入力状態を格納する
        int tmpKey;
        if (GetHitKeyStateAll(isKey) == -1) {
            //System::MessageError(ERR_INPUT_KEY);
        }// 全てのキーの入力状態を得る
        tmpKey = mPressingCount;
        if (isKey[mCode] != 0) { // i番のキーコードに対応するキーが押されていたら
            mPressingCount++;     // 加算
            KeyStatus = mPressingCount > 1 ? DeviceStatus::Press : DeviceStatus::Down;
        }
        else {              // 押されていなければ
            if (tmpKey > 0) { //1フレーム前まで押されていたら
                KeyStatus = DeviceStatus::Up;
            }
            else {
                KeyStatus = DeviceStatus::Release;
            }
            mPressingCount = 0;   // 0にする
        }
    }




    Key() = default;

    /// <summary>
    /// キーコードを利用した初期化コンストラクタ
    /// </summary>
    /// <param name="KeyCode">DxLib.h内で宣言されている定数または同様の数値</param>
    /// <returns></returns>
    const Key(uint32_t KeyCode) {
        mCode = KeyCode;
    }

    /// <summary>
    /// キーが押下されているかを返す
    /// </summary>
    /// <returns>押下されていたらtrue</returns>
    virtual bool Press() const {
        return (KeyStatus == DeviceStatus::Press) || (KeyStatus == DeviceStatus::Down);
    }

    /// <summary>
    /// キーが押下されていないかを返す
    /// </summary>
    /// <returns>押下されていなかったらtrue</returns>
    virtual bool Release() const {
        return KeyStatus == DeviceStatus::Release;
    }

    /// <summary>
    /// キーが離されたかどうかを返す
    /// </summary>
    /// <returns>キーが離されたらtrue</returns>
    virtual bool Up() const {
        return KeyStatus == DeviceStatus::Up;
    }

    /// <summary>
    /// キーが押されたかを返す
    /// </summary>
    /// <returns>押された瞬間だったらtrue</returns>
    virtual bool Down() const {
        return KeyStatus == DeviceStatus::Down;
    }

    /// <summary>
    /// 押下されている時間を返す
    /// </summary>
    /// <returns>キーが押されてからの時間[フレーム]</returns>
    virtual uint32_t PressingCount() const {
        return mPressingCount;
    }
};

一番上のDeviceクラスを継承してKeyクラスを作ります。Deviceにはキーコード、押されている時間を保持する変数と入力状態のenum、押された瞬間、離された瞬間、押されている間、離されている間のboolを返す仮想関数と押されている時間を返す仮想関数があります。
Keyクラスでは入力状態を保持する変数を用意します。Update()はさっきのgpUpdateKey()を書き換えたものです。
ではUpdate()の説明をしましょう。途中までは変わりませんね。tmpKey = mPressingCount;は、1フレーム前の入力時間を代入します。キーが離されているときの判別に使用します。KeyStatus = mPressingCount > 1 ? DeviceStatus::Press : DeviceStatus::Down;は入力時間が1より大きければ押され続けている、そうでなければ押された瞬間となります。

if (tmpKey > 0) { //1フレーム前まで押されていたら
    KeyStatus = DeviceStatus::Up;
}
else {
    KeyStatus = DeviceStatus::Release;
}
mPressingCount = 0;   // 0にする

は、キーが押されていない状態であれば、先ほど保存した1フレーム前と今を比較します。1フレーム前が0でなければ、直前までは押されていたとし、そうでなければ離されていた状態となります。そして入力時間をリセットします。

一番大変なこと

さて、これで押された瞬間、押されている間、離された瞬間、離されている間、押下時間を取得できるようになりました。ただこれ、キー一つ一つでオブジェクトを定義し、一つ一つUpdate()を舞フレーム実行しなければいけません。ただ、キーは合計で約110個ほどあります。これを全部定義しなければいけません。めんどいですね。頑張ってください。ちなみに私はExcelを使いました。結構楽ですよ。

マウスとゲームパッド

ちょっと書き換えてマウスの入力とゲームパッドの入力でも同じように、押された瞬間、押されている間、離された瞬間、離されている間、押下時間を取得できます。いろいろ頑張ってみてください。

@追記 2019/12/28
ただしGetHitKeyStateAll()部分を変えるなど、少しコードの変更が必要です。ただ基本構造は同じです。

最後に

この記事は結構寝ぼけながら書いてるので大事なことが抜けてたり、誤字脱字があったりする可能性があります。現にタイプミスがとても多い... そこら辺と、ここもっとこうした方がいいとかこれおかしいとかいうことのコメントなどを待ってます。

@追記 2019/12/18
CheckHitKeyAll()を使うとキーが押下されてる時だけ実行とかできそう。誰か試してほしいなぁ(チラチラ-

ライセンス

ライセンスはCC0です。自由に使用、改変して結構です。

更新履歴

2019/12/12 投稿
2019/12/28 追記