> ## 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.

# PlayFab Game Saves 的帐户关联策略

> 比较 PlayFab Game Saves 的跨平台进度策略，包括主关联标识以及必需与可选的平台帐户关联。

## 主关联标识

任何跨平台进度策略的首要要求是确定哪个玩家标识将跨越各个游戏平台。玩家需要能够在提供该游戏的任何设备上使用此标识登录。对于第一方游戏，这通常是 Microsoft 帐户 (MSA)/XBOX 帐户。对于许多第三方而言，这可能是通过 OpenID Connect 实现公开的发行商标识。此玩家标识需要满足以下两个要求：

1. 在玩家可能玩该游戏的每个平台上均可供玩家使用
2. 受现有 PlayFab Login 调用支持，例如 `LoginWithXbox` 或 `LoginWithOpenIdConnect`

除这两个要求外，如果该标识支持某种基于设备的缓存，并在后续启动游戏时支持静默登录，将会很有帮助。这不是硬性要求，但可以降低工作流的复杂性和/或减少玩家阻力。

## 平台原生标识

平台原生标识是玩家运行游戏所在平台提供的帐户系统。示例包括：

* Steam：Steam 帐户和身份验证票证 (`ISteamUser::GetAuthTicketForWebApi`)
* PC/主机上的 XBOX：MSA/XBOX `XUserHandle` 和 XSTS 令牌
* PlayStation：PSN 在线 ID/帐户
* Nintendo：Nintendo 服务帐户/设备 ID

这些标识通常用于为给定设备上的活动玩家创建和重用 `PFLocalUserHandle`(例如 `PFLocalUserCreateHandleWithSteamUser` 或包装 `XUserHandle`)。在适当情况下，它们也可用于通过特定于提供程序的 PlayFab Login API(例如 `LoginWithSteam`、`LoginWithXbox`)进行身份验证。

在跨平台进度方案中，平台原生标识会关联到主关联标识，以便进度和权利随玩家跨平台流转。本文档中推荐的工作流使用平台原生标识作为本地用户上下文，并使用主关联标识作为跨平台锚点。

## 关联策略选项

本文档探讨两种相关策略：

1. 所有玩家在游戏之前**必须**将其平台原生标识与你的主关联标识进行关联。
2. 平台原生标识与主关联标识之间的关联是**可选的**，但强烈建议在游戏之前进行关联。

第一种策略更易于实现，并可避免玩家日后在可能丢失进度方面做出艰难决定。但它确实会增加开始游戏时的初始阻力，并且在玩家认为预先关联要求价值不足时，可能导致负面情绪。对于许多依赖极低门槛来吸引尽可能多玩家的免费游戏和移动游戏，这种策略尤其不适合。

第二种策略改善了初始玩家体验，但代价是将来可能出现冲突和艰难决定，并且开发人员的编码复杂性也会增加。玩家无需任何额外的登录要求即可开始游戏。如果游戏支持，他们甚至可以完全离线开始。当他们日后决定关联时，这种灵活性可能会带来问题。他们要关联的标识可能已在另一个平台上存在进度。如果是这种情况，建立关联可能需要放弃进度，或由游戏处理复杂的合并。即使游戏选择此策略，也建议尽早推动关联，说明关联的好处，并警告不关联的潜在风险。

最终，这两种策略之间的选择必须由业务角度驱动。两者并无明显优劣之分。有明确的市场证据表明，发布后从选项 2 过渡到选项 1 会引起玩家不满。这表明尽早做出正确选择并从一开始就围绕它进行设计非常重要。

## 期望状态

无论采用哪种策略，最终目标都是让每个玩家进入相同的状态。其主关联标识应与其游戏所用每台设备上的平台原生标识相关联。在该状态下，进度始终绑定到主关联标识，并且平台原生标识也可以在所有平台上用作主标识的有效代理标识。

