ゲームパッドと振動
このページでは、[Windows.Gaming.Input.Gamepad][game pad] を使用したゲーム パッドのプログラミングの基本と、ユニバーサル Windows プラットフォーム (UWP) の関連 API について説明します。
ここでは、次の項目について紹介します。
- 接続されているゲーム パッドとそのユーザーの一覧を収集する方法
- ゲーム パッドが追加または削除されたことを検出する方法
- 1 つ以上のゲーム パッドから入力を読み取る方法
- 振動とインパルス コマンドを送信する方法
- ゲーム パッドが UI ナビゲーション デバイスとして動作する方法
ゲームパッドの概要
Xbox ワイヤレス コントローラーや Xbox ワイヤレス コントローラー S などのゲームパッドは、汎用ゲーム入力デバイスです。 これらは Xbox One の標準入力デバイスであり、キーボードとマウスを優先しない Windows ゲーマーにとって一般的な選択肢です。 ゲームパッドは、Windows.Gaming.Input 名前空間によって、Windows 10 または Windows 11 および Xbox UWP アプリでサポートされています。
Xbox One ゲームパッドには、方向パッド、A、B、X、Y、ビュー、およびメニューの各ボタン、左右のサムスティック、L ボタンと R ボタン、およびトリガー、そして、合計 4 個の振動モーターがあります。 どちらのサムスティックも、X 軸と Y 軸でデュアル アナログ読み取り値を提供し、内側に押すとボタンとしても機能します。 どちらも、どの程度戻されたかを示すアナログの読み取り値を提供します。
Note
Windows.Gaming.Input.Gamepad
は Xbox 360 ゲームパッドもサポートします。このコントロール レイアウトは、標準の Xbox One ゲームパッドと同じです。
振動とインパルス トリガー
Xbox One ゲームパッドは、強力と微弱なゲームパッドの振動に 2 つの独立したモーターを、かつ各トリガーに鋭い振動を提供するための 2 つの専用モーターを提供しています (このユニークな機能は、Xbox One ゲームパッド トリガーがインパルス トリガーと呼ばれる理由です)。
Note
Xbox 360 ゲームパッドにはリアル トリガーは搭載されていません。
詳細については、「振動トリガーとインパルス トリガーの概要」を参照してください。
サムスティックのデッドゾーン
中心位置の保存時のサムスティックは、X 軸と Y 軸で毎回同じニュートラルな読み取り値を生成することが理想的です。 しかし、機械的な力とサムスティックの感度により、中心位置の実際の読み取り値は理想的な中立値に近いだけで、後続の読み取り値によって異なる場合があります。 そのため、小さなデッドゾーン (理想の中央の位置付近の、無視される範囲の値) を常に使用して、製造上の差異や、機械的な磨耗、またはその他のゲームパッドの問題を吸収します。
デッドゾーンが大きいほど、意図的な入力と意図しない入力を分離するための簡単な戦略が提供されます。
詳細については、「サムスティックの読み取り」を参照してください。
UI ナビゲーション
ユーザー インターフェイス ナビゲーションのためにさまざまな入力デバイスをサポートする負担を軽くし、ゲームとデバイスの間の一貫性を促進するために、ほとんどの物理入力デバイスは、UI ナビゲーション コントローラーと呼ばれる別の論理入力デバイスとして同時に機能します。 UI ナビゲーション コントローラーは、各種入力デバイスに共通の UI ナビゲーション コマンドのボキャブラリを提供します。
UI ナビゲーション コントローラーとして、ゲームパッドは、ナビゲーション コマンドの必須セットを左のサムスティック、方向パッド、ビュー ボタン、メニュー ボタン、A ボタン、およびB ボタンにマップします。
ナビゲーション コマンド | ゲームパッドの入力 |
---|---|
上へ | 左サムスティックを上 / D パッドを上 |
[下へ] | 左サムスティックを下 / D パッドを下 |
Left | 左サムスティックを左 / D パッドを左 |
Right | 左サムスティックを右 / D パッドを右 |
ビュー | View ボタン |
メニュー | Menu ボタン |
同意する | A ボタン |
キャンセル | B ボタン |
さらに、ゲームパッドは、ナビゲーション コマンドのオプション セットをすべて、残りの入力にマップします。
ナビゲーション コマンド | ゲームパッドの入力 |
---|---|
Page Up | 左トリガー |
Page Down | 右トリガー |
ページを左 | 左バンパー |
ページを右 | 右バンパー |
上にスクロール | 右サム スティック上 |
下にスクロール | 右サム スティック下 |
左へスクロール | 右サム スティック左 |
右へスクロール | 右サム スティック右 |
コンテキスト 1 | X ボタン |
コンテキスト 2 | Y ボタン |
コンテキスト 3 | 左サムスティック押下 |
コンテキスト 4 | 右サムスティック押下 |
ゲームパッドを検出して追跡する
ゲームパッドはシステムによって管理されるため、ゲームパッドを作成または初期化する必要はありません。 システムには、接続されているゲームパッドとイベントの一覧が表示され、ゲームパッドが追加または削除されたときに通知されます。
ゲームパッドの一覧
Gamepad クラスは、静的プロパティである Gamepads を提供します。これは、現在接続されているゲームパッドの読み取り専用リストです。 接続されているゲームパッドの一部しか必要ない場合もあるため、Gamepads
プロパティを利用してデバイスにアクセスするのではなく、独自のコレクションを保持しておくことをお勧めします。
次の例では、接続されているすべてのゲームパッドを新しいコレクションにコピーします。 バックグラウンドの他のスレッドがこのコレクションにアクセスするため (GamepadAdded および GamepadRemoved で)、コレクションを読み込む、または更新するコードをロックする必要があることに注意してください。
auto myGamepads = ref new Vector<Gamepad^>();
critical_section myLock{};
for (auto gamepad : Gamepad::Gamepads)
{
// Check if the gamepad is already in myGamepads; if it isn't, add it.
critical_section::scoped_lock lock{ myLock };
auto it = std::find(begin(myGamepads), end(myGamepads), gamepad);
if (it == end(myGamepads))
{
// This code assumes that you're interested in all gamepads.
myGamepads->Append(gamepad);
}
}
private readonly object myLock = new object();
private List<Gamepad> myGamepads = new List<Gamepad>();
private Gamepad mainGamepad;
private void GetGamepads()
{
lock (myLock)
{
foreach (var gamepad in Gamepad.Gamepads)
{
// Check if the gamepad is already in myGamepads; if it isn't, add it.
bool gamepadInList = myGamepads.Contains(gamepad);
if (!gamepadInList)
{
// This code assumes that you're interested in all gamepads.
myGamepads.Add(gamepad);
}
}
}
}
ゲームパッドの追加と削除
ゲームパッドが追加または削除されると、GamepadAdded イベントおよび GamepadRemoved イベントが発生します。 これらのイベントのハンドラーを登録して、現在接続されているゲームパッドを追跡できます。
次の例では、追加されたゲームパッドの追跡が開始されます。
Gamepad::GamepadAdded += ref new EventHandler<Gamepad^>(Platform::Object^, Gamepad^ args)
{
// Check if the just-added gamepad is already in myGamepads; if it isn't, add
// it.
critical_section::scoped_lock lock{ myLock };
auto it = std::find(begin(myGamepads), end(myGamepads), args);
if (it == end(myGamepads))
{
// This code assumes that you're interested in all new gamepads.
myGamepads->Append(args);
}
}
Gamepad.GamepadAdded += (object sender, Gamepad e) =>
{
// Check if the just-added gamepad is already in myGamepads; if it isn't, add
// it.
lock (myLock)
{
bool gamepadInList = myGamepads.Contains(e);
if (!gamepadInList)
{
myGamepads.Add(e);
}
}
};
次の例では、削除されたゲームパッドの追跡が停止されます。 また、削除されたときに追跡しているゲームパッドの動作を処理する必要もあります。たとえば、このコードでは、1 つのゲームパッドからの入力のみを追跡し、削除されたら nullptr
に設定するだけです。 ゲームパッドがアクティブになっている場合はすべてのフレームを確認し、コントローラーが接続されてから切断されるまでどのゲームパッドで入力が収集されているかを更新する必要があります。
Gamepad::GamepadRemoved += ref new EventHandler<Gamepad^>(Platform::Object^, Gamepad^ args)
{
unsigned int indexRemoved;
critical_section::scoped_lock lock{ myLock };
if(myGamepads->IndexOf(args, &indexRemoved))
{
if (m_gamepad == myGamepads->GetAt(indexRemoved))
{
m_gamepad = nullptr;
}
myGamepads->RemoveAt(indexRemoved);
}
}
Gamepad.GamepadRemoved += (object sender, Gamepad e) =>
{
lock (myLock)
{
int indexRemoved = myGamepads.IndexOf(e);
if (indexRemoved > -1)
{
if (mainGamepad == myGamepads[indexRemoved])
{
mainGamepad = null;
}
myGamepads.RemoveAt(indexRemoved);
}
}
};
詳細については、「ゲームの入力プラクティス」を参照してください。
ユーザーとヘッドセット
各ゲームパッドをユーザー アカウントに関連付けて、自分の ID をゲームプレイにリンクしたり、ボイス チャットやゲーム内機能を容易にするためにヘッドセットをアタッチしたりできます。 ユーザーとヘッドセットの操作の詳細については、「ユーザーとそのデバイスの追跡」と「ヘッドセット」を参照してください。
ゲームパッドの読み取り
関心のあるゲームパッドを特定したら、それから入力を収集する準備が整いました。 ただし、慣れている他の種類の入力とは異なり、ゲームパッドはイベントを発生させることで状態変化を伝えるのではありません。 代わりに、イベントをポーリングすることで現在の状態を定期的に読み取ります。
ゲームパッドのポーリング
ポーリングによって、ナビゲーション デバイスの正確な時点のスナップショットがキャプチャされます。 入力収集に対するこのアプローチは、ほとんどのゲームに適しています。通常、そのロジックがイベントドリブンではなく確定的なループで実行されるためです。また、一般にゲーム コマンドを解釈するのは、一度に収集された入力からの方が、時間の経過につれて収集される多数の単一の入力からよりも簡単です。
GetCurrentReading を呼び出してゲームパッドをポーリングします。この関数は、ゲームパッドの状態を含む GamepadReading を返します。
次の例では、ゲームパッドの現在の状態をポーリングします。
auto gamepad = myGamepads[0];
GamepadReading reading = gamepad->GetCurrentReading();
Gamepad gamepad = myGamepads[0];
GamepadReading reading = gamepad.GetCurrentReading();
ゲームパッドの状態に加えて、各読み取りには、状態がいつ取得されたかを正確に示すタイムスタンプが含まれます。 このタイムスタンプは、以前の読み取りのタイミングや、ゲームのシミュレーションのタイミングと関連付けに便利です。
サムスティックの読み取り
各サムスティックは、X 軸と Y 軸で -1.0 ~ +1.0 のアナログ読み取り値を提供します。 X 軸では、値 -1.0 は最も左のサムスティック位置に対応します。+1.0 の値は最も右の位置に対応します。 Y 軸では、値 -1.0 は最も下のサムスティック位置に対応します。+1.0 の値は最も上の位置に対応します。 どちらの軸も、スティックが中央の位置にある場合、値は約 0.0 になりますが、正確な値は、後続の読み取り値間であっても、異なるのが普通です。このばらつきを緩和する対策については、このセクションで後ほど説明します。
左サムスティックの X 軸の値は、GamepadReading 構造体の LeftThumbstickX
プロパティから読み取られます。Y 軸の値は、LeftThumbstickY
プロパティから読み取られます。 右サムスティックの X 軸の値は RightThumbstickX
プロパティから読み取られます。Y 軸の値は RightThumbstickY
プロパティから読み取られます。
float leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
float leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
float rightStickX = reading.RightThumbstickX; // returns a value between -1.0 and +1.0
float rightStickY = reading.RightThumbstickY; // returns a value between -1.0 and +1.0
double leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
double leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
double rightStickX = reading.RightThumbstickX; // returns a value between -1.0 and +1.0
double rightStickY = reading.RightThumbstickY; // returns a value between -1.0 and +1.0
サムスティックの値を読み取ると、サムスティックが中央の位置に保存されている場合、0.0 のニュートラルな読み取り値が確実に生成されないことがわかります。代わりに、サムスティックを移動して中央の位置に戻すたびに、0.0 付近に異なる値が生成されます。 このばらつきを少なくするために、小さなデッドゾーンを実装します。デッドゾーン+は、理想の中央の位置付近の、無視される範囲の値です。 デッドゾーンを実装する 1 つの方法は、サムスティックが移動した中心からどのくらい離れているかを判断し、選択した距離よりも近い読み取り値を無視することです。 この距離は、ピタゴラスの定理を使って、概算できます (サムスティックの読み取り値は、平面値ではなく、本質的に極値であるため正確な計算にはなりません)。 これで、放射状のデッドゾーンが作られます。
次の例は、ピタゴラス定理を使用した基本的な放射状デッドゾーンを示しています。
float leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
float leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
// choose a deadzone -- readings inside this radius are ignored.
const float deadzoneRadius = 0.1;
const float deadzoneSquared = deadzoneRadius * deadzoneRadius;
// Pythagorean theorem -- for a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;
// accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) > deadzoneSquared)
{
// input accepted, process it
}
double leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
double leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
// choose a deadzone -- readings inside this radius are ignored.
const double deadzoneRadius = 0.1;
const double deadzoneSquared = deadzoneRadius * deadzoneRadius;
// Pythagorean theorem -- for a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
double oppositeSquared = leftStickY * leftStickY;
double adjacentSquared = leftStickX * leftStickX;
// accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) > deadzoneSquared)
{
// input accepted, process it
}
各サムスティックは、内側に押されたときにボタンとしても機能します。この入力の読み取りの詳細については、「ボタンの読み取り」を参照してください。
トリガーの読み取り
トリガーは、0.0 (完全に解放) から 1.0 (完全に押下) までの浮動小数点値として表されます。 左側のトリガーの値は、GamepadReading 構造体の LeftTrigger
プロパティから読み取られます。右側のトリガーの値は、RightTrigger
プロパティから読み取られます。
float leftTrigger = reading.LeftTrigger; // returns a value between 0.0 and 1.0
float rightTrigger = reading.RightTrigger; // returns a value between 0.0 and 1.0
double leftTrigger = reading.LeftTrigger; // returns a value between 0.0 and 1.0
double rightTrigger = reading.RightTrigger; // returns a value between 0.0 and 1.0
ボタンの読み取り
ゲームパッドの各ボタン (方向パッドの 4 方向、L ボタンと R ボタン、左右のサムスティックを押す動作、A、B、X、Y、ビュー、メニュー) は、デジタルの読み取り値によって、押されている (ダウン) か離されている (アップ) かを示します。 効率を高めるため、ボタンの読み取り値は個別のブール値としては表されません。代わりに、読み取り値はすべて、GamepadButtons 列挙型で表される単一のビットフィールドにパックされます。
ボタンの値は、GamepadReading 構造体の Buttons
プロパティから読み取られます。 このプロパティはビットフィールドであるため、ビット演算子マスクを使用して目的のボタンの値を分離します。 対応するビットが設定されているときはボタンが押されており (ダウン)、それ以外の場合はボタンが離されています (アップ)。
次の例では、A ボタンが押されているかどうかを判断します。
if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
// button A is pressed
}
if (GamepadButtons.A == (reading.Buttons & GamepadButtons.A))
{
// button A is pressed
}
次の例では、A ボタンが離されているかどうかを判断します。
if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
// button A is released
}
if (GamepadButtons.None == (reading.Buttons & GamepadButtons.A))
{
// button A is released
}
場合によっては、ボタンが押された状態から離された状態への移行またはその逆方向への移行のタイミング、複数のボタンが押されているか離されているかの状態、または一連のボタンが特定のパターンの状態になっているかどうか (一部が押されていて、一部が押されていない) を特定する必要があります。 これらの各状態を検出する方法について詳しくは、「ボタンの状態遷移の検出」および「ボタンの複雑な配置の検出」をご覧ください。
ゲームパッドの入力サンプルを実行する
GamepadUWP サンプル (github) では、ゲームパッドに接続してその状態を読み取る方法を示します。
振動とインパルス トリガーの概要
ゲームパッド内の振動モーターは、ユーザーに触覚的なフィードバックを提供するためのものです。 ゲームでは、この機能を使用して、より大きな没入感を生み出し、状態情報 (損傷の発生など) を伝えたり、重要なオブジェクトへの近接性を通知したり、他のクリエイティブな用途に役立ちます。
Xbox One ゲームパッドには、合計 4 つの独立した振動モーターが搭載されています。 2 つは、大型のモーターでゲームパッド本体に内蔵されています。左のモーターは、激しい高振幅の振動を生み出し、右のモーターは、より穏やかで繊細な振動を生み出します。 他の2つは、ユーザーのトリガー指に直接振動の鋭いバーストを提供する、各トリガーの内側に 1 つ、小さなモーターです。Xbox One ゲームパッドのこのユニークな機能は、そのトリガーがインパルス トリガーと呼ばれる理由です。 これらのモーターを一緒に調整することで、幅広い触覚感覚を生み出すことができます。
振動とインパルスの使用
ゲームパッドの振動は、Gamepad クラスの Vibration プロパティによって制御されます。 Vibration
は、GamepadVibration 構造体のインスタンスで、4 つの浮動小数点値で構成されます。各値は、それぞれのモーターの強さを表します。
Gamepad.Vibration
プロパティのメンバーは直接変更できますが、必要な値用に別の GamepadVibration
インスタンスを初期化して、それを Gamepad.Vibration
プロパティにコピーして実際のモーターの強さを一度に変更することをお勧めします。
次の例では、モーターの強度を一度に変更する方法を示します。
// get the first gamepad
Gamepad^ gamepad = Gamepad::Gamepads->GetAt(0);
// create an instance of GamepadVibration
GamepadVibration vibration;
// ... set vibration levels on vibration struct here
// copy the GamepadVibration struct to the gamepad
gamepad.Vibration = vibration;
// get the first gamepad
Gamepad gamepad = Gamepad.Gamepads[0];
// create an instance of GamepadVibration
GamepadVibration vibration = new GamepadVibration();
// ... set vibration levels on vibration struct here
// copy the GamepadVibration struct to the gamepad
gamepad.Vibration = vibration;
振動モーターの使用
左右の振動モーターは、0.0 (振動なし) から 1.0 (最も激しい振動) の間の浮動小数点値を取ります。 左モーターの強度は、GamepadVibration 構造体の LeftMotor
プロパティによって設定されます。右のモーターの強度は、RightMotor
プロパティによって設定されます。
次の例では、振動モーターの強度を設定し、ゲームパッドの振動をアクティブにします。
GamepadVibration vibration;
vibration.LeftMotor = 0.80; // sets the intensity of the left motor to 80%
vibration.RightMotor = 0.25; // sets the intensity of the right motor to 25%
gamepad.Vibration = vibration;
GamepadVibration vibration = new GamepadVibration();
vibration.LeftMotor = 0.80; // sets the intensity of the left motor to 80%
vibration.RightMotor = 0.25; // sets the intensity of the right motor to 25%
mainGamepad.Vibration = vibration;
これら 2 つのモーターは同じではないため、これらのプロパティを同じ値に設定しても、一方のモーターでは他のモーターと同じ振動が生成されないことに注意してください。 どの値でも、左のモーターは、右のモーターよりも低い周波数で強い振動を生み、右のモーターは同じ値に対して、より高い周波数で、より穏やかな振動を生みます。 最大値であっても、左モーターは右モーターの高周波数を生成できず、右モーターは左モーターの高い力を生成することもできません。 それでも、モーターはゲームパッド本体によって強固に接続されているため、モーターの特性が異なり、異なる強度で振動できる場合でも、プレイヤーは振動を完全に独立して経験することがありません。 この配置により、モーターが同一である場合よりも、より広く表現力の高い感覚を生み出すことができます。
インパルス トリガーの使用
各インパルストリガーモータは、0.0 (振動なし) から 1.0 (最も激しい振動) の間の浮動小数点値を取ります。 左トリガーモーターの強度は、GamepadVibration 構造体の LeftTrigger
プロパティによって設定されます。右トリガーの強度は、RightTrigger
プロパティによって設定されます。
次の例では、両方のインパルス トリガーの強度を設定し、アクティブにします。
GamepadVibration vibration;
vibration.LeftTrigger = 0.75; // sets the intensity of the left trigger to 75%
vibration.RightTrigger = 0.50; // sets the intensity of the right trigger to 50%
gamepad.Vibration = vibration;
GamepadVibration vibration = new GamepadVibration();
vibration.LeftTrigger = 0.75; // sets the intensity of the left trigger to 75%
vibration.RightTrigger = 0.50; // sets the intensity of the right trigger to 50%
mainGamepad.Vibration = vibration;
他のモーターとは異なり、トリガー内の 2 つの振動モーターは同じであるため、どちらのモーターでも同じ値に対して同じ振動が生成されます。 ただし、これらのモーターは何らかの方法で強固に接続されていないため、プレイヤーは独立した振動を経験します。 この配置により、完全に独立した感覚を両方のトリガーに同時に向けることができ、ゲームパッド本体のモーターよりも具体的な情報を伝えるのに役立ちます。
ゲームパッドの振動サンプルを実行する
GamepadVibrationUWP サンプル (github) では、ゲームパッドの振動モーターとインパルス トリガーを使用してさまざまな効果を生成する方法を示します。