Archives

August 2011 の記事

引き続き Android OS 3.1 に Xbox360 Pad と Playstation3 Pad を
つないだ場合の値の取り方です。(前回)
アナログスティックの情報は onGenericMotionEvent で受け取れます。

@Override
public boolean onGenericMotionEvent( MotionEvent event ) {
  float posx= event.getAxisValue( MotionEvent.AXIS_X );
  ~
}

入力値に差が生じた場合のみイベントが発生します。
getX()/getY() は getAxisValue() の AXIS_X, AXIS_Y と同値です。

対応付は下記の通り

axis            Xbox360Pad   PS3 Pad       範囲  Source
-----------------------------------------------------------------------
AXIS_X          左stick 横   左stick 横   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_Y          左stick 縦   左stick 縦   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_Z          右stick 横   右stick 横   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_RZ         右stick 縦   右stick 縦   -1~1  SOURCE_CLASS_JOYSTICK
AXIS_HAT_X      十字キー横   ----         -1~1  SOURCE_CLASS_JOYSTICK
AXIS_HAT_Y      十字キー縦   ----         -1~1  SOURCE_CLASS_JOYSTICK
AXIS_LTRIGGER   Lトリガ      L2            0~1
AXIS_RTRIGGER   Rトリガ      R2            0~1

左上が (-1,-1), 右下が (1,1)

Xbox360 Pad のみ、デジタル方向ボタン(十字キー) が MotionEvent の
AXIS_HAT_X/AXIS_HAT_Y を返します。(KeyEvent も発生します)

他にも MotionEvent と同時に KeyEvent を発生させるものがあります。

◎Xbox360 Pad / PS3 Pad
・AXIS_X           KEYCODE_DPAD_LEFT/KEYCODE_DPAD_RIGHT
・AXIS_Y           KEYCODE_DPAD_UP/KEYCODE_DPAD_DOWN

◎PS3 Pad のみ
・AXIS_LTRIGGER    KEYCODE_L2
・AXIS_RTRIGGER    KEYCODE_R2

◎Xbox360 Pad のみ
・AXIS_HAT_X       KEYCODE_DPAD_LEFT/KEYCODE_DPAD_RIGHT
・AXIS_HAT_Y       KEYCODE_DPAD_UP/KEYCODE_DPAD_DOWN


●デジタル方向キーとアナログスティックの区別方法

ここで問題となるのは、左アナログスティックで MotionEvent だけでなく
KeyEvent も発生してしまうことです。
デジタル方向ボタンの代わりに左アナログスティックを使うことができる反面、
アナログとデジタルに異なる操作を割り当てたいときに困ります。


KeyCode だけではこの両者を区別できないので方法を探しました。

Xbox360 Pad の方向キーは AXIS_HAT_X/AXIS_HAT_Y からも値をとれるため
KeyEvent を参照せずにこちらを使えば区別することができます。


PS3 Pad の場合は SOURCE_CLASS で区別できるようです。

訂正 2011/08/11 不要でした。Xbox360 のみ AXIS → KeyCode 変換だけで OK です。

KeyEvent.getSource() & InputDevice.SOURCE_CLASS_MASK

PS3 のみデジタル方向キーが SOURCE_CLASS_BUTTON となります。

◎KeyEvent の SOURCE_CLASS 値

                    Xbox360 Pad              PS3 Pad
--------------------------------------------------------------------
デジタル方向キー    SOURCE_CLASS_JOYSTICK    SOURCE_CLASS_BUTTON
アナログ方向キー    SOURCE_CLASS_JOYSTICK    SOURCE_CLASS_JOYSTICK


◎MotionEvent の情報

                    Xbox360 Pad              PS3 Pad
--------------------------------------------------------------------
デジタル方向キー    AXIS_HAT_X/AXIS_HAT_Y    無し
アナログ方向キー    AXIS_X/AXIS_Y            AXIS_X/AXIS_Y

実際のコード(Activity 抜粋)は下記の通り。
これで Xbox360 Pad / PS3 Pad 共に、デジタル方向ボタンを
KEYCODE_DPAD_UP/DOWN/LEFT/RIGHT を受け取れるようになります。
(プログラムコード訂正しました 2011/08/11 )

final static int   SENDKEY_UP      =   0;
final static int   SENDKEY_DOWN    =   1;

private int mPrevState= 0;

private boolean sendKey( int KeyCode, int up_down ) {
   // 実際のキーコード処理

   ~
}

@Override
public boolean onKeyDown( int KeyCode, KeyEvent event ) {
    return  sendKey( KeyCode, SENDKEY_DOWN );
}

@Override
public boolean onKeyUp( int KeyCode, KeyEvent event ) {
    return  sendKey( KeyCode, SENDKEY_UP );
}