在某些平台上，主关联标识**就是**平台原生标识(在 XBOX 上运行的第一方游戏)。在这些情况下，达到期望状态变得非常简单。本文档不深入探讨这些情况，因为现有的登录和关联指南已经足够。

## LocalUser 与登录

区分 PlayFab SDK 中存在的两个相关但独立的概念非常重要。

LocalUserCreate 调用会构造本地用户对象并返回 PFLocalUserHandle，而不执行任何身份验证。它通过特定于平台的或持久化的本地 ID(例如包装 XUserHandle)来标识和缓存用户，以便你可以在各个操作和游戏实例之间重用相同的本地上下文。此操作完全是本地的：它不会发出网络请求、获取实体令牌或创建 PlayFab 帐户。

相比之下，Login 调用会使用 PlayFab 对本地用户进行身份验证，并建立经过身份验证的实体，包括令牌和 ID。Login 调用会执行网络请求(例如 /Client/LoginWithXbox)并遵循 createAccount 等标志；成功后，结果会被缓存，以便后续调用可以重用经过身份验证的状态。

简而言之，创建本地用户会设置 PlayFab Game Saves 所需的本地标识和句柄管理，而登录则是联系 PlayFab 进行身份验证并启用需要实体的 API 的步骤。

## 策略 1 - 必需的主标识关联

为了说明此策略，我们将讨论一款在 Steam 上发布的 XBOX 第一方游戏。该游戏使用 XBOX/MSA 作为其主关联标识。它要求所有玩家在进行任何游戏之前登录其 XBOX/MSA。它仍使用基于 Steam 标识的 LocalUserHandle，但会阻止实际登录，直到玩家创建并关联 XBOX 标识。在后续启动时，由于已验证关联，它可以直接使用 Steam 标识继续。

### 概要

* 玩家必须先使用跨平台标识 (XBOX/MSA) 登录，然后才能游戏。
* 从平台帐户 (Steam) 创建本地玩家，但在 XBOX/MSA 登录完成之前暂停在线游戏。
* XBOX/MSA 登录后，将平台帐户关联到跨平台帐户。
* 刷新本地平台配置文件，使其连接到跨平台帐户。
* 以后的启动将无缝进行：由于关联已建立，玩家可以直接开始游戏。

### 详细演练

**创建 Steam 本地用户句柄**

* 调用 `PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, customContext, outLocalUserHandle)`。
* 结果：`PFLocalUserHandle`；尚未进行身份验证。

**尝试联机**

* 调用 `PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async)`。
* 完成时，如果 `PFLocalUserLoginGetResult` 失败并返回 `E_PF_ACCOUNT_NOT_FOUND`，则触发 XBOX 登录以引导创建帐户。
* 如果 `PFLocalUserLoginGetResult` 成功，则说明我们已有处于期望状态的帐户并且已联机。工作流完成。

> [!NOTE]
>
> 如果你的游戏要求跨平台标识保持与当前用户关联(例如，游戏要求 XBOX 登录，并期望实体的 XBOX 关联与当前登录的 XBOX 帐户匹配)，则仅平台登录成功是不够的。登录后调用 `PFAccountManagementClientGetAccountInfoAsync`，以验证跨平台关联是否与当前用户匹配。如果不匹配，则实体可能绑定到过时的跨平台标识，请按照下面的**通过 XBOX 登录**和**关联 Steam** 步骤进行重新对齐。

**通过 XBOX 登录**

* 构建 `PFAuthenticationLoginWithXUserRequest`：
  * 设置 `createAccount=true`，以便在需要时创建 PlayFab 帐户。
  * 提供 PC 上已登录 XBOX 用户的 `XUserHandle`。
* 调用 `PFAuthenticationLoginWithXUserAsync(serviceConfigHandle, request, async)`。
* 使用 `PFAuthenticationLoginWithXUserGetResultSize(...)` 和 `PFAuthenticationLoginWithXUserGetResult(...)` 完成操作以获取 `PFEntityHandle`。
* 注意：如果 LoginWithXUser 变体不可用，请使用 `PFAuthenticationLoginWithXbox`。这需要你自行从 `XUserHandle` 中提取 XSTS 令牌。

