【C++】XInputでゲームコントローラー入力を取得する方法を解説

サムネイル画像 C++

今回は、C++で「ゲームコントローラーをXInputで取得する方法」について解説していきます。

Xboxコントローラーをゲームで使いたい」「振動機能を実装したい」と思ったことはありませんか?

この記事を読み終えると、あなたはXInputを使ったコントローラー入力と振動制御をマスターできると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

XInputとは?

XInputは、Microsoftが開発したXboxコントローラー専用のAPIです。

Windows 7以降に標準搭載されており、Xbox 360コントローラー、Xbox Oneコントローラー、Xbox Series X|Sコントローラーなどに対応しています。

XInputの特徴

  • シンプルなAPI: DirectInputより簡単
  • ボタン配置が統一: 自動マッピング不要
  • 振動機能対応: 左右独立で制御可能
  • トリガーがアナログ: L2/R2が0~255の範囲
  • Xbox系限定: PlayStation系コントローラーは非対応
  • 同時接続は4台まで: ID 0~3のみ

C++の開発環境については、DebugビルドとReleaseビルドの違いも理解しておくと、開発がスムーズに進みます。

完全版のコードについて

この記事では、実装の要点を解説します。

完全なソースコードは、以下のGitHubリポジトリで公開しています。

🔗 https://github.com/it-kashiros/controller_xinput

リポジトリには以下のファイルが含まれています。

  • game_controller.h: コントローラークラスのヘッダー
  • game_controller.cpp: コントローラークラスの実装
  • main.cpp: デバッグ用のサンプルプログラム

Visual Studioで開いて、すぐにビルド・実行できます。

基本的な使い方

XInputでコントローラー入力を取得する基本的な流れは以下の通りです。

  1. XInputGetState()で入力状態を取得
  2. ボタン・スティック・トリガーの値を確認
  3. XInputSetState()で振動を制御

必要なヘッダーとライブラリ

▼game_controller.h(抜粋)

#pragma once
#include <windows.h>
#include <xinput.h>

#pragma comment(lib, "xinput.lib")

xinput.libをリンクするだけで、追加のセットアップは不要です。

C++のヘッダーファイルについては、プロトタイプ宣言とは?の記事でも解説しています。

実装のポイント

1. コントローラーの接続確認

XInputでは、コントローラーにID(0~3)が割り当てられます。

▼game_controller.cpp(抜粋)

XINPUT_STATE state;
ZeroMemory(&state, sizeof(XINPUT_STATE));

// コントローラー0番を確認
if (XInputGetState(0, &state) == ERROR_SUCCESS) {
    // 接続されている
    std::cout << "Controller 0 is connected.\n";
} else {
    // 接続されていない
    std::cout << "Controller 0 not found.\n";
}

実行結果の例

Controller 0 is connected.

ERROR_SUCCESSが返ってきたら、そのIDのコントローラーが使えます。

2. ボタンの判定

ボタンはビットフラグで取得されます。

▼game_controller.cpp(抜粋)

XINPUT_STATE state;
XInputGetState(0, &state);

// Aボタン(XINPUT_GAMEPAD_A)が押されているか
if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A) {
    std::cout << "Button A pressed!\n";
}

// Bボタン
if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B) {
    std::cout << "Button B pressed!\n";
}

実行結果の例

Button A pressed!

主なボタン定数は以下の通りです。

  • XINPUT_GAMEPAD_A : Aボタン
  • XINPUT_GAMEPAD_B : Bボタン
  • XINPUT_GAMEPAD_X : Xボタン
  • XINPUT_GAMEPAD_Y : Yボタン
  • XINPUT_GAMEPAD_DPAD_UP : 十字キー上
  • XINPUT_GAMEPAD_LEFT_SHOULDER : LBボタン
  • XINPUT_GAMEPAD_RIGHT_SHOULDER : RBボタン

3. スティックの取得

スティックの値は-32768 ~ 32767の範囲で取得されます。

▼game_controller.cpp(抜粋)

XINPUT_STATE state;
XInputGetState(0, &state);

// 左スティック
short leftX = state.Gamepad.sThumbLX;
short leftY = state.Gamepad.sThumbLY;

// 正規化(-1.0 ~ 1.0)
float normLX = (float)leftX / 32767.0f;
float normLY = (float)leftY / 32767.0f;

std::cout << "Left Stick X: " << normLX << "\n";
std::cout << "Left Stick Y: " << normLY << "\n";

実行結果の例

Left Stick X: 0.85
Left Stick Y: -0.42

