Seaside Laboratory

Post

ハットスイッチ (POV) と DirectInput

Windows 8 に移行したらサターンパッドが使えなくなってしまったので、コントローラー変換アダプターを買って Wii のクラシックコントローラーをつなぐことにした。


ELECOM の JC-W01UWH。2 ポート版もほぼ同じ値段ではあるが、小さい 1 ポート版を購入。

この変換アダプター、十字キーをハットスイッチ (POV) として扱うので、軸入力を前提とした多くのゲームでは使うことができないのが難点。この現象は「ハットスイッチ問題」と呼ばれているようで、多くの人は JoyToKey を挟むことで回避している模様。購入前にこの問題を知ったので別の商品を買うという選択肢もあったが、DirectInput でハットスイッチの入力を軸入力に変換するプログラムが書きたかったので不便覚悟で購入。

POV からの入力に対応する

ハットスイッチの入力は DIJOYSTATE 構造体の rgdwPOV メンバに格納されるので取得自体は難しくない。上 (0 度) を基準にした時計回りの角度 (100 倍精度) が入っているので、この値を使えば 8 方向の検出は出来る。ネット上に落ちているコードは以下のように角度を決め打ちしたものが多い。

switch ( rgdwPOV )
{
case 0:
    return "上";
case 4500:
    return "右上";
case 9000:
    return "右";
case 13500:
    return "右下";
case 18000:
    return "下";
case 22500:
    return "左下";
case 27000:
    return "左";
case 31500:
    return "左上";
}

「デジタルパッドなら大丈夫」みたいなことが書かれているが、少しでも角度がずれると動かなくなってしまうコードはとても不安になるので、多段階のアナログな値が返ってきても正常に動くコードを書くことにした。

360 度を 8 方向に分解するので、45 度毎に方向が切り替わるという点は先程のコードと同じで、違うのは角度が特定の値と一致するかどうかではなく、範囲に収まっているかどうかで判定をすること。例えば上方向なら 0 度を中心とした -22 ~ +22 度までの範囲を持つので、

// 上への入力
if ( -2200 <= rgdwPOV && rgdwPOV <= +2200 )

みたいなコードを書くことになる。ただ、角度は 0 以上の値で表現されるので -22 度は 338 度と表現しなければならず、

// 上への入力
if ( +33800 <= rgdwPOV || rgdwPOV <= +2200 )

上方向だけ少し変則的な条件文になる。

角度の折り返しを意識したくないのと、大量の if 文に角度情報を埋め込んでいくのはよろしくないので、角度を方向番号に変換してから使うことにする。

  1. 角度を 22 度回転させることで折り返し地点を跨がないようにする。
  2. そのままだと 338 度以降が範囲外の値になってしまうので 360 で剰余を取る。
  3. 0 度から 45 度単位で分割された値になるので 45 で割って 0 ~ 7 の番号に変換する。
// 角度を方向番号に変換
int nDirection = (rgdwPOV + 2200) % 36000 / 4500;

後はこの値を軸情報に反映させる。

// DIJOYSTATE から軸データを取得
LONG lX = dijs.lX;
LONG lY = dijs.lY;

// ハットスイッチが中心にあるときは処理しない
if ( LOWORD( dwPOV ) != 0xffff )
{
    // 方向を元に軸をエミュレーション
    switch ( nDirection )
    {
    case 0: // 上
        lX = 0;
        lY = MINRANGE; // DIPROPRANGE.lMin と同じ値
        break;
    }
}

// 以下、軸から入力を読み取る既存のコード

ハットスイッチの入力があったら、DIPROPRANGE を設定した時の値を使って、軸が最小または最大まで傾けられたように見せかける。説明を分かりやすくするために switch を使っているが、実際のコードは方向と軸を対応付けたルックアップテーブルを使用している。

ハットスイッチの中心位置判定が少々特殊な理由については、DirectX のヘルプに書かれているので引用しておく。

中心位置は通常 -1 と報告されるが、これについては「注意」を参照すること。

注意
POV インジケータの中心位置を 65,535 と報告するドライバもある。
POV インジケータが中心位置にあるかどうかは、次のようにして確認する。

BOOL POVCentered = (LOWORD(dwPOV) == 0xFFFF);

「角度なんて 16 ビットで十分。WORD で -1 入れたれ!」というもったいない精神が働いたのだろうか。ろくにドキュメントを読まないアホが作った可能性も否定できない・・・。