**将 Steam 关联到经过身份验证的(XBOX 支持的)PlayFab 帐户**

* 使用当前 Steam 身份验证票证(来自 `ISteamUser::GetAuthTicketForWebApi` 或集成中的等效函数；请在代码中确认确切的函数名称)构建客户端关联请求。
* 调用 `PFAccountManagementClientLinkSteamAccountAsync(entityHandle, linkRequest, async)`。
* 注意："LinkSteamAccount" 位于 Account Management 下，而不是 Authentication 下。
* 成功后，玩家的 Steam 标识将关联到 XBOX 支持的 PlayFab 帐户。

**将 Steam 本地用户与实体关联(关联后)**

* 再次调用 `PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async)`。
* `PFLocalUserLoginGetResult` 现在会成功；生成的 `PFEntityHandle` 绑定到 Steam 本地用户。

**之后的正常操作**

* Game Save 和其他在线 API 使用经过身份验证的 `PFEntityHandle`。
* 由于所有关联均已就绪，后续启动游戏时可以直接开始游戏。

注意

* `PFLocalUserCreateHandleWithSteamUser` 本身不会登录，但尝试调用 `PFGameSaveFilesAddUserWithUiAsync` 会导致登录尝试。请确保在该调用之前已完成帐户关联流程。

### 关联冲突处理

将 Steam 关联到 XBOX 支持的实体时，可能出现两种不同的冲突。每种冲突需要不同的补救措施：

| 错误 | 含义 | 补救措施 |
| - | - | - |
| `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED` | 当前 Steam 帐户已关联到**另一个** PlayFab 实体。 | (在征得同意后)使用 `forceLink=true` 再次调用 `PFAccountManagementClientLinkSteamAccountAsync`。这会将 Steam 关联移动到当前实体。 |
| `E_PF_ACCOUNT_ALREADY_LINKED` | 当前实体已关联了**另一个** Steam 帐户。 | 先调用 `PFAccountManagementClientUnlinkSteamAccountAsync` 删除旧的 Steam 关联，然后为新的 Steam 帐户调用 `PFAccountManagementClientLinkSteamAccountAsync`。`forceLink` 无法解决此错误。 |

**推荐流程：**

* XBOX 登录后，调用 `PFAccountManagementClientGetAccountInfoAsync(xboxEntity, getInfoRequest, async)` 以获取帐户信息并检查已关联的标识。
* 如果实体已关联了**另一个 Steam 帐户**：
  * 警告玩家继续操作会从此实体中删除旧的 Steam 关联。征得同意。
  * 调用 `PFAccountManagementClientUnlinkSteamAccountAsync(xboxEntity, unlinkRequest, async)` 删除现有的 Steam 关联。
  * 然后使用当前 Steam 票证调用 `PFAccountManagementClientLinkSteamAccountAsync`。
* 如果关联调用返回 `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED`(该 Steam 帐户属于另一个实体)：
  * 警告玩家此 Steam 帐户已与另一个 PlayFab 帐户关联，在此处关联会删除该关联。
  * 征得同意后使用 `forceLink=true` 重试。

这既保留了玩家的自主权，又避免了无意中覆盖关联，同时仍提供了协调标识的清晰途径。

### 示例 C++ 流程(Steam 优先门控、XBOX 引导、关联 Steam、再次使用 Steam 登录)：

> [!NOTE]
>
> 此示例使用 `static XAsyncBlock` 变量来简化生存期管理。生产代码应动态分配异步块(例如作为上下文结构的一部分)，以支持并发调用或重入调用。

