フライト スティック
このページでは、Windows.Gaming.Input.FlightStick とユニバーサル Windows プラットフォーム (UWP) 用の関連 API を使った、Xbox One 認定フライト スティックを対象にしたプログラミングの基礎について説明します。
ここでは、次の項目について紹介します。
- 接続されているフライト スティックとそのユーザーの一覧を収集する方法
- フライト スティックが追加または削除されたことを検出する方法
- 1 つ以上のフライト スティックの入力を読み取る方法
- フライト スティックを UI ナビゲーション デバイスとして使用する方法
フライト スティックは、航空機や宇宙船のコックピットにあるフライト スティックの操作感を再現することを重視したゲーム用の入力デバイスです。 フライトを迅速かつ正確に制御するのに最適な入力デバイスです。 フライト スティックは、Windows 10 または Windows 11 および Xbox One アプリで、Windows.Gaming.Input 名前空間を介してサポートされます。
Xbox One 認定のフライト スティックには、次のコントロールが装備されています。
- ロール、ヨー、ピッチに対応したツイスト可能なアナログ ジョイスティック
- アナログ スロットル
- 2 つのファイア ボタン
- 8 方向デジタル ハット スイッチ
- View ボタンと Menu ボタン
注意
View ボタンと Menu ボタンは、ゲームプレイ コマンドではなく、UI ナビゲーションをサポートするために使用されます。したがって、ジョイスティック ボタンとは異なり、簡単にはアクセスできません。
ユーザー インターフェイスの操作に異なる入力デバイスをサポートする負担を軽くし、ゲームとデバイス間の整合性を高めるため、ほとんどの物理入力デバイスは、UI ナビゲーション コントローラーと呼ばれる個別の論理入力デバイスとして同時に機能します。 UI ナビゲーション コントローラーは、各種入力デバイスに共通の UI ナビゲーション コマンドのボキャブラリを提供します。
フライト スティックは、UI ナビゲーション コント ローラーとして、ナビゲーション コマンドの必須セットを、ジョイスティックと View、Menu、FirePrimary、FireSecondary ボタンにマップします。
ナビゲーション コマンド | フライト スティックの入力 |
---|---|
上へ | ジョイスティック上 |
[下へ] | ジョイスティック下 |
Left | ジョイスティック左 |
Right | ジョイスティック右 |
表示 | View ボタン |
メニュー | Menu ボタン |
承諾 | FirePrimary ボタン |
キャンセル | FireSecondary ボタン |
フライト スティックは、ナビゲーション コマンドのオプション セットはマップしません。
フライト スティックの検出と追跡の方法はゲームパッドの場合とまったく同じですが、Gamepad クラスの代わりに FlightStick クラスを使用する点だけが異なります。 詳細については、「ゲームパッドと振動」を参照してください。
目的のフライト スティックを特定したら、入力を収集する準備は完了です。 ただし、なじみのある一部の他の種類の入力とは違い、フライト スティックはイベントを発生することによって状態の変化を伝えるわけではありません。 代わりに、イベントをポーリングすることで現在の状態を定期的に読み取ります。
ポーリングでは、明確な時点におけるフライト スティックのスナップショットをキャプチャします。 入力を収集するこのアプローチはほとんどのゲームに最適です。ゲームのロジックはイベント駆動型ではなく、確定的なループの中で実行されることが一般的なためです。 また通常は、徐々に集めた多数の単一の入力によるコマンドを解釈するより、一度に集めた入力によるゲーム コマンドを解釈する方が簡単になります。
フライト スティックをポーリングするには、FlightStick.GetCurrentReading を呼び出します。 この関数は、フライト スティックの状態を含む FlightStickReading を返します。
次の例では、フライト スティックをポーリングして現在の状態を確認します。
auto flightStick = myFlightSticks->GetAt(0);
FlightStickReading reading = flightStick->GetCurrentReading();
読み取りデータには、フライト スティックの状態だけでなく、正確にいつ状態が取得されたかを示すタイムスタンプも含まれます。 このタイムスタンプは、以前の読み取りのタイミングや、ゲームのシミュレーションのタイミングと関連付けに便利です。
ジョイスティックは、X、Y、Z 軸で -1.0 ~ +1.0 のアナログの読み取り値 (それぞれロール、ピッチ、ヨー) を提供します。 ロールの場合、-1.0 の値はジョイスティックを最も左に移動した位置に対応し、1.0 の値はジョイスティックを最も右に移動した位置に対応します。 ピッチの場合、-1.0 の値はジョイスティックを最も下に移動した位置に対応し、1.0 の値はジョイスティックを最も上に移動した位置に対応します。 ヨーの場合、-1.0 の値は反時計回りに最もひねった位置に対応し、1.0 の値は時計回り最もひねった位置に対応します。
すべての軸において、ジョイスティックが中央の位置にあるときには値がほぼ 0.0 になりますが、それ以降の読み取り値の間でも、正確な値が変化するのは正常です。 このバリエーションを軽減するための方法は、このセクションで後ほど説明します。
ジョイスティックのロールの値は FlightStickReading.Roll プロパティから読み取られ、ピッチの値は FlightStickReading.Pitch プロパティから読み取られ、ヨーの値は FlightStickReading.Yaw プロパティから読み取られます。
// Each variable will contain a value between -1.0 and 1.0.
float roll = reading.Roll;
float pitch = reading.Pitch;
float yaw = reading.Yaw;
ジョイスティックの値を読み取るとき、中央の位置で待機中のジョイスティックの値は、一定してニュートラルの 0.0 にはなりません。ジョイスティックを動かし、中央の位置に戻るたびに、0.0 に近い値が生成されます。 このばらつきを少なくするために、小さなデッドゾーンを実装します。デッドゾーン+は、理想の中央の位置付近の、無視される範囲の値です。
デッドゾーンを実装する方法の 1 つは、ジョイスティックが中央から移動された距離を特定し、読み取り値が指定した距離以下の場合は無視することです。 この距離は、ピタゴラスの定理を使って、概算できます (ジョイスティックの読み取り値は、平面値ではなく、本質的に極値であるため正確な計算にはなりません)。 これで、放射状のデッドゾーンが作られます。
次の例は、ピタゴラスの定理を使った基本的な放射状のデッドゾーンを示しています。
// Choose a deadzone. Readings inside this radius are ignored.
const float deadzoneRadius = 0.1f;
const float deadzoneSquared = deadzoneRadius * deadzoneRadius;
// Pythagorean theorem: For a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
float oppositeSquared = pitch * pitch;
float adjacentSquared = roll * roll;
// Accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) < deadzoneSquared)
{
// Input accepted, process it.
}
フライト スティックの 2 つのファイア ボタンはそれぞれ、ボタンが押されている (ダウン) か、離されている (アップ) かを示すデジタルの読み取り値を提供します。 効率を高めるため、ボタンの読み取り値は個別のブール値としては表されません。代わりに、読み取り値はすべて、FlightStickButtons 列挙型で表される単一のビットフィールドにパックされます。 さらに、8 方向ハット スイッチは、GameControllerSwitchPosition 列挙型で表される単一のビットフィールドにパックされる方向を提供します。
注意
フライト スティックには、View ボタンや Menu ボタンなど、UI ナビゲーションに使用するボタンも搭載されています。 これらのボタンは FlightStickButtons
列挙型には含まれず、UI ナビゲーション デバイスとしてフライト スティックを利用する場合にのみ読み取られます。 詳しくは、「UI ナビゲーション コントローラー」をご覧ください。
ボタンの値は、FlightStickReading.Buttons プロパティから読み取られます。 このプロパティはビットフィールドであるため、ビット演算子マスクを使用して目的のボタンの値を分離します。 対応するビットが設定されているときはボタンが押されており (ダウン)、それ以外の場合はボタンが離されています (アップ)。
次の例では、FirePrimary ボタンが押されているかどうかを判別します。
if (FlightStickButtons::FirePrimary == (reading.Buttons & FlightStickButtons::FirePrimary))
{
// FirePrimary is pressed.
}
次の例では、FirePrimary ボタンが離されているかどうかを判別します。
if (FlightStickButtons::None == (reading.Buttons & FlightStickButtons::FirePrimary))
{
// FirePrimary is released (not pressed).
}
場合によっては、ボタンが押された状態から離された状態への移行またはその逆方向への移行のタイミング、複数のボタンが押されているか離されているかの状態、または一連のボタンが特定のパターンの状態になっているかどうか (一部が押されていて、一部が押されていない) を特定する必要があります。 これらの各状態を検出する方法について詳しくは、「ボタンの状態遷移の検出」および「ボタンの複雑な配置の検出」をご覧ください。
ハット スイッチの値は、FlightStickReading.HatSwitch プロパティから読み取られます。 このプロパティもビットフィールドであるため、ここでもビット単位のマスクを使用してハット スイッチの位置を特定します。
次の例では、ハット スイッチが上の位置であるかどうかを特定します。
if (GameControllerSwitchPosition::Up == (reading.HatSwitch & GameControllerSwitchPosition::Up))
{
// The hat switch is in the up position.
}
次の例では、ハット スイッチが中央の位置であるかどうかを特定します。
if (GameControllerSwitchPosition::Center == (reading.HatSwitch & GameControllerSwitchPosition::Center))
{
// The hat switch is in the center position.
}