飞行棒

本页介绍了使用 Windows.Gaming.Input.FlightStick 以及通用 Windows 平台(UWP)的相关 API 为 Xbox One 认证的飞行棒编程的基础知识。

通过阅读此页面,你将了解:

  • 如何获取已连接的飞行摇杆及其用户列表
  • 如何检测飞行摇杆是否已被添加或移除
  • 如何从一个或多个飞行摇杆读取输入
  • 飞行摇杆作为用户界面导航设备的表现如何

概述

飞行棒是游戏输入设备,用于重现飞机或宇宙飞船驾驶舱中发现的飞行棒的感觉。 它们是用于快速准确地控制飞行的完美输入设备。 在 Windows 10 或 Windows 11 以及 Xbox One 应用中,通过 Windows.Gaming.Input 命名空间支持飞行摇杆。

Xbox One 认证的飞行棒具有以下控件:

  • 一个可扭曲的模拟游戏杆,能够滚动、俯仰和偏航
  • 模拟油门
  • 两个触发按钮
  • 8 向数字帽子开关
  • 视图菜单 按钮

注释

视图菜单 按钮用于用于 UI 导航,而不是游戏命令,因此无法直接通过操纵杆按钮访问。

UI 导航

为了减轻为用户界面导航支持不同输入设备的负担,并鼓励游戏和设备之间的一致性,大多数 物理 输入设备同时充当称为 UI 导航控制器的单独 逻辑 输入设备。 UI 导航控制器提供了跨输入设备的 UI 导航命令的常见词汇。

作为 UI 导航控制器,飞行摇杆将导航命令所需的 映射到游戏杆和 视图菜单FirePrimaryFireSecondary 按钮。

导航命令 飞行摇杆输入
向上 游戏杆向上
向下 操纵杆下
左边 操纵杆向左
正确 摇杆向右
查看 视图 按钮
菜单 菜单 按钮
接受 FirePrimary 按钮
取消 FireSecondary 按钮

飞行摇杆不映射任何的可选导航指令集

检测和跟踪飞行棒

检测和跟踪飞行棒的原理与手柄完全相同,只是使用 FlightStick 类,而非 Gamepad 类。 有关详细信息,请参阅 游戏控制器和振动

阅读飞行棒

确定您感兴趣的飞行摇杆后,就可以开始从中获取输入。 但是,与您可能习惯的一些其他类型的输入不同,飞行摇杆不会通过触发事件来传达状态更改。 通过 轮询 他们,定期获取他们的当前状态数据。

轮询飞行摇杆

轮询在精确时间点捕获飞行棒的快照。 这种输入收集方法非常适合大多数游戏,因为它们的逻辑通常在确定性循环中运行,而不是事件驱动。 同时,从一次收集到的所有输入中解释游戏命令通常比从逐渐收集的多个单独输入中解释要更简单。

通过调用 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 的不同值。 为了减小此类变化的影响,您可以实现一个小的 死区,这是指在理想中心位置附近的一系列被忽略的数值范围。

实现死区的方法之一是确定游戏杆从中心移动的距离,并忽略离所选距离更近的读数。 你可以通过使用勾股定理大致计算距离,虽然这不是很精确,因为游戏杆的读数本质上是极坐标值而不是平面值。 这会生成径向死区。

以下示例演示了使用 Pythagorean 定理的基本径向死区:

// 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.
}

读取按钮和帽子开关

飞行棒的两个射击按钮提供一个数字读数,指示它是按下(向下)还是释放(向上)。 为了提高效率,按钮读取不会表示为单个布尔值,而是全部打包到由 FlightStickButtons 枚举表示的单个位字段。 此外,8方向帽形开关提供一个方向,该方向打包到一个由 GameControllerSwitchPosition 枚举表示的单个位字段中。

注释

飞行摇杆配备了用于 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).
}

有时,你可能想要确定按钮何时从按下状态转换为释放状态或从释放状态转换为按下状态,或者多个按钮何时同时被按下或释放,或者一组按钮是否以特定方式排列——一些被按下,一些没有。 有关如何检测这些条件的信息,请参阅 检测按钮转换检测复杂按钮排列

hat switch 值是从 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.
}

另请参阅