```cpp theme={null}
// Assumes: serviceConfigHandle, taskQueue, and a signed-in XUserHandle (xUserHandle) are available
//
// NOTE: RETURN_IF_FAILED is used here for brevity. Because XAsyncBlock callbacks return void,
// production code should replace it with proper error handling (for example, log and return).

// Single context block used by multiple nested callbacks
struct Strategy1AsyncCtx
{
    PFServiceConfigHandle serviceConfig{};
    XTaskQueueHandle queue{};
    PFLocalUserHandle steamUser{};
    XUserHandle xUserHandle{};
    PFEntityHandle xboxEntity{};
};

// 1) Create Steam local user
PFLocalUserHandle steamUser{};
RETURN_IF_FAILED(PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, /*customContext*/ nullptr, &steamUser));

// 2) Attempt Steam login with createAccount=false
XAsyncBlock steamLoginAsync{};
steamLoginAsync.queue = taskQueue;
// Provide context for nested callbacks
// NOTE: Caller is responsible for deleting s1ctx in the terminal callback.
Strategy1AsyncCtx* s1ctx = new Strategy1AsyncCtx{ serviceConfigHandle, taskQueue, steamUser, xUserHandle };
steamLoginAsync.context = s1ctx;
steamLoginAsync.callback = [](XAsyncBlock* async)
{
    auto* ctx = static_cast<Strategy1AsyncCtx*>(async->context);
    // Try to get result-size; failure means login failed
    size_t bufferSize{};
    HRESULT hrSize = PFLocalUserLoginGetResultSize(async, &bufferSize);
    if (FAILED(hrSize))
    {
        if (hrSize == E_PF_ACCOUNT_NOT_FOUND)
        {
            // 3) Bootstrap via Xbox (provider API), then link Steam
            PFAuthenticationLoginWithXUserRequest xreq{};
            xreq.createAccount = true;
            xreq.user = ctx->xUserHandle; // supply your signed-in XUserHandle

            static XAsyncBlock xboxLoginAsync{}; // ensure lifetime until callback
            xboxLoginAsync.queue = async->queue;
            xboxLoginAsync.context = ctx;
            xboxLoginAsync.callback = [](XAsyncBlock* xAsync)
            {
                auto* ctx = static_cast<Strategy1AsyncCtx*>(xAsync->context);
                // Obtain Xbox login result
                PFEntityHandle xboxEntity{};
                size_t xSize{};
                RETURN_IF_FAILED(PFAuthenticationLoginWithXUserGetResultSize(xAsync, &xSize));
                std::vector<uint8_t> xbuf(xSize);
                PFAuthenticationLoginResult const* xres{};
                RETURN_IF_FAILED(PFAuthenticationLoginWithXUserGetResult(xAsync, &xboxEntity, xbuf.size(), xbuf.data(), &xres, nullptr));
                ctx->xboxEntity = xboxEntity;

                // Detect existing Steam linkage before linking
                // Build a minimal GetAccountInfo request
                // Then call PFAccountManagementClientGetAccountInfoAsync(xboxEntity, &getInfoReq, &getInfoAsync)
                // and inspect the linked accounts section of the result.
                //
                // If the entity already has a DIFFERENT Steam account linked:
                //   Call PFAccountManagementClientUnlinkSteamAccountAsync(xboxEntity, &unlinkReq, &unlinkAsync)
                //   to remove the old Steam link, then proceed to link the current Steam account below.
                //   forceLink does NOT resolve this case (E_PF_ACCOUNT_ALREADY_LINKED error).
                //
                // If a DIFFERENT entity has this Steam account claimed:
                //   The link call below will fail with E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED.
                //   Retry with forceLink=true after obtaining player consent.

                // Link Steam to Xbox-backed account
                PFAccountManagementClientLinkSteamAccountRequest linkReq{};
                linkReq.steamTicket = GetCurrentSteamTicket(); // your helper wrapping ISteamUser::GetAuthTicketForWebApi
                bool isServiceSpecific = true;
                linkReq.ticketIsServiceSpecific = &isServiceSpecific;
                // To resolve E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED after consent:
                // bool forceLink = true;
                // linkReq.forceLink = &forceLink;

                static XAsyncBlock linkAsync{};
                linkAsync.queue = xAsync->queue;
                linkAsync.context = ctx;
                linkAsync.callback = [](XAsyncBlock* linkXAsync)
                {
                    auto* ctx = static_cast<Strategy1AsyncCtx*>(linkXAsync->context);
                    RETURN_IF_FAILED(XAsyncGetStatus(linkXAsync, false));

                    // 4) Re-login Steam local user to bind entity handle
                    static XAsyncBlock steamReloginAsync{};
                    steamReloginAsync.queue = linkXAsync->queue;
                    steamReloginAsync.context = ctx;
                    steamReloginAsync.callback = [](XAsyncBlock* reloginAsync)
                    {
                        auto* ctx = static_cast<Strategy1AsyncCtx*>(reloginAsync->context);
                        PFEntityHandle entity{};
                        RETURN_IF_FAILED(PFLocalUserLoginGetResult(reloginAsync, &entity, 0, nullptr, nullptr, nullptr));
                        // Now the Steam local user has an associated entity
                    };

                    RETURN_IF_FAILED(PFLocalUserLoginAsync(ctx->steamUser, /*createAccount*/ false, &steamReloginAsync));
                };

                RETURN_IF_FAILED(PFAccountManagementClientLinkSteamAccountAsync(ctx->xboxEntity, &linkReq, &linkAsync));
            };

            // NOTE: On platforms that don't support LoginWithXUser, replace this with PFAuthenticationLoginWithXboxAsync
            RETURN_IF_FAILED(PFAuthenticationLoginWithXUserAsync(ctx->serviceConfig, &xreq, &xboxLoginAsync));
        }
        // Other failures: handle/log as needed
        return;
    }

    // Steam login succeeded: get entity
    PFEntityHandle entity{};
    RETURN_IF_FAILED(PFLocalUserLoginGetResult(async, &entity, 0, nullptr, nullptr, nullptr));
    // Use entity as needed; PFServices/GameSave APIs, etc.
};

RETURN_IF_FAILED(PFLocalUserLoginAsync(steamUser, /*createAccount*/ false, &steamLoginAsync));
```

