Seaside Laboratory

Posts

ハットスイッチ (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 )

のような条件式を書くことになる。ただ、角度は符号なし整数として格納されているので -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 のヘルプに書かれているので引用しておく。

rgdwPOV[4]
最高 4 つまでの方向コントローラ (視点インジケータなど) の現在位置。この位置は、北から (ユーザーの正面側) 時計回りの実角度の 100 倍で表される。中心位置は通常 -1 と報告されるが、これについては「注意」を参照すること。

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

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

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