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 = 26; //ゲームパッドのボタンおよびスティックの総数
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ゲーム内でプレイヤーを移動させるときは今回実装したものとは違う方法で求める。
続きにはこちら↓

コメント