DXライブラリとC言語でゲーム制作その6。今回はゲームパッドで操作できるようにする方法について。XInputで3Dゲーム(=左右のスティックを使う)を作る前提です。
ゲームパッドで操作できるようにしたい
PCゲームの場合キーボードで操作できるようにするのが基本だがやはりゲームパッドで操作できるようにしたいところ(タイピングゲームとかなら別)。
ゲームパッドにはDirectInputとXInputという2つの形式があるが今回はXInputの方を採用。理由はDirectInputだと各社によってゲームパッドの仕様が異なりやりにくいため(特に右スティックが曲者)。
XInputは主にXBOXで使われるゲームパッドの形式だったが近年ではPCゲーム(特にSteam)で推奨されることが多い。ちなみにNintendo Switchで使われているのはDirectInput。
XInput対応のゲームパッドなんてないよ、という人はこちら↓

あと3Dゲーム(=左右のスティックを両方使う)で操作するのを前提にしてます。
実装
さっそく実装。以前作ったInput.cpp/hに以下のコードを追加。
Input.h
//以下の変数・関数を追加 const static int XINPUT_LEFTTRIGGER = 16; const static int XINPUT_RIGHTTRIGGER = 17; const static int XINPUT_THUNMBL_UP = 18; const static int XINPUT_THUNMBL_DOWN = 19; const static int XINPUT_THUNMBL_LEFT = 20; const static int XINPUT_THUNMBL_RIGHT = 21; const static int XINPUT_THUNMBR_UP = 22; const static int XINPUT_THUNMBR_DOWN = 23; const static int XINPUT_THUNMBR_LEFT = 24; const static int XINPUT_THUNMBR_RIGHT = 25; void Input_UpdateGanepad(); bool Input_GetGamepadDown(int KeyCode); int Input_GetGamepad(int KeyCode); bool Input_GetGamepadUp(int KeyCode); void Input_AllUpdate();
Input.cpp
//以下の変数・関数を追加 const static int GAMEPAD_MAX = 25; //ゲームパッドのボタンおよびスティックの総数 const static int TRIGGER_DEADZONE = 127; //トリガーの無効範囲(この値までを無効) const static int STICK_DEADZONE = 12000; //スティックの無効範囲(この値までを無効) static int Input_Gamepad[GAMEPAD_MAX]; //ゲームパッドが押されているフレーム数を格納する //ゲームパッドの入力状態を更新する void Input_UpdateGanepad() { int i; XINPUT_STATE input; //現在のゲームパッドの入力状態を格納する // 入力状態を取得 GetJoypadXInputState(DX_INPUT_PAD1, &input); for (i = 0; i < GAMEPAD_MAX; i++) { if (i < 16) { //ボタン if (input.Buttons[i] != 0) { //i番に対応するボタンが押されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_LEFTTRIGGER) { //Lトリガー if (input.LeftTrigger > TRIGGER_DEADZONE) { //Lトリガーが一定以上押し込まれていれば Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_RIGHTTRIGGER) { //Rトリガー if (input.RightTrigger > TRIGGER_DEADZONE) { //Rトリガーが一定以上押し込まれていれば Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBL_UP) { //左スティック if (input.ThumbLY > STICK_DEADZONE) { //左スティックが上に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBL_DOWN) { if (input.ThumbLY < -STICK_DEADZONE) { //左スティックが下に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBL_LEFT) { if (input.ThumbLX < -STICK_DEADZONE) { //左スティックが左に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBL_RIGHT) { if (input.ThumbLX > STICK_DEADZONE) { //左スティックが右に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBR_UP) { //右スティック if (input.ThumbRY > STICK_DEADZONE) { //右スティックが上に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBR_DOWN) { if (input.ThumbRY < -STICK_DEADZONE) { //右スティックが下に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBR_LEFT) { if (input.ThumbRX < -STICK_DEADZONE) { //右スティックが左に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } else if (i == XINPUT_THUNMBR_RIGHT) { if (input.ThumbRX > STICK_DEADZONE) { //右スティックが右に一定以上倒されていたら Input_Gamepad[i]++; //加算 } else { if (Input_Gamepad[i] > 0) { //前フレームで対応するキーが押されていれば Input_Gamepad[i] = -1; //-1にする。-1はキーが離された時用の値 } else { Input_Gamepad[i] = 0; //0にする } } } } } //指定のゲームパッドのボタンが押された瞬間か調べる bool Input_GetGamepadDown(int KeyCode) { if (KeyCode >= GAMEPAD_MAX) return false; if (Input_Gamepad[KeyCode] == 1) return true; return false; } //指定のゲームパッドのボタンが押されているか調べる。返り値は押されているフレーム数 int Input_GetGamepad(int KeyCode) { if (KeyCode >= GAMEPAD_MAX) return 0; return Input_Gamepad[KeyCode]; } //指定のゲームパッドのボタンが離された瞬間か調べる bool Input_GetGamepadUp(int KeyCode) { if (KeyCode >= GAMEPAD_MAX) return false; if (Input_Gamepad[KeyCode] == -1) return true; return false; } //全ての入力状態を更新する void Input_AllUpdate() { //キーボード Input_UpdateKeyboard(); //ゲームパッド Input_UpdateGanepad(); }
ゴリ押し感があるが気にしない。アナログ入力があるせい。
まず XINPUT_STATE input を定義して int GetJoypadXInputState() でボタンおよびスティックの生の情報を取得。あとはキーボードの時と同じく int Input_Gamepad[GAMEPAD_MAX] に押されているフレーム数を格納。あとはそのフレーム数で押された瞬間や離された瞬間を判定する。
トリガーおよびスティックの入力確認のときに使用している TRIGGER/STICK_DEADZONE は無効範囲の指定用。この値が小さいと誤判定されやすくなるのである程度大きめに。スティックの場合は SetJoypadDeadZone(int InputType, double Zone)で無効範囲を指定してもOK。
XINPUT_STATEや SetJoypadDeadZone についての詳細な情報はこちらのページにて。
Input.cpp/hにコードを追加したらあとは他のソースファイルのキー入力の更新・確認の部分を書き直していく。今回は System.cppのSystem_MainLoop() にあるキー入力更新の部分を以下のように変更。
//メインループ bool System_MainLoop() { while (1) { //中略 //全ての入力状態を更新(Input_UpdateKeyboard()から変更) Input_AllUpdate(); //中略 } return true; }
Menu.cppの Menu_Update() もキー入力確認の部分を以下のように変更。
//更新 void Menu_Update() { if (Input_GetKeyboardDown(KEY_INPUT_DOWN) || Input_GetGamepadDown(XINPUT_BUTTON_DPAD_DOWN) || Input_GetGamepadDown(XINPUT_THUNMBL_DOWN)) { //下キーが押されていたら NowSelect = (NowSelect + 1) % eMenu_Num;//選択状態を一つ下げる } if (Input_GetKeyboardDown(KEY_INPUT_UP) || Input_GetGamepadDown(XINPUT_BUTTON_DPAD_UP) || Input_GetGamepadDown(XINPUT_THUNMBL_UP)) { //上キーが押されていたら NowSelect = (NowSelect + (eMenu_Num - 1)) % eMenu_Num;//選択状態を一つ上げる } if (Input_GetKeyboardDown(KEY_INPUT_RETURN) || Input_GetGamepadDown(XINPUT_BUTTON_B)) { //エンターキーかBボタンが押されたら switch (NowSelect) {//現在選択中の状態によって処理を分岐 case eMenu_Game://ゲーム選択中なら SceneMgr_ChangeScene(eScene_Game);//シーンをゲーム画面に変更 break; case eMenu_Config://設定選択中なら SceneMgr_ChangeScene(eScene_Config);//シーンを設定画面に変更 break; default: break; } } }
あとは実行して十字キーの上下や左スティック、Bボタンを押して正しく処理されているかどうか確認する。
補足
とりあえずキーボードと同じようにゲームパッドで操作できるようにしてみたが、実際はキーボードとゲームパッドの入力をマージしてそれで入力を判定するのが基本。この機能を作る際一度ゲームでの操作操作(移動・ジャンプ・ダッシュ・攻撃etc)をまとめたものが必要になる。キーコンフィグを実装するときも同様。
ちなみに3Dゲーム内でプレイヤーを移動させるときは今回実装したものとは違う方法で求める。
続きについては実装でき次第更新。
コメント