实现说明：

* 使用 `E_PF_ACCOUNT_NOT_FOUND` 来决定是否走 XBOX 引导路径；单独处理其他错误。
* 继续使用 Steam `PFLocalUserHandle`；特定于提供程序的 XBOX 登录返回的 `entityHandle` 仅用于关联 Steam。
* 关联后，再次使用 Steam 登录，以将实体绑定到 LocalUser。
* 确保获取新的 Steam 票证用于关联。
* 不再需要时关闭句柄(`PFLocalUserCloseHandle`、`PFEntityCloseHandle`)。

## 策略 2 - 可选的主标识关联

在此方案中，游戏在 Steam 上运行，并使用 XBOX/MSA 作为主关联标识。玩家无需立即登录 XBOX/MSA 即可开始游戏。游戏通过明确的好处说明和警告来鼓励尽早关联，但不会阻止初始游戏。

### 概要

* 使用平台帐户 (Steam) 创建本地玩家，并尝试在不创建新帐户的情况下进行在线游戏。
* 如果在线可用，则继续；如果不可用，则提供以下选择：
  * 使用跨平台标识 (XBOX/MSA) 登录以创建统一帐户，或者
  * 立即创建仅限平台的帐户并马上开始游戏。
* 通过说明好处和风险，鼓励尽早关联到 XBOX/MSA。
* 当玩家选择关联时：
  * 如果直接关联成功，则使用统一帐户继续游戏。
  * 如果 XBOX/MSA 帐户在其他地方已有进度，则暂停并询问要保留哪个进度(协调)：
    * 保留 XBOX/MSA 进度并附加当前平台帐户。
    * 保留当前平台进度并推迟关联。
