> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 从 XInput 移植到 GameInput

> 从 XInput 移植到 GameInput

<a id="introductionSection" />

在所有遗留 API 中，从 XInput 移植到 GameInput 最为轻松。这是因为 GameInput 深受 XInput 简单（且易于使用）的编程模型影响，因此许多 XInput API 与 GameInput 中的对应函数一一对应。

<a id="keyDifferencesSection" />

## 关键差异

以下各节讨论 XInput 与 GameInput 之间的关键差异。

<a id="cVsCppSection" />

### C 与 C++

XInput API 是一组扁平的 C 函数。而 GameInput 是 C++，并使用接口（与图形和音频 API 一样）。在实践中，这不会使使用 GameInput API 的代码更加复杂，不会影响性能，并且在你更熟悉 GameInput 的工作方式后会呈现一些优势。

请务必注意，尽管这些接口看起来像 COM，但它们并非 COM。使用这些接口只需对引用计数有基本了解。有关详细信息，请参阅 [GameInput 基础的“接口”一节](/build/core-features/common/input/overviews/input-fundamentals#interfacesSection)。

<a id="gettingInputSection" />

### 获取输入

在 XInput 中，大多数游戏会循环遍历用户索引，直到找到一个具有已连接设备的索引，然后从该设备读取状态。游戏通常会记住该用户索引，以便下次无需再次循环。例如，以下代码是游戏提示用户在其控制器上“按 A”的典型写法：

```c++ theme={null}
// This function looks for a gamepad that currently has the "A" button pressed.
void FindActiveGamepad()
{
    for (DWORD index = 0; index < XUSER_MAX_COUNT; index++)
    {
        XINPUT_STATE state;
        if (XInputGetState(index, &state) == ERROR_SUCCESS)
        {
            if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A)
            {
                // Found the user's gamepad at this index.
            }
        }
    }
}
```

在 GameInput 中，你先在不指定设备的情况下获取输入，然后可根据需要查询输入来自哪个设备。代码看起来类似，但省去显式的设备枚举可带来更简单的算法。

```c++ theme={null}
// This function looks for a gamepad that currently has the "A" button pressed.
void FindActiveGamepad(IGameInput * gameInput)
{
    // This checks for input from all gamepads simultaneously.
    IGameInputReading * reading;
    if (SUCCEEDED(gameInput->GetCurrentReading(GameInputKindGamepad, nullptr, &reading)))
    {
        GameInputGamepadState state;
        reading->GetGamepadState(&state);

        if (state.buttons & GameInputGamepadA)
        {
            // Found the user's gamepad.  At this point we can
            // get the device that generated this input, and then
            // pass that into future calls to the GetCurrentReading
            // method to receive input only from that gamepad.
        }

        reading->Release():
    }
}
```

代码不如 XInput 那么简单，但非常相似。当你更熟悉 GameInput API 后，会发现此模型提供了 XInput 中不存在的强大输入处理选项。

还值得注意的是，XInput 以 **BYTE** 类型返回扳机的模拟值，以 **SHORT** 类型返回拇指摇杆的模拟值。而在 GameInput API 中，这些模拟值以 **float** 值返回，扳机为 0 到 1，拇指摇杆为 -1 到 1。

<a id="rumbleFeedbackSection" />

### 震动反馈

在 XInput 中，游戏只需调用 `XInputSetState` 向设备发送震动指令。而在 GameInput 中，游戏需要先获取该设备的 [IGameInputDevice](/reference/input/gameinput/interfaces/igameinputdevice/igameinputdevice) 实例，然后调用其 [SetRumbleState](/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_setrumblestate) 方法。这两种方法的用法相似。这是一个需要在设备接口上调用函数，而不再仅将其作为设备标识的例子。

<a id="appFocusSection" />

### 应用程序焦点

在主机上，GameInput 仅在应用程序处于焦点时向其提供输入。否则，返回的状态包含中性或“静止”值，就像用户根本没有触摸设备一样。这样就无需处理焦点变化的额外输入代码（例如调用 `XInputEnable`）。

在 PC 上，输入默认发送到所有进程。未来，可通过 [SetFocusPolicy](/reference/input/gameinput/interfaces/igameinput/methods/igameinput_setfocuspolicy) 方法更改此行为。

<a id="xinputWrapperSection" />

## XInputOnGameInput 包装器

Microsoft Game Development Kit (GDK) 附带了名为 `XInputOnGameInput.h` 的头文件，其中包含基于 GameInput 实现的 XInput API。我们建议直接移植到 GameInput，尤其是当你需要键盘、鼠标或其他输入设备时。但是，XInputOnGameInput 包装器可用于在无需更改现有 XInput 代码的情况下启动初步移植工作。

要使用 XInputOnGameInput 包装器，只需将以下代码：

```c++ theme={null}
#include <XInput.h>
```

替换为以下代码：

```c++ theme={null}
#include <XInputOnGameInput.h>
using namespace XInputOnGameInput;
```

然后重新编译代码。

XInput 包装器代码的实现完全在头文件中，因此也可作为使用 GameInput API 的示例进行研究，并/或按需修改。

<a id="wrapperDifferencesSection" />

### XInput 与 XInputOnGameInput 的差异

一般来说，XInputOnGameInput 包装器是遗留 XInput API 的直接替代。但存在一些小差异：

* 为简单起见，包装器中只编写了对游戏手柄设备的支持。如果你需要支持其他设备，例如赛车方向盘或街机摇杆，请直接使用 GameInput，或在 XInputOnGameInput 代码中添加对这些设备的支持。

* 仅当游戏处于焦点时，包装器才会返回游戏手柄输入。当游戏不在焦点时，返回的任何游戏手柄状态都设置为中性或“静止”值，就像用户根本没有触摸游戏手柄一样。无论是否调用 `XInputEnable`，都会这样处理。

* `XUSER_MAX_COUNT` 的值已从 4 增加到 8。对大多数遗留 XInput 代码而言，这通常是透明的。但请仔细审查代码中 `XInputGetKeystroke` 函数的所有使用位置，以确保没有硬编码假定 `XINPUT_KEYSTROKE` 结构体的 `UserIndex` 成员的最大返回值为 4；否则可能出现缓冲区溢出。

* 添加了一些新函数（见下文），仅当你打算继续在生产代码中使用 XInput 包装器时才有意义。

<a id="productionWrapperUseSection" />

### 在生产代码中使用 XInputOnGameInput

XInputOnGameInput 包装器编写为高性能且无锁，继承了 GameInput API 的所有性能优化，因此适合在生产代码中使用。它还继承了 GameInput 更广的设备支持（例如流行的 HID 游戏手柄），并向 API 添加以下新函数：

* **`XInputSetStateEx`** 与 `XInputSetState` 类似，但增加了对扳机马达的支持。

* **`XInputGetStateWithToken`** 与 `XInputGetState` 类似，但允许调用方提供 D3DX 帧管线令牌，以便将特定输入读取与图形帧关联，稍后在 PIX 中进行分析。

  > \[!NOTE]
  > 在五月预览版中，`XInputGetStateWithToken` 目前的行为与 `XInputGetState` 完全相同，因为底层 GameInput 代码尚未完全实现。

* **`XInputGetDeviceId`** 返回给定用户索引处设备的 `APP_LOCAL_DEVICE_ID`。将此 ID 传递给 [IGameInput](/reference/input/gameinput/interfaces/igameinput/igameinput) 上的 [FindDeviceFromId](/reference/input/gameinput/interfaces/igameinput/methods/igameinput_finddevicefromid) 方法，可返回该用户索引对应的 [IGameInputDevice](/reference/input/gameinput/interfaces/igameinputdevice/igameinputdevice)。然后可用于访问 GameInput API 中未通过 XInput 包装器公开的其他功能。

<a id="optimizingSection" />

#### 优化包装器代码

默认情况下，XInputOnGameInput 包装器被配置为与遗留 XInput API 直接兼容。不需要 100% 兼容行为的游戏可通过定义以下任一预处理器宏来微调包装器的行为和性能：

##### XINPUT\_ON\_GAMEINPUT\_EXPLICIT\_INITIALIZATION

默认情况下，XInput 包装器在首次调用任何包装器函数时会自动懒初始化底层 GameInput API。这可确保与现有 XInput 代码的直接兼容，但存在一些小缺点：

1. 首次调用 XInput 包装器函数的执行时间会比平时更长。

2. 每次调用 XInput 包装器函数时，都必须检查是否已进行懒初始化。这只是对一个全局变量的简单检查，分支预测器应能缓解成本，但仍属额外开销。

3. 尽管理论上不会失败，但无法得知底层 GameInput API 的懒初始化是否成功。

4. 底层 [IGameInput](/reference/input/gameinput/interfaces/igameinput/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 包装器的代码/数据大小。

<a id="seeAlsoSection" />

## 参考 API 文档

* [GameInput（API 目录）](/reference/input/gameinput/gameinput_members)
* [XInputOnGameInput（API 目录）](/reference/input/xinputongameinput/xinputongameinput_members)

## 另请参阅

[GameInput 概述](/build/core-features/common/input/overviews/input-overview)

[GameInput API 参考](/reference/input/gc-reference-input-toc)

[Microsoft Game Development Kit](/services/playfab/sdks/platforms/gdk)


## Related topics

- [XInputOnGameInput](/zh-CN/reference/input/xinputongameinput/xinputongameinput_members.md)
- [将现有输入代码移植到 GameInput](/zh-CN/build/core-features/common/input/porting/index.md)
- [XINPUT_KEYSTROKE](/zh-CN/reference/input/xinputongameinput/structs/xinput_keystroke.md)
- [XINPUT_STATE](/zh-CN/reference/input/xinputongameinput/structs/xinput_state.md)
- [XINPUT_VIBRATION](/zh-CN/reference/input/xinputongameinput/structs/xinput_vibration.md)
