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

# 統計資料和成就

> 將 Steam 成就和統計資料移植到 XBOX GDK 遊戲管理 API，在遊戲中追蹤進度，並將簽入同步至 XBOX 服務。

在 XBOX Game Development Kit (GDK) 和 Steam 中，成就都具有名稱、描述、圖示、是否已為目前使用者解除鎖定等資訊，以及完成成就的進度（選擇性）。Steam 也有統計資料 API，遊戲會使用它將使用者的進度與某個預先設定的目標進行比較，並在超過這些目標時解除鎖定成就。

事實上，XBOX Game Development Kit (GDK) 有兩個成就 API：*以事件為基礎*（先前稱為 *Achievements 2013*）和\_遊戲管理\_（先前稱為 *Achievements 2017*）。雖然這兩個系統都是可行的選項，但如果您是從 Steam 轉換過來，遊戲管理的成就/統計資料會讓您更熟悉，而且會符合您遊戲中可能已存在的邏輯。因此，本主題將說明該 API。如需這兩個 API 之間差異的詳細資訊，請參閱[以事件為基礎與遊戲管理的成就](/zh-TW/services/xbox-services/player-data/achievements/index)。下列各節說明 Steamworks API 與 XBOX Game Development Kit (GDK) 中成就的差異。

若要使用遊戲管理的成就 API，您需要建立 XBOX 服務內容控制代碼。您可以使用 [XblContextCreateHandle](/zh-TW/reference/live/xsapi-c/xbox_live_context_c/xbox_live_context_c_members) 函式和來自 [XUserAddResult API](/zh-TW/build/core-features/common/user/xuser_howto_best_practice_signing_in) 的 `XUserHandle`（來自[使用者驗證和擁有權](/zh-TW/build/steam-porting-guide/features/user-authentication-and-ownership)主題）來完成此動作。

## 追蹤成就進度

在 Steam API 中，您可以使用 `ISteamUserStats::GetStat`/`ISteamUser::SetStat` API 追蹤使用者在成就上的進度，使儲存在 Steam Stats API 中的值成為目前進度值的事實來源。

使用遊戲管理的成就時，您的遊戲是唯一的事實來源，必須自行追蹤使用者的目前進度。透過 API 呼叫更新雲端中的目前進度，是一種「簽入」動作，用於在 XBOX 生態系統的某些介面中顯示使用者的進度。關於目前進度值應存放在何處並沒有特定指引，但許多遊戲選擇將它儲存在存檔中（以及雲端中），而其他遊戲則可能儲存在自己的後端服務上。請務必讓雲端與遊戲內部統計資料管理系統之間的值保持同步。

## 列舉所有成就

若要使用 Steamworks API 取得使用者的所有統計資料，您通常會呼叫 `ISteamUserStats::RequestUserStats` 從伺服器提取最新的統計資料。在它引發對應的回呼之後，您就可以使用 `ISteamUserStats::GetStat`/`ISteamUserStats::GetAchievement` 函式依 API 名稱逐一查看每個成就，並使用它們傳回的資料初始化遊戲狀態。

使用 XBOX Game Development Kit (GDK) 時，您只要使用 [XblAchievementsGetAchievementsForTitleIdAsync](/zh-TW/reference/live/xsapi-c/achievements_c/achievements_c_members) 函式，然後使用 [XblAchievementsResultGetAchievements](/zh-TW/reference/live/xsapi-c/achievements_c/achievements_c_members) 之 Out 參數所提供陣列中每個成就物件的資料來初始化遊戲狀態即可。每個成就都以 [XblAchievement](/zh-TW/reference/live/xsapi-c/achievements_c/achievements_c_members) 結構提供。如需其所包含資料的詳細資訊，請參閱[其參考主題](/zh-TW/reference/live/xsapi-c/achievements_c/achievements_c_members)。

### Steamworks

```cpp theme={null}
void MyGameClass::Initialize()
{
    // ...
    SteamUserStats()->RequestCurrentStats();
    STEAM_CALLBACK( MyGameClass, OnUserStatsReceived, UserStatsReceived_t );
}

void MyGameClass::OnUserStatsReceived()
{
    // Iterate over stats/achievements, and initialize any necessary game data.
    // Assume that m_achievementIds is an array of "API names" for achievements.
    for (int i = 0; i < m_achievementIds.length; i++)
    {
        const char *achievementName = SteamUserStats()->GetAchievementDisplayAttribute(m_achievementIds[i], "name");
        const char *description = SteamUserStats()->GetAchievementDisplayAttribute(m_achievementIds[i], "desc");
        bool hidden = SteamUserStats()->GetAchievementDisplayAttribute(m_achievementIds[i], "hidden");
        bool isUnlocked;
        SteamUserStats()->GetAchievement(m_achievementIds[i], &isUnlocked);
        // Do any necessary achievement initialization...
    }
}
```