* 关联后，刷新本地平台配置文件，使其连接到统一帐户。
* 以后的启动将无缝进行：玩家可以直接开始游戏(假设未推迟关联)。

### 详细演练

**创建 Steam 本地用户句柄**

* 调用 `PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, customContext, outLocalUserHandle)`。
* 结果：`PFLocalUserHandle`；尚未进行身份验证。

**尝试联机(静默 Steam 路径)**

* 调用 `PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async)`。
* 完成时：
  * 如果 `PFLocalUserLoginGetResult` 成功，则使用生成的 `PFEntityHandle` 并继续联机。
    * 检查 Steam 支持的帐户是否已关联到 XBOX/MSA。调用 `PFAccountManagementClientGetAccountInfoAsync(entityHandle, getInfoRequest, async)` 并检查已关联的标识。如果未关联，则提示玩家使用下面的流程关联 XBOX(非阻塞)，以便将来的跨平台进度能够无缝工作。
  * 如果 `PFLocalUserLoginGetResult` 失败并返回 `E_PF_ACCOUNT_NOT_FOUND`，则玩家将保持离线状态，直到创建帐户。提供两个选择：
    * 立即创建 XBOX 支持的 PlayFab 帐户：使用 XBOX/MSA 登录(例如 `PFAuthenticationLoginWithXUserAsync`)。
    * 立即使用 Steam 登录并开始游戏：调用 `PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ true, async)` 创建绑定到本地句柄的 Steam 支持的 PlayFab 帐户；成功后，使用生成的 `PFEntityHandle` 立即联机。
  * 对于其他错误，请妥善处理(重试/退避)。

**提示关联 XBOX(非阻塞)**

* 展示关联的好处(跨平台进度、权利可移植性)。
* 提供 XBOX/MSA 登录。

**通过 XBOX 登录(引导或延迟关联，并检测是否需要协调)**

* 如果玩家稍后发起 XBOX 关联(他们已从当前会话获得 Steam 支持的 `PFEntityHandle`)：
  * 不要创建新的 XBOX 支持的帐户。
  * 通过调用相应的关联 API(例如 `PFAccountManagementClientLinkXboxAccountAsync(currentSteamEntity, linkRequest, async)`)，尝试将 XBOX 标识直接关联到当前 Steam 支持的帐户。
  * 如果关联成功，则 XBOX/MSA 标识现已关联；继续正常操作。
  * 如果关联因预先存在的关联/冲突错误而失败(XBOX 标识已在其他位置关联)，则进入帐户协调模式。立即使用 `createAccount=false` 执行 XBOX/MSA 登录(例如 `PFAuthenticationLoginWithXUserAsync`)以获取 XBOX 支持的 `PFEntityHandle`，然后在提示玩家之前获取配置文件元数据作为上下文。征得同意后，继续进行关联决策。
* 如果玩家没有 XBOX 的现有帐户(引导路径)：
  * 首先使用 `PFAuthenticationLoginWithXUserAsync`(或在 `LoginWithXUser` 不可用时使用 `PFAuthenticationLoginWithXboxAsync`)以 `createAccount=false` 尝试 XBOX/MSA 登录。
    * 如果登录成功：XBOX/MSA 帐户已有 PlayFab 帐户，并且可能已有进度。进入帐户协调模式以选择要保留的进度。
    * 如果登录失败并返回 `E_PF_ACCOUNT_NOT_FOUND`：通过使用 `createAccount=true` 再次登录来创建 PlayFab 帐户。
  * 使用 `PFAuthenticationLoginWithXUserGetResultSize(...)` 和 `PFAuthenticationLoginWithXUserGetResult(...)` 完成操作以获取 `PFEntityHandle`。

**将 Steam 关联到经过身份验证的(XBOX 支持的)PlayFab 帐户**

