赛车方向盘和力回馈

此页介绍使用 Windows.Gaming.Input.RacingWheel 和通用 Windows 平台 (UWP) 的相关 API 进行 Xbox One 赛车方向盘编程的基本知识。

在本页中,你将了解如下内容:

  • 如何收集连接的赛车方向盘及其用户的列表
  • 如何检测是否已添加或已移除赛车方向盘
  • 如何从一个或多个赛车方向盘读取输入
  • 如何发送力回馈命令
  • 赛车方向盘作为 UI 导航设备的行为方式

赛车方向盘概述

赛车方向盘是类似于真实赛车驾驶舱感觉的输入设备。 赛车方向盘是街机式和模拟式赛车(主要包括汽车或卡车)游戏的完美输入设备。 赛车方向盘在 Windows 10 或 Windows 11 和 Xbox One UWP 应用中受 Windows.Gaming.Input 命名空间支持。

赛车方向盘以各种价格点提供,随着价格点的提高,通常会具有更多更好的输入和力回馈功能。 所有赛车方向盘均配备了模拟方向盘、模拟油门和刹车控制装置以及一些方向盘按钮。 某些赛车方向盘还配备了模拟离合器和手刹控件、模式换挡器和力回馈功能。 并非所有赛车方向盘都配备有相同的功能组,并且对某些功能的支持也可能有所不同。例如,方向盘可能支持不同范围的旋转,档位可能支持不同的档数。

设备功能

不同的赛车方向盘提供不同的可选设备功能组,并对这些功能提供不同级别的支持,在 Windows.Gaming.Input API 支持的设备当中,单一类型的输入设备之间的这种差异级别是唯一的。 此外,你将遇到的大多数设备至少支持一些可选功能或其他变体。 因此,必须单独确定每个连接的赛车方向盘的功能,并支持对游戏有意义的功能的所有变体。

有关详细信息,请参阅确定赛车方向盘功能

力回馈

某些赛车方向盘提供真实的力回馈,即它们可以将实际用力施加于控制轴(如方向盘),而不只是简单的震动。 游戏利用这种能力创造出更大的沉浸感(模拟车祸损坏、“道路感”),并增加正常驾驶的挑战。

有关详细信息,请参阅力回馈概述

UI 导航

为了减轻支持不同输入设备进行用户界面导航造成的负担,并促进游戏和设备之间的一致性,大多数物理输入设备会同时充当独立的逻辑输入设备(称为 UI 导航控制器)。 UI 导航控制器可跨各种输入设备提供通用的 UI 导航命令词汇。

由于对模拟控件的独有关注以及不同赛车方向盘之间的差异,它们通常配备了数字方向键、视图、菜单、A、B、X 和 Y 按钮,类似于游戏板上那样;这些按钮不支持游戏命令,并且不能作为赛车方向盘按钮轻松使用。

赛车方向盘作为 UI 导航控制器,将导航命令的必需组映射为左操纵杆、方向键、视图、菜单、A 和 B 按钮

导航命令 赛车轮输入
向上 方向键向上
向下 方向键向下
Left 方向键向左
Right 方向键向右
视图 视图按钮
菜单 “菜单”按钮
Accept A 按钮
Cancel B 按钮

此外,某些赛车方向盘可能会将导航命令的某些可选组映射到它们支持的其他输入,但命令映射可能因设备而异。 也考虑支持这些命令,但请确保这些命令对于导航游戏界面来说不是必需的。

导航命令 赛车轮输入
Page Up 不定
Page Down 不定
向左翻页键 不定
向右翻页键 不定
向上滚动 不定
向下滚动 不定
向左滚动键 不定
向右滚动键 不定
上下文 1 X 按钮(一般情况)
上下文 2 Y 按钮(一般情况)
上下文 3 不定
上下文 4 不定

检测与跟踪赛车方向盘