### XBOX Game Development Kit (GDK)

```cpp theme={null}
auto asyncBlock = std::make_unique<XAsyncBlock>();
asyncBlock->context = nullptr;
asyncBlock->callback = [](XAsyncBlock* asyncBlock)
{
    // Take over ownership of the XAsyncBlock*
    std::unique_ptr<XAsyncBlock> asyncBlockPtr{ asyncBlock }; 

    XblAchievementsResultHandle resultHandle;
    auto hr = XblAchievementsGetAchievementsForTitleIdResult(asyncBlock, &resultHandle);

    if (SUCCEEDED(hr))
    {
        const XblAchievement* achievements = nullptr;
        size_t achievementsCount = 0;
        hr = XblAchievementsResultGetAchievements(resultHandle, &achievements, &achievementsCount);

        for (size_t i = 0; i < achievements.length; i++)
        {
            auto name = achievement[i].name;
            char description[256];
            char progress[4] = "100";
            if (achievement[i].progressState == XblAchievementProgressState::Achieved)
            {
                strcpy(description, achievement[i].unlockedDescription);
            }
            else
            {
                strcpy(description, achievement[i].lockedDescription);
                strcpy(progress, achievement[i].progression.requirements[0].currentProgressValue);
            }
            // Use this data to initialize achievements...
        }

        // When you're done with the handle, close it. This will free the achievements list 
        // from memory.
        XblAchievementsResultCloseHandle(resultHandle);
        achievements = nullptr; 
        // Instead, you couldn't close the handle and store it.  
        // If you needed to copy the handle, call XblAchievementsResultDuplicateHandle()
    }
};

// Assume m_xblContext is an XBOX services Context handle that has been initialized properly.
uint64_t xuid;
XblContextGetXboxUserId(m_xblContext, &xuid);

HRESULT hr = XblAchievementsGetAchievementsForTitleIdAsync(
    m_xblContext,
    xuid, 
    m_titleId, // Assume this is a uint32_t holding your game's title ID.
    XblAchievementType::Persistent, // achievementType: You probably want to use XblAchievementType::Persistent.
    false, //unlockedOnly: If true, returns only unlocked achievements.
    XblAchievementOrderBy::DefaultOrder, // orderBy: How to order the incoming achievements list. 
    0, // skipItems: The number of achievements to skip. 
    0, // maxItems: Set to 0 to get all achievements/disable pagination.
    asyncBlock.get()
);
if (SUCCEEDED(hr))
{
    // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership.
    // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock*
    asyncBlock.release();
}
```

如需遊戲管理成就的詳細資訊，請參閱[取得遊戲管理的成就](/zh-TW/services/xbox-services/player-data/achievements/index)。

## 更新成就進度和解除鎖定成就

在 XBOX Game Development Kit (GDK) 和 Steam 中，都是由遊戲的程式碼決定何時應解除鎖定成就。使用 XBOX Game Development Kit (GDK) 時，將成就的進度設定為 100 即可解除鎖定。下列各節說明使用這兩個 API 更新使用者在指定成就上之進度所需的步驟。

以下是在兩個 SDK 中實作的 `UpdateAchievement` 方法程式碼範例，它會接收成就識別碼/API 名稱（在 Steam 中還有統計資料 API 名稱）、成就要遞增的數量，以及解除鎖定該成就的目標值，並據此更新成就的進度和解除鎖定狀態。

### Steamworks

```cpp theme={null}
void MyGame::UpdateAchievement(const char *achievementApiName, const char *statApiName, int diff, int target)
{
    bool isCompleted;
    int value;
    bool result = SteamUserStats()->GetAchievement(achievementApiName, &isCompleted);
    if (!result) {
        // Handle error.
    }

    result = SteamUserStats()->GetStat(statApiName, &value);
    if (!result) {
        // Handle error.
    }

    SteamUserStats()->SetStat(statApiName, value + diff);
    
    if (!isCompleted && value + diff >= target)
    {
             // Unlock the achievement.
        SteamUserStats()->SetAchievement(achievementApiName);
        SteamUserStats()->StoreStats();
    }
}

// At the next checkpoint...
if(!SteamUserStats()->StoreStats()) {
    // Handle error.
}
```

### XBOX Game Development Kit (GDK)