* 适用于刚刚创建全新 XBOX 支持的 PlayFab 帐户(没有现有 Steam 关联)的情况。
* 如果之前已进入帐户协调，则关联决策和任何所需的 `forceLink` 操作都在那里处理；跳过此步骤。
* 使用当前 Steam 身份验证票证(来自 `ISteamUser::GetAuthTicketForWebApi` 或你的集成)构建客户端关联请求。
* 使用 `forceLink=false`(默认值)调用 `PFAccountManagementClientLinkSteamAccountAsync(entityHandle, linkRequest, async)`。对于全新帐户，此操作预计会成功。
* 如果关联失败，请将其视为错误情况(意外冲突或身份验证失败)。向玩家显示错误，并考虑在重试之前提示他们重新登录。
* 成功后，玩家的 Steam 标识将关联到 XBOX 支持的 PlayFab 帐户。

**将 Steam 本地用户与实体关联(关联后)**

* 再次调用 `PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async)`。
* `PFLocalUserLoginGetResult` 现在会成功；生成的 `PFEntityHandle` 绑定到 Steam 本地用户。

**之后的正常操作**

* Game Save 和其他在线 API 使用经过身份验证的 `PFEntityHandle`。
* 由于所有关联均已就绪，后续启动游戏时将能够直接开始游戏。

注意

* 不要使用 XBOX/MSA 登录阻止初始游戏；应尽早提示，但允许玩家推迟。
* `PFLocalUserCreateHandleWithSteamUser` 本身不会登录。请确保在调用会触发登录的 API(如 `PFGameSaveFilesAddUserWithUiAsync`)之前完成关联流程。

### 帐户协调(XBOX/MSA 上已有进度)

当玩家使用已有进度(例如来自主机或其他平台)的 XBOX/MSA 帐户登录时，必须避免盲目创建新帐户或覆盖现有关联。请使用谨慎的两阶段方法进行检测和协调。

> [!TIP]
>
> \*\*面向玩家的措辞：\*\*在开发人员文档中，我们使用"协调"一词。对于玩家 UI，建议使用更清晰的术语，例如"游戏配置文件选择"、"选择进度"或"保留现有进度"，以减少混淆，并强调玩家是在选择一个配置文件继续(而不是合并)。

#### 检测阶段

* 可以通过两种途径进入协调：
  1. 检测到现有 XBOX/MSA 帐户：使用 `createAccount=false` 尝试 XBOX/MSA 登录。
     * 如果登录成功，则 XBOX/MSA 标识已有 PlayFab 帐户，并且可能已有进度，此时进入协调。
     * 如果登录失败并返回 `E_PF_ACCOUNT_NOT_FOUND`，则继续进行引导 (`createAccount=true`)。无需进入协调。
  2. 检测到延迟关联冲突：将 XBOX/MSA 关联到当前 Steam 支持的帐户时，关联因预先存在的关联/冲突错误而失败(XBOX 标识已与另一个 Steam 标识关联)。
     * 立即使用 `createAccount=false` 执行 XBOX/MSA 登录(例如 `PFAuthenticationLoginWithXUserAsync`)以获取 XBOX 支持的 `PFEntityHandle`。
     * 获取配置文件元数据(例如最近的进度、存档槽、时间戳)以展示上下文。
     * 进入协调以决定要保留哪个进度。

#### 协调阶段

* 推荐最直接的选项：保留 XBOX/MSA(主关联标识)进度并放弃本地 Steam 进度。这样可以保留跨平台锚点，并避免复杂的合并。
* 玩家实际上有三种选择(游戏可以选择仅提供其中 1-2 种)：
  1. 保留 XBOX/MSA 进度(推荐)：继续将当前 Steam 帐户关联到现有的 XBOX 支持的 PlayFab 帐户。
  2. 保留当前 Steam 进度并取消关联：暂时继续使用 Steam 支持的帐户；不关联或覆盖 XBOX/MSA 数据。下次再次提供关联。
  3. 保留当前 Steam 进度并放弃 XBOX 进度：切换到 Steam 支持的帐户，并有意放弃 XBOX/MSA 进度。此途径需要明确的游戏设计考量，不应轻易尝试，尤其是当其他平台原生标识已附加到 XBOX 帐户时。本文档不涉及此方案的实现细节。