检测与跟踪赛车方向盘的工作原理与检测与跟踪游戏板完全相同,只不过是使用 RacingWheel 类而不是 Gamepad 类。 有关详细信息,请参阅游戏板和振动

读取赛车方向盘

确定感兴趣的赛车方向盘后,即可从中收集输入。 但与你可能习惯的一些其他类型的输入不同,赛车方向盘不会通过引发事件来传达状态更改。 您可以通过轮询来定期读取它们的当前状态。

轮询赛车方向盘

轮询会捕获赛车方向盘在精确时间点的快照。 这种收集输入的方法非常适合大多数游戏,因为它们的逻辑通常运行在一个确定的循环中,而不是由事件驱动;通常情况下,根据一次收集的输入解释游戏命令也会比根据一段时间内收集的许多单个输入进行解释更加简单。

可通过调用 GetCurrentReading 来轮询赛车方向盘;此函数会返回 RacingWheelReading,其中包含赛车方向盘状态。

以下是轮询赛车方向盘当前状态的示例。

auto racingwheel = myRacingWheels[0];

RacingWheelReading reading = racingwheel->GetCurrentReading();

除了赛车方向盘状态之外,每个读数还包括一个精确指示检索状态时间的时间戳。 该时间戳对于关联之前读数的时间或者游戏模拟的时间非常有用。

确定赛车方向盘功能

许多赛车方向盘控件都是可选的,或者即使在必选控件中也会支持不同的变体,因此,在处理赛车方向盘的每个读取中收集的输入之前,必须单独确定每个赛车方向盘的功能。

可选控件是手刹、离合器和模式换挡器;可以通过分别读取赛车方向盘的 HasHandbrakeHasClutchHasPatternShifter 属性来确定连接的赛车方向盘是否支持这些控件。 如果属性的值为 true,则该控件受支持;否则不受支持。

if (racingwheel->HasHandbrake)
{
    // the handbrake is supported
}

if (racingwheel->HasClutch)
{
    // the clutch is supported
}

if (racingwheel->HasPatternShifter)
{
    // the pattern shifter is supported
}

此外,可能会有所不同的控件是方向盘和模式换挡器。 方向盘可能因实际方向盘可以支持的物理旋转程度而异,而模式换挡器可能因其支持的不同前进档的数量而异。 可以通过读取赛车方向盘的 MaxWheelAngle 属性来确定实际方向盘支持的最大旋转角度;其值为顺时针方向(正)支持的最大物理角度(度数),或逆时针方向支持的最大物理角度(负度数)。 你可以读取赛车方向盘的 MaxPatternShifterGear 属性来确定档位支持的最大前进档,其值是档位支持的最大前进档(含该档),也就是说,如果值为 4,则档位支持倒档、空挡、一档、二档、三档和四档。

auto maxWheelDegrees = racingwheel->MaxWheelAngle;
auto maxShifterGears = racingwheel->MaxPatternShifterGear;

最后,某些赛车方向盘支持方向盘力回馈。 可以通过读取赛车方向盘的 WheelMotor 属性来确定连接的赛车方向盘是否支持力回馈。 如果 WheelMotor 不为 null,则支持力回馈,否则不支持力回馈。

if (racingwheel->WheelMotor != nullptr)
{
    // force feedback is supported
}

有关如何使用受支持的赛车方向盘的力回馈功能的信息,请参阅力回馈概述

读取按钮

每个赛车方向盘按钮(方向键的四个方向、减档和加档按钮,以及 16 个其他按钮)提供一个数字读数,指出其是按下(向下)还是释放(向上)。 为了提高效率,按钮读数不以单独的布尔值表示,而是全部打包到一个由 RacingWheelButtons 枚举表示的单独位域中。

注意

赛车方向盘配备了用于 UI 导航的其他按钮,例如“视图”和“菜单”按钮。 这些按钮不是 RacingWheelButtons 枚举的一部分,只能作为 UI 导航设备通过访问赛车方向盘进行读取。 有关详细信息,请参阅 UI 导航设备

