> ## 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 登入呼叫支援，例如 `LoginWithXbox` 或 `LoginWithOpenIdConnect`

除了這兩項需求之外，如果該身分識別支援某種以裝置為基礎的快取，以及後續啟動遊戲時的無訊息登入，會很有幫助。這並非硬性需求，但可以降低工作流程的複雜度和/或玩家的阻力。

## 平台原生身分識別

平台原生身分識別是玩家執行遊戲之平台所提供的帳戶系統。範例包括：

* Steam：Steam 帳戶和驗證票證 (`ISteamUser::GetAuthTicketForWebApi`)
* PC/主機上的 XBOX：MSA/XBOX `XUserHandle` 和 XSTS 權杖
* PlayStation：PSN Online ID / 帳戶
* Nintendo：Nintendo Service Account / 裝置識別碼

這些身分識別通常用於為特定裝置上的作用中玩家建立和重複使用 `PFLocalUserHandle` (例如 `PFLocalUserCreateHandleWithSteamUser` 或包裝 `XUserHandle`)。在適當的情況下，它們也可以透過提供者特定的 PlayFab 登入 API (例如 `LoginWithSteam`、`LoginWithXbox`) 進行驗證。

在跨平台進度案例中，平台原生身分識別會連結至主要連結身分識別，讓進度和權利跨平台跟隨玩家。本文件中建議的工作流程使用平台原生身分識別作為本機使用者內容，並使用主要連結身分識別作為跨平台錨點。

## 連結策略選項

本文件探討兩種相關的策略：

1. 所有玩家都**必須**在遊玩之前將其平台原生身分識別與您的主要連結身分識別連結。
2. 平台原生身分識別與主要連結身分識別之間的連結是**選擇性的**，但強烈鼓勵在遊玩之前進行連結。

第一種策略較容易實作，並可避免玩家日後面臨可能遺失進度的困難決定。它確實會增加開始遊戲時的初始阻力，並可能在玩家看不到預先連結需求的足夠價值時引發負面觀感。它特別不適合許多仰賴極低門檻來吸引盡可能多玩家的免費遊玩遊戲和行動遊戲。

第二種策略改善了一開始的玩家體驗，但代價是日後可能出現的衝突和困難決定，以及開發人員更高的程式碼複雜度。玩家可以在沒有任何額外登入需求的情況下開始遊玩。如果遊戲支援，他們甚至可以完全離線開始。這種彈性可能會在他們日後決定連結時造成問題。他們所連結的身分識別可能在另一個平台上已有既有進度。如果是這樣，建立連結可能涉及放棄進度，或是遊戲需要處理的雜亂合併。即使遊戲選擇此策略，也鼓勵它們盡早推廣連結、說明連結的好處，並警告不連結的潛在風險。

最終，這兩種策略之間的選擇必須從商業角度出發。兩者之間沒有哪一個明顯較佳。有明確的市場證據顯示，在推出後從選項 2 轉換為選項 1 會造成玩家不滿。這說明了盡早做出正確選擇並從一開始就圍繞它進行設計的重要性。

## 理想狀態

無論採用哪種策略，最終目標都是讓每位玩家進入相同的狀態。他們的主要連結身分識別應與他們遊玩之每部裝置上的平台原生身分識別連結。在此狀態下，進度一致地繫結至主要連結身分識別，而平台原生身分識別也可以在所有平台上作為主要身分識別的有效代理身分識別。

在某些平台上，主要連結身分識別可能**就是**平台原生身分識別 (在 XBOX 上執行的第一方遊戲)。在這些情況下，達到理想狀態變得輕而易舉。本文件不會深入探討這些情況，因為現有的登入和連結指引已經足夠。

## LocalUser 與登入

請務必區分 PlayFab SDK 中存在的兩個相關但不同的概念。

LocalUserCreate 呼叫會建構本機使用者物件並傳回 PFLocalUserHandle，而不執行任何驗證。它會依平台特定或保存的本機識別碼 (例如包裝 XUserHandle) 識別並快取使用者，讓您可以在各個作業和遊戲執行個體之間重複使用相同的本機內容。此作業完全是本機作業：它不會發出網路要求、取得實體權杖或建立 PlayFab 帳戶。

相反地，Login 呼叫會向 PlayFab 驗證本機使用者，並建立已驗證的實體，包括權杖和識別碼。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** 步驟重新對齊。
</Note>

**透過 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 登入)：

<Info>
  此範例使用 `static XAsyncBlock` 變數來簡化存留期管理。生產程式碼應以動態方式配置非同步區塊 (例如作為內容結構的一部分)，以支援並行或可重新進入的呼叫。
</Info>

```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 登入會傳回只用來連結 Steam 的 `entityHandle`。
* 連結後，再次以 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，建議使用更清楚的用語，例如「遊戲設定檔選擇」、「選擇進度」或「保留既有進度」，以減少混淆，並強調玩家是在選擇一個要繼續的設定檔 (而不是合併)。
</Tip>

#### 偵測階段

* 可以透過兩條路徑進入調和：
  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 流程圖

下圖顯示完整的決策樹。每條路徑都會匯聚到單一終端節點，玩家在此達到可遊玩狀態。

<img src="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" alt="策略 2 流程圖，顯示從 Steam 本機使用者建立到登入、帳戶連結和調和路徑的完整決策樹。" width="1424" height="1968" data-path="images/playfab/player-progression/game-saves/strategy2-flow.svg" />

## 另請參閱

* [PlayFab Game Saves 概觀](/zh-TW/services/playfab/player-progression/game-saves/overview)
* [Game Saves 快速入門](/zh-TW/services/playfab/player-progression/game-saves/quickstart)
* [Game Saves 衝突](/zh-TW/services/playfab/player-progression/game-saves/conflicts)
* [權杖到期和重新登入](/zh-TW/services/playfab/sdks/c/relogin)


## Related topics

- [整合通用 Windows 平台與 PlayFab](/zh-TW/services/playfab/identity/player-identity/platform-specific-authentication/uwp-integration.md)
- [在 Unity 中從 Google 登入移轉至 Google Play Games](/zh-TW/services/playfab/identity/player-identity/platform-specific-authentication/google-play-games-sign-in-migration-details.md)
- [Game Saves 概觀](/zh-TW/services/playfab/player-progression/game-saves/overview.md)
- [帳戶連結快速入門](/zh-TW/services/playfab/identity/player-identity/login/quickstart.md)
- [XR-013 將 Microsoft 帳戶與發行者帳戶連結](/zh-TW/publishing/certification/xr/xr-013.md)
