3.入力周りの処理


0.はじめに

前回の予告どおり今回は入力周りをやっていきます。
入力周りはゲームの種類によって必要になる機能も異なるため、
中々難しい部分があるなと作っていて思いました。
今回はとりあえず最低限の機能のみを実装しておきました。

1.下位レベルの入力取得

Windowsプログラムでキー入力を受け取るにはウィンドウプロシージャ、DirectInput等幾つかの方法がありますが、
今回は手軽さでWindowsAPIにあるGetAsyncKeyState()を選びました。
GetAsyncKeyState()の使い方は単純で、調べたいキーコードを引数に渡して呼び出し、
Short型の最上位ビット0x8000が立っているかどうかで現在キーが押されているか判定できます。
GetAsyncKeyState()での入力判定
if(GetAsyncKeyState('Z') & 0x8000) キーが入力されている時;
と、上のコードでZキーが押されたかを判定できます。
引数に渡すキーコードはVK_RETURN、VK_LEFT、
VK_RIGHT、VK_UP、VK_DOWN、文字コードなど色々用意されているのでヘルプを参照してみてください。

2.キーボード入力管理クラス

前節で個別のキーが押されているかどうかについて判定できるようになったので、
次は上のような処理を便利に扱えるようにクラス化について考えてみます。
まずゲームで入力を扱う場合によく使うボタンを押した瞬間、
押し続けた場合のキーリピートなどは上の処理だけだと行えないので、
ボタンを押し続けている時間を保存する変数を各ボタンに持たせます。
またオプションなどでキー配置を変更しやすいように各ボタンにキーコードを持たせます。
で上の通りにすると各ボタン毎に必要なデータは下のようになります。
ボタン一つに必要なデータ
struct KeyInfo
{
    char code_;     //!<入力情報とキーコードの関連付けデータ
    int pressCnt_;  //!<押し続けカウンタ
};
であとは上の構造体を配列で持っていて毎フレーム、
キー入力を取得して状態を更新してくれるまとめクラスがあればある程度便利に使えそうなので、
そんなクラスを作ってみます。
キーボード入力管理クラス
class Keyboard
{
public:
    Keyboard() : count_(0)
    {
    }
    ~Keyboard()
    {
        Release();
    }
public:
    bool Create(const char* keyCodes, int keyCnt)
    {
        count_ = keyCnt;
        MMM_CHECK(BTNID_MAX >= count_);

        int i = 0;
        for(; i < count_; ++i)
        {
            keys_[i].code_ = keyCodes[i];
            keys_[i].pressCnt_ = -1;
        }

        return true;
    }
    bool Release()
    {
        count_ = 0;

        return true;
    }
    InputData Update()
    {
        UINT data = 0;
        for(int i = 0; i < count_; ++i)
        {
            if(GetAsyncKeyState(keys_[i].code_) & 0x8000)
            {
                data |= (1 << i);
                keys_[i].pressCnt_ = Max(keys_[i].pressCnt_+1, 1);
            }
            else
            {
                keys_[i].pressCnt_ = Min(--keys_[i].pressCnt_, 0);
            }
        }
        return data;
    }
public:
    int GetCount() const { return count_; }
public:
    bool IsDownRep(int devID, int repCnt) const
    {
        if(keys_[devID].pressCnt_ <= 0) return false;
        if(keys_[devID].pressCnt_ == 1) return true;
        if(keys_[devID].pressCnt_ > repCnt)
        {
            return (keys_[devID].pressCnt_ % ((repCnt>>1)+1)) == 0;
        }
        else
        {
            return (keys_[devID].pressCnt_ % repCnt) == 0;
        }
    }
    bool IsDown(int devID) const { return keys_[devID].pressCnt_ == 1; }
    bool IsPress(int devID) const { return keys_[devID].pressCnt_ > 1; }
    bool IsUp(int devID) const { return keys_[devID].pressCnt_ == 0; }
    bool IsRelease(int devID) const { return keys_[devID].pressCnt_ < 0; }
private:
    struct KeyInfo
    {
        char code_;     //!<入力情報とキーコードの関連付けデータ
        int pressCnt_;  //!<押し続けカウンタ
    };
private:
    int count_;                 //!<キー数
    KeyInfo keys_[BTNID_MAX];   //!<キー配列
};
と上のような感じになりました。
とりあえずこのクラスだけでもキーボードの入力は扱えますが、
ジョイスティックとキーボードの兼用や、複数人で遊ぶゲーム(PCではオンラインが一般的ですが一応)、
デバッグ時用などにも使えるのでもう少し扱いやすいように作ってみました。

3.入力管理クラス

今回はキーボードとジョイスティックが兼用できるようにすること、
幾つかの入力機器からの入力を受け取れること、
オプションなどでキーコンフィグが行いやすいようにすることを目的に次のような管理用クラスを作ってみました。
入力管理クラスの宣言
//------------------------------------------------------------
//!ボタン識別番号
//!@memo ゲームの種類毎に数は変更
//------------------------------------------------------------
enum ButtonID
{
    BTNID_LEFT,     //十字キー
    BTNID_RIGHT,
    BTNID_UP,
    BTNID_DOWN,
    BTNID_0,        //その他ボタン
    BTNID_1,
    BTNID_2,
    BTNID_3,
    BTNID_4,
    BTNID_5,
    BTNID_6,
    BTNID_7,
    BTNID_MAX
};

typedef UINT InputData;

//------------------------------------------------------------
//!入力管理クラス
//------------------------------------------------------------
class Input
{
public:
#define GetInput() mmm::Input::Get()
    static Input& Get()
    {
        static Input inst; return inst;
    }
private:
    Input();
    ~Input();
public:
    bool Create(int deviceCount);
    bool Release();
public:
    void Update();
public:
    bool CreateDevice(int devID);
    bool SetKeyboardData(int devID, const char* keyCodes, int keyCnt);
    bool ReleaseDevice(int devID);
public:
    //ボタン入力の判定用関数
    bool IsBtnDown(int btnID, int devID = 0);
    bool IsBtnDownRep(int btnID, int repCnt = DEFAULT_REPEAT_KEY_DELAY, int devID = 0);
    bool IsBtnPress(int btnID, int devID = 0);
    bool IsBtnUp(int btnID, int devID = 0);
    bool IsBtnRelease(int btnID, int devID = 0);
public:
    InputData GetInputData(int devID = 0);
public:
    bool SetMouseWindow(const AWindow& wnd);
    Vector2 GetMousePos();
private:
    enum{ DEFAULT_REPEAT_KEY_DELAY = 8 };
private:
    class Device;
private:
    Device* devices_;   //!<デバイス配列
    int deviceCnt_;     //!<デバイス数
    HWND hWnd_;         //!<マウス位置取得用ウィンドウハンドル
};
と上の方で入力を検出するボタンの種類を定義しています。
このボタンの種類番号に後でキーボードのキーやジョイスティックのキーを対応させていくことになります。
後は初期化時に使うデバイスの数を指定して生成し、
その後で個別のデバイスの初期化を行い、IsBtn***()系の関数で入力状態を調べていくという感じになっています。
後のソースは色々と前回から変わっていたりしますが、
ちょっと趣味で作ってしまったものなのであまり気にしないでください。
あと左上の方で現在の入力情報を表示しています。
で次回はもう少しまともに3D描画をやってみたいと思います。
今回のソース。
戻る。