RacingWheelReading 结构的 Buttons 属性中读取按钮值。 因为此属性是位域,所以使用按位掩码隔离你感兴趣的按钮值。 设置相应位时按钮为按下(向下);否则,按钮为释放(向上)。

以下示例确定是否按下了“下一档”按钮。

if (RacingWheelButtons::NextGear == (reading.Buttons & RacingWheelButtons::NextGear))
{
    // Next Gear is pressed
}

以下示例确定是否释放了“下一档”按钮。

if (RacingWheelButtons::None == (reading.Buttons & RacingWheelButtons::NextGear))
{
    // Next Gear is released (not pressed)
}

有时你可能需要确定:何时将按钮从按下转换为释放或从释放转换为按下,是按下还是释放多个按钮,或者是否按特定方式安排一组按钮(按下一些按钮,释放一些按钮)。 有关如何检测这些条件的信息,请参阅检测按钮切换检测复杂按钮排列

读取方向盘

方向盘是提供模拟读数的必选控件,读数范围介于 -1.0 和 +1.0 之间。 值 -1.0 对应于方向盘位置的最左侧;值 +1.0 对应于最右侧位置。 从 RacingWheelReading 结构的 Wheel 属性中读取方向盘的值。

float wheel = reading.Wheel;  // returns a value between -1.0 and +1.0.

虽然方向盘读数对应的实际方向盘中的物理旋转角度不尽相同,具体取决于物理赛车方向盘支持的旋转范围,但你通常不希望缩放方向盘读数;支持更大旋转角度的方向盘只会提供更高的精度。

读取油门和刹车

油门和刹车是必选控件,每个控件可提供介于 0.0(完全释放)和 1.0(完全按下)之间的模拟读数,这些读数表示为浮点值。 油门控件的值通过 RacingWheelReading 结构的 Throttle 属性读取,而刹车控件的值通过 Brake 属性来读取。

float throttle = reading.Throttle;  // returns a value between 0.0 and 1.0
float brake    = reading.Brake;     // returns a value between 0.0 and 1.0

读取手刹和离合器

手刹和离合器是可选控件,每个控件可提供介于 0.0(完全释放)和 1.0(完全接合)之间的模拟读数,这些读数表示为浮点值。 手刹控件的值通过 RacingWheelReading 结构的 Handbrake 属性读取,而离合器控件的值通过 Clutch 属性来读取。

float handbrake = 0.0;
float clutch = 0.0;

if(racingwheel->HasHandbrake)
{
    handbrake = reading.Handbrake;  // returns a value between 0.0 and 1.0
}

if(racingwheel->HasClutch)
{
    clutch = reading.Clutch;        // returns a value between 0.0 and 1.0
}

读取模式换挡器

模式换挡器是一个可选控件,它提供介于 -1 和 MaxPatternShifterGear 之间的数字读取,这些读数表示为带符号整数值。 值 -1 或 0 分别对应于倒挡和空挡;正值越大对应的前进挡位越高,最高为 MaxPatternShifterGear(含)。 档位值通过 RacingWheelReading 结构的 PatternShifterGear 属性读取。

if (racingwheel->HasPatternShifter)
{
    gear = reading.PatternShifterGear;
}

注意

模式换档器(如果支持)会与所需的“上一档”和“下一档”按钮同时存在,后两个按钮也会影响玩家汽车的当前档位。 统一这些输入(如果同时存在)的简单策略是:当玩家为汽车选择自动变速时,忽略模式换档器(和离合器);当玩家为汽车选择手动变速时,忽略“上一档”和“下一档”按钮,条件是玩家的赛车方向盘配备了模式换档器控件。 如果以上不适合你的游戏,可以实施不同的统一策略。

运行 InputInterfacing 示例