* 提交之前：
  * 为 XBOX 实体调用 `PFAccountManagementClientGetAccountInfoAsync`，以检查现有的提供程序关联并验证是否已存在 Steam 关联。
  * 如果该实体已关联**另一个 Steam 帐户**，请警告玩家，在添加新关联之前，会从该实体中删除此现有 Steam 关联。这需要在关联当前 Steam 帐户之前调用 `PFAccountManagementClientUnlinkSteamAccountAsync`。`forceLink` 无法解决此情况。
  * 如果**当前 Steam 帐户**已关联到**另一个实体**，则调用 `PFAccountManagementClientLinkSteamAccountAsync` 会返回 `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED`。警告关联会将 Steam 标识从另一个实体移走，然后在征得同意后使用 `forceLink=true` 重试。

#### 提交操作(基于玩家选择)

* 玩家选择 XBOX/MSA 进度：
  * 确保已获取 XBOX 登录实体(如果在检测期间尚未执行，则使用 `createAccount=false` 执行 `PFAuthenticationLoginWithXUserAsync`)。
  * 如果 XBOX 实体已关联另一个 Steam 帐户，请先调用 `PFAccountManagementClientUnlinkSteamAccountAsync` 将其删除。
  * 将当前 Steam 关联到 XBOX 支持的帐户。如果调用返回 `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED`(Steam 位于另一个实体上)，请在征得同意后使用 `forceLink=true` 重试。
  * 再次使用 Steam 本地用户登录 (`PFLocalUserLoginAsync(..., /*createAccount*/ false, ...)`) 以绑定实体。
* 玩家选择 Steam 进度：
  * 如果 Steam 尚无 PlayFab 帐户，则通过 `PFLocalUserLoginAsync(..., /*createAccount*/ true, ...)` 创建/绑定。
  * 推迟 XBOX 关联；允许立即开始游戏。下次再次提供关联。

#### 注意

* 始终获取并显示足够的上下文，以便玩家做出决定。
* 记录协调结果的遥测数据，以便随着时间推移改进提示和默认设置。

### 策略 2 流程图

下图显示了完整的决策树。每条路径最终都汇聚到同一个终端节点，玩家在该节点达到可玩状态。

![策略 2 流程图，显示从 Steam 本地用户创建到登录、帐户关联和协调路径的完整决策树。](https://mintcdn.com/microsoft-4404708b/U1LR64ZWxo45eXwl/images/playfab/player-progression/game-saves/strategy2-flow.svg?fit=max\&auto=format\&n=U1LR64ZWxo45eXwl\&q=85\&s=a4c6001009c7729b9a68014f478b1a20)

## 另请参阅

* [PlayFab Game Saves 概述](/zh-CN/services/playfab/player-progression/game-saves/overview.md)
* [Game Saves 快速入门](/zh-CN/services/playfab/player-progression/game-saves/quickstart.md)
* [Game Saves 冲突](/zh-CN/services/playfab/player-progression/game-saves/conflicts.md)
* [令牌过期和重新登录](/zh-CN/services/playfab/sdks/c/relogin.md)


## Related topics

- [PlayFab 的 Microsoft 帐户身份验证](/zh-CN/services/playfab/identity/dev-identity/authentication/aad-authentication.md)
- [XR-013 将 Microsoft 帐户与发行商帐户关联](/zh-CN/publishing/certification/xr/xr-013.md)
- [XR013 将 Microsoft 帐户与发行商帐户关联](/zh-CN/publishing/certification/fma/xr-013.md)
- [游戏存档概述](/zh-CN/services/playfab/player-progression/game-saves/overview.md)
- [用于 PlayFab API 的 Microsoft Entra ID 身份验证](/zh-CN/services/playfab/identity/dev-identity/authentication/entra-id-api-authentication.md)