さらに、デッドゾーンを適用して、わずかな傾きを無視します。

static float ApplyDeadzone(float value, float deadzone = 0.15f) {
    if (fabs(value) < deadzone) return 0.0f;
    
    float sign = (value > 0) ? 1.0f : -1.0f;
    float adjustedValue = (fabs(value) - deadzone) / (1.0f - deadzone);
    return sign * adjustedValue;
}

4. トリガーの取得

L2/R2トリガーは0~255の範囲で取得されます。

▼game_controller.cpp(抜粋)

XINPUT_STATE state;
XInputGetState(0, &state);

// L2トリガー
BYTE leftTrigger = state.Gamepad.bLeftTrigger;
// R2トリガー
BYTE rightTrigger = state.Gamepad.bRightTrigger;

// 正規化(0.0 ~ 1.0)
float normLT = (float)leftTrigger / 255.0f;
float normRT = (float)rightTrigger / 255.0f;

std::cout << "L2: " << normLT << "\n";
std::cout << "R2: " << normRT << "\n";

実行結果の例

L2: 0.00
R2: 0.82

5. 振動機能

XInputの大きな特徴が、振動機能(Vibration)です。

▼game_controller.cpp(抜粋)

XINPUT_VIBRATION vibration;
ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));

// 左モーター(低周波)を50%
vibration.wLeftMotorSpeed = 32767;
// 右モーター(高周波)を100%
vibration.wRightMotorSpeed = 65535;

// コントローラー0番に送信
XInputSetState(0, &vibration);

実行結果

コントローラーが振動します(左側が弱く、右側が強く振動)
  • wLeftMotorSpeed : 左モーター(0~65535)
  • wRightMotorSpeed : 右モーター(0~65535)

振動を止めるには、両方を0にします。

XINPUT_VIBRATION vibration;
ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));
XInputSetState(0, &vibration); // 振動停止

私が実際にこの機能を使った時の体験談

私がゲーム制作でXInputを初めて使った時、振動が止まらなくなるという問題に遭遇しました。

具体的には、プログラムが異常終了したとき、XInputSetState()で振動を停止する処理が実行されず、コントローラーがずっと震え続けてしまったのです。

原因と解決策

調査した結果、以下のことが原因でした。

  • 終了処理(Finalize())で振動を止めるコードを書いていたが、例外発生時に実行されなかった。
  • デバッグ中に強制終了(Ctrl+C)すると、終了処理がスキップされた。

最終的に、以下のような対応をしました。

  1. RAIIパターンを使い、クラスのデストラクタで必ず振動を停止するようにした。
  2. 振動開始から一定時間経過したら自動で止まる仕組みを導入した。
  3. 異常終了しても、次回起動時に全コントローラーの振動をリセットする処理を追加した。

この経験から、振動機能は必ず終了処理で止めることの重要性を学びました。

XInput使用時のよくあるエラーと対処法

初心者がハマりやすいポイントを3つ紹介します。

エラー1: XInputGetState()が常に失敗する

症状: コントローラーが接続されているのに、XInputGetState()ERROR_DEVICE_NOT_CONNECTEDを返す。

原因: 接続されているコントローラーがXInput非対応(PlayStation系など)か、別のドライバが優先されている。

解決策:

  • Xboxコントローラーを使用する。
  • デバイスマネージャーで「Xbox 360 Peripherals」または「Xbox Gaming Device」として認識されているか確認する。
  • DirectInput対応コントローラーの場合は、WinMM記事を参照してください。

エラー2: ボタンの反応が遅延する

症状: ボタンを押してから反応するまで数フレーム遅れる。

原因: XInputGetState()を毎フレーム呼び出していない。または、前フレームの状態を保存せずにトリガー判定を実装している。

解決策:

// 前フレームの状態を保存
static XINPUT_STATE prevState;
XINPUT_STATE currentState;

XInputGetState(0, ¤tState);

// トリガー判定(押された瞬間)
bool aTrigger = (currentState.Gamepad.wButtons & XINPUT_GAMEPAD_A) &&
                !(prevState.Gamepad.wButtons & XINPUT_GAMEPAD_A);

if (aTrigger) {
    std::cout << "Button A just pressed!\n";
}

// 前フレームの状態を更新
prevState = currentState;

エラー3: 振動が止まらない

症状: プログラム終了後もコントローラーが振動し続ける。

原因: 終了処理でXInputSetState()を呼び出していない。

解決策:

// 終了処理
void GameController::Finalize() {
    // すべてのコントローラーの振動を停止
    XINPUT_VIBRATION vibration;
    ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));
    
    for (int i = 0; i < 4; i++) {
        XInputSetState(i, &vibration);
    }
}