GitHub 上的 InputInterfacingUWP 示例应用演示如何同时使用赛车方向盘和不同类型的输入设备,以及这些输入设备如何作为 UI 导航控制器运行。

力回馈概述

许多赛车方向盘都具有力回馈功能,可提供更具沉浸感和充满挑战性的驾驶体验。 支持力回馈的赛车方向盘通常配备单发动机,该发动机会沿单轴(即方向盘旋转轴)向方向盘施加力。 力回馈在 Windows 10 或 Windows 11 和 Xbox One UWP 应用中受 Windows.Gaming.Input.ForceFeedback 命名空间支持。

注意

力回馈 API 能够支持多个力轴,但目前没有赛车方向盘支持方向盘旋转以外的任何回馈轴。

使用力回馈

以下部分介绍对赛车方向盘的力回馈效果进行编程的基础知识。 回馈通过效果进行应用,这些效果会首先加载到力回馈设备上,然后可通过类似于声音效果的方式启动、暂停、继续和停止;但是,必须首先确定赛车方向盘的回馈功能。

确定力回馈功能

可以通过读取赛车方向盘的 WheelMotor 属性来确定连接的赛车方向盘是否支持力回馈。 如果 WheelMotor 为 null,则不支持力回馈,否则,便支持力回馈。并且,你可以继续确定电机的特定反馈功能,例如它可以影响的轴。

if (racingwheel->WheelMotor != nullptr)
{
    auto axes = racingwheel->WheelMotor->SupportedAxes;

    if(ForceFeedbackEffectAxes::X == (axes & ForceFeedbackEffectAxes::X))
    {
        // Force can be applied through the X axis
    }

    if(ForceFeedbackEffectAxes::Y == (axes & ForceFeedbackEffectAxes::Y))
    {
        // Force can be applied through the Y axis
    }

    if(ForceFeedbackEffectAxes::Z == (axes & ForceFeedbackEffectAxes::Z))
    {
        // Force can be applied through the Z axis
    }
}

加载力回馈效果

力回馈效果将加载到回馈设备上,并根据游戏的命令自主进行“播放”。 提供了一些基本效果,可以通过实现 IForceFeedbackEffect 接口的类创建自定义效果。

效果类 效果说明
ConditionForceEffect 对设备内的电流传感器施加可变力的效果。
ConstantForceEffect 沿向量施加恒定力的效果。
PeriodicForceEffect 沿向量施加由波形定义的可变力的效果。
RampForceEffect 沿向量施加线性增大/减小力的效果。
using FFLoadEffectResult = ForceFeedback::ForceFeedbackLoadEffectResult;

auto effect = ref new Windows.Gaming::Input::ForceFeedback::ConstantForceEffect();
auto time = TimeSpan(10000);

effect->SetParameters(Windows::Foundation::Numerics::float3(1.0f, 0.0f, 0.0f), time);

// Here, we assume 'racingwheel' is valid and supports force feedback

IAsyncOperation<FFLoadEffectResult>^ request
    = racingwheel->WheelMotor->LoadEffectAsync(effect);

auto loadEffectTask = Concurrency::create_task(request);

loadEffectTask.then([this](FFLoadEffectResult result)
{
    if (FFLoadEffectResult::Succeeded == result)
    {
        // effect successfully loaded
    }
    else
    {
        // effect failed to load
    }
}).wait();

使用力回馈效果

加载后,所有效果都可以通过调用赛车方向盘 WheelMotor 属性上的函数,或通过单独调用回馈效果本身的函数进行同步启动、暂停、继续和停止。 通常,在游戏开始之前,你应该将希望使用的所有效果加载到回馈设备上,然后使用其各自的 SetParameters 函数在游戏进行期间更新效果。

if (ForceFeedbackEffectState::Running == effect->State)
{
    effect->Stop();
}
else
{
    effect->Start();
}

最后,可以根据需要在特定的赛车方向盘上异步启用、禁用或重置整个力回馈系统。

另请参阅