```cpp theme={null}
void MyGame::UpdateAchievement(const std::string& achievementId, int diff, int target)
{
    // Assume this function returns the specified achievement's progress from wherever you're storing it.
    auto previousValue = MyGetAchievementProgress(achievementId);
    // If you're storing progress elsewhere, make sure to update it there, too! Again, we're assuming 
    // that this is a function you've written to update the progress value wherever it's stored.
    MySetAchievementProgress(previousValue + diff);
    // Calculate the percent of the target that the user has reached after adding the value of diff.
    uint32_t percentComplete = (previousValue + diff) / target;

    auto async = std::make_unique<XAsyncBlock>();
    async->context = nullptr;
    async->callback = [](XAsyncBlock *async)
    {
        // Take over ownership of the XAsyncBlock*
        std::unique_ptr<XAsyncBlock> asyncBlockPtr{ async }; 

        HRESULT result = XAsyncGetStatus(async, true);

        if (SUCCEEDED(result))
        {
            // Success!
        }
        else
        {
            if (result == HTTP_E_STATUS_NOT_MODIFIED)
            {
                // The achievement was already completed or it was previously
                // set to a higher value, so it's unchanged.
            }
            else
            {
                // Handle error.
            }
        }
    };

    // Assume m_xblContext is an XBOX services Context handle that has been initialized properly.
    uint64_t xuid;
    XblContextGetXboxUserId(m_xblContext, &xuid);
    HRESULT hr = XblAchievementsUpdateAchievementAsync(
        m_xblContext,
        xuid,
        achievementId.c_str(),
        percentComplete,
        async
    );

    if (SUCCEEDED(hr))
    {
        // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership.
        // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock*
        asyncBlock.release();
    }
}
```

如需遊戲管理成就的詳細資訊，請參閱[更新遊戲管理的成就](/zh-TW/services/xbox-services/player-data/achievements/index)。

## 離線成就

解除鎖定 XBOX 成就不需要網路連線。如果使用者未連線到 XBOX 網路（也稱為 XBOX Live），成就稍後會自動與伺服器同步解除鎖定，不需要額外的程式碼。

## 取得其他使用者的成就

若要取得其他使用者的成就，您只要使用要擷取其成就之使用者的 XBOX 使用者識別碼 (XUID) 呼叫 [XblAchievementsGetAchievementsForTitleAsync](/zh-TW/reference/live/xsapi-c/achievements_c/achievements_c_members)/[XblAchievementsGetAchievementAsync](/zh-TW/reference/live/xsapi-c/achievements_c/achievements_c_members) 函式即可。這類似於在 Steam 中使用 `ISteamUser::RequestUserStats`、`ISteamUserStats::GetUserStat` 和 `ISteamUserStats::GetUserAchievement` 函式。

## 重設成就

Steamworks API 的 `ISteamUserStats::ResetAllStats` 可用來重設使用者在所有統計資料上的進度，以及（如有指定）成就；但與此不同，XBOX Game Development Kit (GDK) 沒有記載以程式設計方式重設成就進度或解除鎖定狀態的功能。嘗試以進度值 0 呼叫 [XblAchievementsUpdateAchievementAsync](/zh-TW/reference/live/xsapi-c/achievements_c/achievements_c_members) 會讓進度值保持不變。不過，您可以在測試時使用[玩家資料重設工具 (XblPlayerDataReset.exe)](/zh-TW/tools/tools-services/live-player-data-reset) 來達成此目的。

## 速率限制

更新成就進度時，請小心避免過於頻繁地呼叫 XBOX Services API (XSAPI)，因為這可能會導致您的遊戲達到其速率限制。這可能會造成非預期的行為，並為使用者帶來不良的體驗。

對於追蹤經常更新之統計資料的成就，請嘗試批次處理 API 呼叫，使其在發生特定數量的事件後引發，或以特定時間間隔在伺服器上更新。

您可以使用 [XBOX 服務追蹤分析器 (XblTraceAnalyzer.exe)](/zh-TW/tools/tools-services/live-trace-analyzer) 偵錯您的 API 呼叫。

如需 XBOX 服務速率限制的詳細資訊，請參閱[精細速率限制](/zh-TW/services/xbox-services/develop/best-practices/live-fine-grained-rate-limiting)。


## Related topics

- [設定 XBOX services](/zh-TW/publishing/game-publishing/concepts/xbox-services.md)
- [快速入門 (Windows) - 呼叫 PlayFab 服務](/zh-TW/services/playfab/sdks/unified-sdk/quickstart-services.md)
- [Steamworks 中沒有的 GDK 功能](/zh-TW/build/steam-porting-guide/gdk-features-not-in-steamworks.md)
- [GDK 與 Steamworks 之間的概念差異](/zh-TW/build/steam-porting-guide/conceptual-differences.md)
- [設定沙箱](/zh-TW/services/xbox-services/fundamentals/sandboxes/live-setting-up-sandboxes.md)