この処理を、プログラム終了前に必ず実行してください。

実際の使い方

実装したクラスは、以下のように使います。

▼使用例

#include "game_controller.h"

int main() {
    // 初期化
    GameController::Initialize();
    
    while (true) {
        // 毎フレーム更新
        GameController::Update();
        
        // 接続確認
        if (!GameController::IsConnected(0)) {
            continue;
        }
        
        // スティックの値を取得
        float leftX = GameController::GetLeftStickX(0);
        float leftY = GameController::GetLeftStickY(0);
        
        // ボタンの押下判定
        if (GameController::IsTrigger_ButtonA(0)) {
            // Aボタンが押された瞬間
            std::cout << "Button A pressed!\n";
        }
        
        // トリガー値の取得
        float rt = GameController::GetRightTrigger(0);
        if (rt > 0.5f) {
            // R2トリガーが50%以上押されている
            std::cout << "Right Trigger: " << rt << "\n";
        }
        
        // 振動
        if (GameController::IsPressed_ButtonB(0)) {
            // Bボタンを押している間、振動
            GameController::SetVibration(0, 0.5f, 0.5f);
        } else {
            // 振動停止
            GameController::SetVibration(0, 0.0f, 0.0f);
        }
    }
    
    // 終了処理
    GameController::Finalize();
    return 0;
}

実行結果の例

Controller 0 connected: Xbox One Controller
Left stick: X=0.00, Y=0.00
Button A pressed!
Right Trigger: 0.85
Vibration activated (B button held)

デバッグモニター

リポジトリのmain.cppには、コントローラーの入力をリアルタイムで表示するデバッグモニターが実装されています。

▼実行すると、以下のような画面が表示されます

スティックの動きやボタンの状態がリアルタイムで視覚化されるため、デバッグに便利です。

XInputとDirectInputの違い

XInputとDirectInputの主な違いを表にまとめました。

項目XInputDirectInput
対応コントローラーXbox系あらゆるコントローラー
ボタン配置統一されている機種ごとに異なる
振動機能○(標準対応)○(一部対応)
トリガーアナログ(0~255)アナログまたはボタン
同時接続4台まで制限なし
API難易度簡単やや複雑

Xboxコントローラーを使うならXInputを選択するのが正解です。

それ以外のコントローラーを使う場合は、WinMMやDirectInputを検討してください。

注意点

1. Xboxコントローラー専用

XInputはXbox系コントローラー専用のAPIです。

  • ✅ Xbox 360コントローラー
  • ✅ Xbox Oneコントローラー
  • ✅ Xbox Series X|Sコントローラー
  • ❌ PlayStationコントローラー
  • ❌ Nintendo Switchコントローラー
  • ❌ 汎用DirectInputコントローラー

Xbox系以外のコントローラーを使う場合は、別のAPIが必要です。

2. 同時接続は4台まで

XInputでは、コントローラーのIDが0~3の4つに制限されています。

5台以上のコントローラーを同時に使う場合は、DirectInputなどを併用する必要があります。

3. 振動は終了時に必ず停止

振動機能を使う場合、プログラム終了時に必ず停止処理を実行してください。

そうしないと、コントローラーが振動し続けてしまいます。

動作確認

リポジトリをダウンロードして、Visual Studioで開いてください。

  1. GitHubからZIPをダウンロード
  2. Visual Studioでプロジェクトを開く
  3. Xboxコントローラーをパソコンに接続
  4. ビルド&実行(F5)
  5. コントローラーを操作して、入力が表示されることを確認

ESCキーで終了できます。

まとめ

XInputを使ったコントローラー入力取得についてまとめます。

  • XInput: Xbox系コントローラー専用のシンプルなAPI
  • XInputGetState()でボタン・スティック・トリガーを取得
  • XInputSetState()で振動を制御
  • スティックは-32768~32767、トリガーは0~255の範囲
  • デッドゾーン適用でわずかな傾きを無視
  • 振動は必ず終了時に停止する
  • PlayStation系やNintendo系コントローラーは非対応
  • 完全なコードはGitHubリポジトリで公開中

これでXInputを使ったコントローラー入力の取得はバッチリですね!

GitHubのコードを参考に、自分のゲームプロジェクトに組み込んでみてください。

ループ処理については、i++と++iの違いの記事も参考になるかもしれません。

ここまで読んでくださり、ありがとうございました。

この記事が皆様の学習に役立てば幸いです。