ゆとりーなの日記

日記的な事を書いて行くと思はれる

第10回〜ジョイパッド入力〜

前回はキーボード入力をやりましたが、ゲームなのにジョイパッド入力ができないとかないわ〜って言われないようにジョイパッドの入力もやっておきましょう。
ジョイパッドの入力にもDirectInputが使われることが多いのですが、前回言ったようにDirectInputの使用は残念ながら非推奨です。代わりに使用が推奨されているのがXInputですが、この子が扱えるパッドがXBox360のコントローラー位しかないというズボラっぷりを発揮しているのであまり使いたくありません。そこで今回はあまり何も評判を聞かないwin32APIを使って敢えて実装してみようと思います。今回から"winmm.lib"をリンクしておく必要があります。
取り敢えずジョイパッド入力の処理を担当するクラスを作ります。使うAPIの仕様上二つしかパッドを扱えません。あと振動機能も駄目です。

// input.h
#pragma once
#include "padcode.h"

namespace Input {
bool joypadKeyDown(JoypadID id, JoypadCode joypadcode);
float joypadAnalogX(JoypadID id);
float joypadAnalogY(JoypadID id);
}

実装上インスタンス化する意味があまりないのと、グローバル関数は嫌われるのでInput名前空間で括ります。
実装です。

// input.cc
#include "input.h"

namespace Input {
bool joypadKeyDown(JoypadID id, JoypadCode joypadcode) {
    JOYINFOEX joypad_info;
    JOYCAPS joypad_caps;
    joyGetDevCaps(id, &joypad_caps, sizeof(joypad_caps));
    joypad_info.dwSize  = sizeof(joypad_info);
    joypad_info.dwFlags = JOY_RETURNBUTTONS;
    if (joyGetPosEx(id, &joypad_info) == JOYERR_NOERROR) {
        if (joypadcode <= PAD_INPUT_28) {
            return (joypad_info.dwButtons & joypadcode) != 0;
        } else if (joypadcode == PAD_INPUT_UP) {
            return (joypad_info.dwYpos < joypad_caps.wYmin + (joypad_caps.wYmax - joypad_caps.wYmin) / 3) != 0;
        } else if (joypadcode == PAD_INPUT_DOWN) {
            return (joypad_info.dwYpos > joypad_caps.wYmax - (joypad_caps.wYmax - joypad_caps.wYmin) / 3) != 0;
        } else if (joypadcode == PAD_INPUT_LEFT) {
            return (joypad_info.dwXpos < joypad_caps.wXmin + (joypad_caps.wXmax - joypad_caps.wXmin) / 3) != 0;
        }else if (joypadcode == PAD_INPUT_RIGHT) {
            return (joypad_info.dwYpos > joypad_caps.wXmax - (joypad_caps.wXmax - joypad_caps.wXmin) / 3) != 0;
        } else {
            return false;
        }
    }
    return false;
}

float joypadAnalogX(JoypadID id) {
    JOYINFOEX joypad_info;
    JOYCAPS joypad_caps;
    joyGetDevCaps(id, &joypad_caps, sizeof(joypad_caps));
    joypad_info.dwSize  = sizeof(joypad_info);
    joypad_info.dwFlags = JOY_RETURNX;
    if (joyGetPosEx(id, &joypad_info) == JOYERR_NOERROR) {
        const float center((static_cast<float>(joypad_caps.wXmin + joypad_caps.wXmax)) / 2.f);
        const float diff(static_cast<float>(joypad_caps.wXmax - joypad_caps.wXmin));
        const float val((static_cast<float>(joypad_info.dwXpos) - center) * 2.f / diff);
        return fabsf(val) < 0.1f ? 0.f : val;
    }
    return false;
}

float joypadAnalogY(JoypadID id) {
    JOYINFOEX joypad_info;
    JOYCAPS joypad_caps;
    joyGetDevCaps(id, &joypad_caps, sizeof(joypad_caps));
    joypad_info.dwSize  = sizeof(joypad_info);
    joypad_info.dwFlags = JOY_RETURNY;
    if (joyGetPosEx(id, &joypad_info) == JOYERR_NOERROR) {
        const float center((static_cast<float>(joypad_caps.wYmin + joypad_caps.wYmax)) / 2.f);
        const float diff(static_cast<float>(joypad_caps.wYmax - joypad_caps.wYmin));
        const float val((static_cast<float>(joypad_info.dwYpos) - center) * 2.f / diff);
        return fabsf(val) < 0.1f ? 0.f : val;
    }
    return false;
}
}

