关键差异
以下各节讨论 XInput 与 GameInput 之间的关键差异。C 与 C++
XInput API 是一组扁平的 C 函数。而 GameInput 是 C++,并使用接口(与图形和音频 API 一样)。在实践中,这不会使使用 GameInput API 的代码更加复杂,不会影响性能,并且在你更熟悉 GameInput 的工作方式后会呈现一些优势。 请务必注意,尽管这些接口看起来像 COM,但它们并非 COM。使用这些接口只需对引用计数有基本了解。有关详细信息,请参阅 GameInput 基础的“接口”一节。获取输入
在 XInput 中,大多数游戏会循环遍历用户索引,直到找到一个具有已连接设备的索引,然后从该设备读取状态。游戏通常会记住该用户索引,以便下次无需再次循环。例如,以下代码是游戏提示用户在其控制器上“按 A”的典型写法:震动反馈
在 XInput 中,游戏只需调用XInputSetState 向设备发送震动指令。而在 GameInput 中,游戏需要先获取该设备的 IGameInputDevice 实例,然后调用其 SetRumbleState 方法。这两种方法的用法相似。这是一个需要在设备接口上调用函数,而不再仅将其作为设备标识的例子。
应用程序焦点
在主机上,GameInput 仅在应用程序处于焦点时向其提供输入。否则,返回的状态包含中性或“静止”值,就像用户根本没有触摸设备一样。这样就无需处理焦点变化的额外输入代码(例如调用XInputEnable)。
在 PC 上,输入默认发送到所有进程。未来,可通过 SetFocusPolicy 方法更改此行为。
XInputOnGameInput 包装器
Microsoft Game Development Kit (GDK) 附带了名为XInputOnGameInput.h 的头文件,其中包含基于 GameInput 实现的 XInput API。我们建议直接移植到 GameInput,尤其是当你需要键盘、鼠标或其他输入设备时。但是,XInputOnGameInput 包装器可用于在无需更改现有 XInput 代码的情况下启动初步移植工作。
要使用 XInputOnGameInput 包装器,只需将以下代码:
XInput 与 XInputOnGameInput 的差异
一般来说,XInputOnGameInput 包装器是遗留 XInput API 的直接替代。但存在一些小差异:- 为简单起见,包装器中只编写了对游戏手柄设备的支持。如果你需要支持其他设备,例如赛车方向盘或街机摇杆,请直接使用 GameInput,或在 XInputOnGameInput 代码中添加对这些设备的支持。
-
仅当游戏处于焦点时,包装器才会返回游戏手柄输入。当游戏不在焦点时,返回的任何游戏手柄状态都设置为中性或“静止”值,就像用户根本没有触摸游戏手柄一样。无论是否调用
XInputEnable,都会这样处理。 -
XUSER_MAX_COUNT的值已从 4 增加到 8。对大多数遗留 XInput 代码而言,这通常是透明的。但请仔细审查代码中XInputGetKeystroke函数的所有使用位置,以确保没有硬编码假定XINPUT_KEYSTROKE结构体的UserIndex成员的最大返回值为 4;否则可能出现缓冲区溢出。 - 添加了一些新函数(见下文),仅当你打算继续在生产代码中使用 XInput 包装器时才有意义。
在生产代码中使用 XInputOnGameInput
XInputOnGameInput 包装器编写为高性能且无锁,继承了 GameInput API 的所有性能优化,因此适合在生产代码中使用。它还继承了 GameInput 更广的设备支持(例如流行的 HID 游戏手柄),并向 API 添加以下新函数:-
XInputSetStateEx与XInputSetState类似,但增加了对扳机马达的支持。 -
XInputGetStateWithToken与XInputGetState类似,但允许调用方提供 D3DX 帧管线令牌,以便将特定输入读取与图形帧关联,稍后在 PIX 中进行分析。[!NOTE] 在五月预览版中,
XInputGetStateWithToken目前的行为与XInputGetState完全相同,因为底层 GameInput 代码尚未完全实现。 -
XInputGetDeviceId返回给定用户索引处设备的APP_LOCAL_DEVICE_ID。将此 ID 传递给 IGameInput 上的 FindDeviceFromId 方法,可返回该用户索引对应的 IGameInputDevice。然后可用于访问 GameInput API 中未通过 XInput 包装器公开的其他功能。
优化包装器代码
默认情况下,XInputOnGameInput 包装器被配置为与遗留 XInput API 直接兼容。不需要 100% 兼容行为的游戏可通过定义以下任一预处理器宏来微调包装器的行为和性能:XINPUT_ON_GAMEINPUT_EXPLICIT_INITIALIZATION
默认情况下,XInput 包装器在首次调用任何包装器函数时会自动懒初始化底层 GameInput API。这可确保与现有 XInput 代码的直接兼容,但存在一些小缺点:- 首次调用 XInput 包装器函数的执行时间会比平时更长。
- 每次调用 XInput 包装器函数时,都必须检查是否已进行懒初始化。这只是对一个全局变量的简单检查,分支预测器应能缓解成本,但仍属额外开销。
- 尽管理论上不会失败,但无法得知底层 GameInput API 的懒初始化是否成功。
- 底层 IGameInput 实例直到 XInput 包装器的全局变量被清理(无论是由于模块卸载还是进程终止)时才会被释放。
XINPUT_ON_GAMEINPUT_EXPLICIT_INITIALIZATION 宏手动控制包装器的初始化和关闭。这会新增两个函数 XInputOnGameInputInitialize 和 XInputOnGameInputUninitialize,可用于精确控制初始化和关闭的时机。
XINPUT_ON_GAMEINPUT_NO_XINPUTENABLE
实现XInputEnable 函数所需的代码会为每次调用 XInputGetState、XInputGetStateWithToken、XInputSetState、XInputSetStateEx 和 XInputGetKeystroke 函数增加额外开销。如果你的代码不调用 XInputEnable,或者可轻松从代码中移除,定义 XINPUT_ON_GAMEINPUT_NO_XINPUTENABLE 宏将移除对 XInputEnable 的支持及其带来的相关开销。底层 GameInput 代码本就会在焦点变化时自动执行 XInputEnable 的功能,因此大多数游戏都会希望定义此宏。
XINPUT_ON_GAMEINPUT_NO_XINPUTGETKEYSTROKE
实现XInputGetKeystroke 函数所需的代码会为包装器的实现新增少量函数和变量。它不会为任何其他 XInput API 函数增加开销,但如果你的代码不调用 XInputGetKeystroke,可以定义 XINPUT_ON_GAMEINPUT_NO_XINPUTGETKEYSTROKE 宏,从而略微减小 XInput 包装器的代码/数据大小。