@Override
public boolean onGenericMotionEvent( MotionEvent event ) {
    // Xbox360 の 十字キーはこれで判別
    final int   KEYFLAG_UP      = (1<<0);
    final int   KEYFLAG_DOWN    = (1<<1);
    final int   KEYFLAG_LEFT    = (1<<2);
    final int   KEYFLAG_RIGHT   = (1<<3);
    final float HAT_BORDER= 0.5f;

    float hatx= event.getAxisValue( MotionEvent.AXIS_HAT_X );
    float haty= event.getAxisValue( MotionEvent.AXIS_HAT_Y );

    int code= 0;
    if( hatx < -HAT_BORDER ){
        code|= KEYFLAG_LEFT;
    }
    if( hatx >  HAT_BORDER ){
        code|= KEYFLAG_RIGHT;
    }
    if( haty < -HAT_BORDER ){
        code|= KEYFLAG_UP;
    }
    if( haty >  HAT_BORDER ){
        code|= KEYFLAG_DOWN;
    }

    int diff= code ^ mPrevState;
    mPrevState= code;
    for( int kc= KeyEvent.KEYCODE_DPAD_UP ; diff != 0 ; diff>>= 1, code>>= 1, kc++ ){
        if( (diff & 1) != 0 ){
            // 代わりのキーコードを発生させる
            sendKey( kc, (code & 1) != 0 ? SENDKEY_DOWN : SENDKEY_UP );
        }
    }


    // アナログ値の受け取り
    float lx= event.getAxisValue( MotionEvent.AXIS_X );
    float ly= event.getAxisValue( MotionEvent.AXIS_Y );
    float rx= event.getAxisValue( MotionEvent.AXIS_Z );
    float ry= event.getAxisValue( MotionEvent.AXIS_RZ );
    float tl= event.getAxisValue( MotionEvent.AXIS_LTRIGGER );
    float tr= event.getAxisValue( MotionEvent.AXIS_RTRIGGER );

    ~
}


●複数のパッドの認識

USB ハブを経由して、同時に複数のコントローラを接続することができました。
どのコントローラも上記の同じイベントを発生させます。

getDeviceId() を参照することで、どのコントローラが送ったイベントなのか
区別できるようです。

Xbox360 Pad と PS3 Pad の組み合わせは OK、PS3 Pad を同時に 2個つないでも
きちんと区別できました。バスパワーハブだったため 3個以上は試していません。


● Playstation3 Pad (PS3 Pad) の注意点

PS3 Pad はもともとワイヤレスコントローラですが、Android 3.1 の場合
USB ケーブルで繋ぎます。

 1. PS3 Pad が電源 OFF の状態を確認
 2. USB ケーブルで Android 3.1 端末に繋ぐ
 3. (PS) ボタンを押す
 4. LED 4つが点滅したままだけど問題なし。そのまま使える。

接続前に (PS) ボタンを押してしまうとペアリングしてある PS3 本体の
電源が入ってしまうので要注意です。
ワイヤレスで PS3 本体につながってしまうと USB ケーブルをつないでも
PS3 が優先され Android で認識できません。
最初の頃、遠くにある PS3 本体の電源がいつの間にか入っていて、
なかなかつながらずにはまりました。


関連エントリ
Android 3.1 と GamePad のイベントコード


Acer ICONIA TAB A500 が Android OS 3.1 になったので早速つないでみました。
Android 3.1 からは USB でゲームパッドがつながります。

 ・Xbox 360 用の USB コントローラ
 ・Playstation3 用コントローラを USB (有線)接続

どちらもきちんと認識しました。
判定の仕方は下記のとおりです。

ボタン類は通常のキーボードと同じ、onKey~ でキーコードとして通知されます。

十字キーはカーソルキーと同じ KEYCODE_DPAD_UP/DOWN/LEFT/RIGHT
(19~22) です。
そのため取り敢えずつないだけでも HOME 画面などでカーソル移動に使えます。
未確認ですが、おそらく KeyEvent.isGamepadButton() で本来のカーソルキーと
区別できると思われます。
訂正2011/08/10 13:22

ボタン類は専用のキーコード(96~)が割り当てられているようです。

KEYCODE_BUTTON_A        96  □
KEYCODE_BUTTON_B        97  △
KEYCODE_BUTTON_C        98
KEYCODE_BUTTON_X        99  ×
KEYCODE_BUTTON_Y       100  ◯
KEYCODE_BUTTON_Z       101
KEYCODE_BUTTON_L1      102
KEYCODE_BUTTON_R1      103
KEYCODE_BUTTON_L2      104
KEYCODE_BUTTON_R2      105
KEYCODE_BUTTON_THUMBL  106
KEYCODE_BUTTON_THUMBR  107
KEYCODE_BUTTON_START   108
KEYCODE_BUTTON_SELECT  109  (BACK)
KEYCODE_BUTTON_MODE    110  (Xbox)
                       188  (PS)

Xbox360 と PS3 ほぼ上記のキーアサイン通りなのですが微妙に互換性がありません。
BUTTON_X は 360 だと (X) ボタンで PS3 は (×) バツボタンです。
AB/XY が入れ替わっているような感じです。
360 Pad 中央の Xbox ボタンは KEYCODE_BUTTON_MODE ですが、
PS ボタンは異なる数値 (188) でした。

アナログ操作は onGenericMotionEvent で MotionEvent が来ます。
L/Rトリガは 360 は MotionEvent だけですが PS3 だと
MotionEvent + BUTTON_L2/R2 の両方が返るようです。

もう少し調査が必要ですが、取り敢えず PC 等と操作方法の
互換性が保てるので便利になりそうです。