Input::joypadKeyDown関数では、指定したIDのジョイパッドの指定したボタンが押されていればtrueを返す関数です。joyGetDevCaps関数で、ジョイパッドの性能を調べ、JOYCAPS構造体のdwSizeメンバにサイズとdwFlagsメンバにボタン入力を得るフラグを立てた後joyGetPosExを使って現在の入力状態を調べます。返り値がJOYERR_NOERROR以外だったらエラーなのでfalseを返しときます。ボタン入力は、JOYINFOEX構造体のdwButtonsメンバには各キーの状態が入っています。ボタンが押されていればそのボタン番号のビットが立っているので、ボタン番号と&した結果で調べます。十字キーの方は傾き具合が縦方向、横方向それぞれdwXpos、dwYposメンバに入っています。なんか変な計算をしているのは、遊びの部分を入力ととらないようにする工夫的な何かです。
joypadAnalogX関数とjoypadAnalogY関数はそれぞれアナログスティックのX軸、Y軸の傾き具合を返します。JOYCAPS構造体のdwFlagsメンバには、アナログスティックの軸の傾き具合を取得するフラグJOY_RETURNX、JOY_RETURNYを入れてからjoyGetPosExを使って傾き具合を調べます。変な計算をしているのはまた例によって遊び部分を傾きとして取らないようにする工夫的な何かです。微妙な傾きは無視してあげた方が多分快適に操作できます。
ところでこれらの関数にはJoypadIDとかJoypadCodeとか変な値を渡していますが、これはなにか変な値を入れられない様に用意した列挙体です。

// padcode.h
#include <mmsystem.h>

enum JoypadID {
    INPUT_PAD1 = JOYSTICKID1,
    INPUT_PAD2 = JOYSTICKID2
};

enum JoypadCode {
    PAD_INPUT_1 = JOY_BUTTON1,
    PAD_INPUT_2 = JOY_BUTTON2,
    PAD_INPUT_3 = JOY_BUTTON3,
    PAD_INPUT_4 = JOY_BUTTON4,
    PAD_INPUT_5 = JOY_BUTTON5,
    PAD_INPUT_6 = JOY_BUTTON6,
    PAD_INPUT_7 = JOY_BUTTON7,
    PAD_INPUT_8 = JOY_BUTTON8,
    PAD_INPUT_9 = JOY_BUTTON9,
    PAD_INPUT_10 = JOY_BUTTON10,
    PAD_INPUT_11 = JOY_BUTTON11,
    PAD_INPUT_12 = JOY_BUTTON12,
    PAD_INPUT_13 = JOY_BUTTON13,
    PAD_INPUT_14 = JOY_BUTTON14,
    PAD_INPUT_15 = JOY_BUTTON15,
    PAD_INPUT_16 = JOY_BUTTON16,
    PAD_INPUT_17 = JOY_BUTTON17,
    PAD_INPUT_18 = JOY_BUTTON18,
    PAD_INPUT_19 = JOY_BUTTON19,
    PAD_INPUT_20 = JOY_BUTTON20,
    PAD_INPUT_21 = JOY_BUTTON21,
    PAD_INPUT_22 = JOY_BUTTON22,
    PAD_INPUT_23 = JOY_BUTTON23,
    PAD_INPUT_24 = JOY_BUTTON24,
    PAD_INPUT_25 = JOY_BUTTON25,
    PAD_INPUT_26 = JOY_BUTTON26,
    PAD_INPUT_27 = JOY_BUTTON27,
    PAD_INPUT_28 = JOY_BUTTON28,
    PAD_INPUT_UP = JOY_BUTTON29,
    PAD_INPUT_DOWN = JOY_BUTTON30,
    PAD_INPUT_LEFT = JOY_BUTTON31,
    PAD_INPUT_RIGHT = JOY_BUTTON32,
};

joyGetDevCaps関数には、JOYSTICKID1かJOYSTICKID2を入れるようにするようになっているらしいのでそれしか入れたくならないようにJoypadID列挙体を取る様にしてみました。JoypadCode列挙体はボタンの種類です。JOYINFOEX構造体のdwButtonsメンバで各ボタンが押されているかを調べるには、JOY_BUTTON1〜JOY_BUTTON32と&することになっているですが、32個もボタンがあることはまずないだろうと勝手に判断してInput::joypadKeyDown関数では後ろ4つを十字キーの入力を調べるように勝手にしました。