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

# 排行榜

> 使用基于事件的 stats 与 Partner Center 中的 stat 规则,在 XBOX GDK 上重建 Steam 排行榜;附上两种排行榜 API 的代码对比示例。

XBOX 服务的排行榜由用户统计派生,并通过查询构建。这与 Steam 的排行榜不同 —— Steam 的排行榜由上传到 API 的“分数”驱动。与成就类似,XBOX Game Development Kit(GDK)提供两种 stats API:基于事件与作品管理型。与成就不同的是,我们推荐使用基于事件的 stats 与排行榜,而不是作品管理型。本主题重点介绍该 API。有关这两种 API 差异的更多信息,参见 [基于事件与作品管理型 Stats](/services/xbox-services/player-data/stats-leaderboards/index)。

两个平台都在各自门户中定义排行榜。区别在于,XBOX 服务需要你先在 Partner Center 定义 stat 规则,然后基于该规则驱动的 stat 构建排行榜。Steam 则不区分两者:你在其门户中或以编程方式创建排行榜,然后直接将用户分数上传到该排行榜。

为了展示每种 API 相较 Steam Leaderboard API 的用法,下面提供在 Steam SDK 与基于事件的 XBOX Game Development Kit(GDK)stats 平台上,如何为“行走最远距离”(该示例来自 Steamworks API 文档)创建排行榜的代码示例。以下小节逐步说明其逻辑。

## 在门户中创建 stat/排行榜

### Steamworks

在 Steamworks 管理门户中,进入 **Stats & Achievements** > **Leaderboards**。按下方截图(来自 Steamworks 文档)所示填写表单。

<img src="https://mintcdn.com/microsoft-4404708b/5IpvKlT-jmAaVkeY/images/nda/steam-porting-guide/steam-portal-add-leaderboard.png?fit=max&auto=format&n=5IpvKlT-jmAaVkeY&q=85&s=e8a3f4a76f8a9bdc60689571bf5a9dbb" alt="Screenshot of the form used to create a leaderboard in the Steamworks Admin Portal" width="806" height="190" data-path="images/nda/steam-porting-guide/steam-portal-add-leaderboard.png" />

### XBOX Game Development Kit(GDK)(基于事件)

在 Partner Center 中,进入你的游戏并选择 **XBOX services** > **Gameplay settings**。在右侧窗格的导航栏选择 **Player Stats** > **Stat Rules**,添加一条类型为 **Show** 的 stat 规则,并按需配置。由于我们的 stat 更新将包含玩家(用户)自上次更新以来行走距离的差值,我们会指定一个测量字段,让服务从中获取此值。

<Note>
  将更新事件的数据存储在事件的 Measurements 或 Dimensions 字段中在功能上没有区别。这两个字段的存在是为了配合 Application Insights 使用。更多信息参见 \[Application Insights API for custom events and
</Note>

metrics]\([https://learn.microsoft.com/en-us/azure/azure-monitor/app/api-custom-events-metrics](https://learn.microsoft.com/en-us/azure/azure-monitor/app/api-custom-events-metrics))。

以下截图展示了在 Partner Center 中创建 stat 规则时使用的表单。

<img src="https://mintcdn.com/microsoft-4404708b/5IpvKlT-jmAaVkeY/images/nda/steam-porting-guide/partner-center-stat-rule.png?fit=max&auto=format&n=5IpvKlT-jmAaVkeY&q=85&s=08b05f51f44f9f912854674ea3c189ae" alt="Screenshot of the form used to create a stat rule in Partner Center" width="1196" height="1046" data-path="images/nda/steam-porting-guide/partner-center-stat-rule.png" />

保存后,在右侧窗格的顶部选择 **Leaderboard**,并创建一个由你刚才创建的 stat 驱动的新排行榜,如下方截图所示。

<img src="https://mintcdn.com/microsoft-4404708b/5IpvKlT-jmAaVkeY/images/nda/steam-porting-guide/partner-center-leaderboard.png?fit=max&auto=format&n=5IpvKlT-jmAaVkeY&q=85&s=5ffd10e8fdb53ed640fcb59ef31e323a" alt="Screenshot of the form used to create a leaderboard in Partner Center" width="1048" height="654" data-path="images/nda/steam-porting-guide/partner-center-leaderboard.png" />

## 上传分数

### Steamworks

在 Steamworks 中,你需要计算行走英尺的新累计值,并通过 `ISteamUserStats::UploaderLeaderboardScore` 方法发送到 API。

```cpp theme={null}
int32 newFeetTraveled; // Calculate this however you want.
const int32* scoreDetails = {}; // Extra values pertaining to the score.
SteamLeaderboard_t leaderboard; // Handle for the leaderboard to upload the score to...
SteamAPICall_t result = UploadLeaderboardScore( leaderboard, ISteamUserStats::k_ELeaderboardUploadScoreMethodForceUpdate, 100, scoreDetails, int 0);
STEAM_CALLBACK(MyGameClass, OnLeaderboardScoreUploaded, LeaderboardScoreUploaded_t);
// Handle errors in the OnLeaderboardScoreUploaded callback function.
```

### XBOX Game Development Kit(GDK)(基于事件)

对于 XBOX Game Development Kit(GDK)的基于事件 stats,你只需将“行走英尺”的差值传给 [`XblEventsWriteInGameEvent`](/reference/live/xsapi-c/events_c/events_c_members),因为我们已经指定:在收到事件时对该 stat 做增量更新,并在 measurements JSON 字符串中指定了该 `diff` 值的位置,如下所示。

```cpp theme={null}
// Assume that this holds the feet that the user has traveled since the last update.
int diffFeetTraveled; 
std::stringstream ss;
ss << "{ \"feet\": " << diffFeetTraveled << " }";
std::string measurements = ss.str();
HRESULT hr = XblEventsWriteInGameEvent(
    m_xboxLiveContext,
    "FeetTraveled",
    "",
    measurements.c_str()
);
```

更多信息参见 [写入事件以驱动基于事件的 Stat](/services/xbox-services/player-data/stats-leaderboards/index)。

## 获取全球排行榜

在 Steamworks SDK 中,可通过调用 `ISteamUserStats::GetLeaderboardEntries`,然后在 `callback` 中调用 `ISteamUserStats::GetDownloadedLeaderboardEntry` 获取全球排行榜的每条条目(“行”)。

### Steamworks

```cpp theme={null}
class MyGameClass
{
    void OnFindLeaderboardCompleted(LeaderboardFindResult_t *callback);
    SteamAPICall_t m_getLoaderboardCall;
    SteamAPICall_t m_getEntriesCall;
};

m_getLoaderboardCall = SteamUserStats()->FindLeaderboard(leaderboardName);
STEAM_CALLBACK(MyGameClass, OnFindLeaderboardCompleted, LeaderboardFindResult_t);

// ...

void MyGameClass::OnFindLeaderboardCompleted(LeaderboardFindResult_t *callback)
{
    if (callback->bLeaderboardFound)
    {
        auto handle = callback->m_hSteamLeaderboard;
        const char *leaderboardName = SteamUserStats()->GetLeaderboardName(handle);
        m_getEntriesCall = SteamUserStats()->GetLeaderboardEntries(handle, ELeaderboardDataRequest::k_ELeaderboardDataRequestGlobal, 0, 100);
        STEAM_CALLBACK(MyGameClass, OnLeaderboardScoresDownloaded, LeaderboardScoresDownloaded_t);
    }
    else
    {
        // Leaderboard wasn't found.
    }

void MyGameClass::OnLeaderboardScoresDownloaded(LeaderboardScoresDownloaded_t *callback)
{
    int numScores = callback->m_cEntryCount;
    for (int i = 0; i < numScores; i++)
    {
        LeaderboardEntry_t entry;
        bool result = SteamUserStats()->GetDownloadedLeaderboardEntry(callback->m_hSteamLeaderboardEntries, i, &entry, nullptr, 0);
        if (!result)
        {
            // Handle error.
        }
        CSteamID userId = entry.m_steamIDUser;
        int32 rank = entry.m_nglobalRank;
        int32 score = entry.m_nScore;
        // Do something with the data. 
    }
}
```

### XBOX Game Development Kit(GDK)(基于事件)

XBOX Game Development Kit(GDK)API 遵循类似模式。构建查询,指定要哪张排行榜、覆盖的范围、排序方式、是否包含额外字段等。使用查询调用 [`XblLeaderboardGetLeaderboardAsync`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) 后,可以使用 [`XblLeaderboardGetLeaderboardResultSize`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) 与 [`XblLeaderboardGetLeaderboardResult`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) 将结果填入指定缓冲区并遍历,如以下代码示例所示。

```cpp theme={null}
auto asyncBlock = std::make_unique<XAsyncBlock>();
asyncBlock->queue = m_taskQueue;
asyncBlock->context = nullptr;
asyncBlock->callback = [](XAsyncBlock* asyncBlock)
{
    std::unique_ptr<XAsyncBlock> asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock*
    size_t resultSize;
    std::vector<uint8_t> leaderboardBuffer;
    HRESULT hr = XblLeaderboardGetLeaderboardResultSize(asyncBlock, &resultSize);

    if (SUCCEEDED(hr))
    {
        leaderboardBuffer.resize(resultSize);
        XblLeaderboardResult* leaderboard{};

        hr = XblLeaderboardGetLeaderboardResult(asyncBlock, resultSize, leaderboardBuffer.data(), &leaderboard, nullptr);

        if (SUCCEEDED(hr))
        {
            // Use XblLeaderboardResult in result.
            for (int row = 0; row < leaderboard->rowsCount; row++)
            {
                uint64_t xuid = leaderboard->rows[row].xboxUserId;
                uint32_t rank = leaderboard->rows[row].rank;
                const char** values = leaderboard->rows[row].columnValues;
                // Do something with the data.
            }
        }
    }
};

XblLeaderboardQuery leaderboardQuery = {}; 
strcpy(&leaderboardQuery.scid[0], m_scid.c_str());
leaderboardQuery.leaderboardName = leaderboardName.c_str(); 
leaderboardQuery.xboxUserId = 0;
leaderboardQuery.order = XblLeaderboardSortOrder::Descending;
leaderboardQuery.skipResultToRank = 0;
leaderboardQuery.maxItems = 100;
leaderboardQuery.statName = "MyStatName";
leaderboardQuery.socialGroup = XblLeaderboardQueryType::None;
// See the link as follows for more options in XblLeaderboardQuery.

HRESULT hr = XblLeaderboardGetLeaderboardAsync(
    xboxLiveContext,
    leaderboardQuery,
    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();
}
```

更多信息参见:

* [基于事件排行榜的示例代码](/services/xbox-services/player-data/stats-leaderboards/index)
* [XblLeaderboardQuery](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members)

## 获取社交排行榜

在 Steamworks 中,你可以使用 `ELeaderboardDataRequest::k_ELeaderboardDataRequestFriends` 枚举,将排行榜限定在当前用户的好友范围内。

在 XBOX Game Development Kit(GDK)中,这称为 *社交排行榜*。要从 XBOX 服务获取社交排行榜,在 [`XblLeaderboardQuery`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) 结构体中,将 `xboxIUserId` 设为当前登录 XBOX 服务用户的 XBOX 用户 ID(XUID),将 `leaderboardName` 设为 `nullptr`,并将 `socialGroup` 设为 `XblSocialGroupType::People` 或 `XblSocialGroupType::Favorites`。然后按前述方式调用 `XblLeaderboardGetLeaderboardAsync`。

更多信息参见 [XblSocialGroupType](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members)。

## Featured Stats

除了用于生成类似 Steam 风格排行榜的常规用户 stats 外,XBOX 服务还有一个额外概念 —— *Featured Stats*。Featured Stats 会出现在 XBOX 生态中你游戏的多个位置,可以展示用户 stats 或已经生成的排行榜的值。你的游戏最多可以创建 20 个 Featured Stats。

有关 Featured Stats 及其添加方式的更多信息,参见 [Featured Stats 概览](/services/xbox-services/player-data/stats-leaderboards/index) 与 [基于事件的 Featured Stats 门户配置](/services/xbox-services/player-data/stats-leaderboards/index)。

## 速率限制

写事件时,注意不要过于频繁地调用 XBOX Services API(XSAPI),否则可能触发游戏的速率限制,导致意外行为并影响用户体验。请注意,这是 XBOX 服务侧的问题,在 Steam 上不存在 —— Steam 中写 stat 值直到游戏显式同步之前不会触发 API 调用。

对于频繁发生的事件,可以考虑批量 API 调用 —— 累计一定事件数或在特定时间间隔后再向服务器同步。

你可以使用 [XBOX Live Trace Analyzer(XblTraceAnalyzer.exe)](/tools/tools-services/live-trace-analyzer) 调试 API 调用。

有关 XBOX 服务速率限制的更多信息,参见 [细粒度速率限制](/services/xbox-services/develop/best-practices/live-fine-grained-rate-limiting)。


## Related topics

- [锦标赛与排行榜](/zh-CN/services/playfab/community/leaderboards/tournaments-leaderboards/index.md)
- [组排行榜](/zh-CN/services/playfab/community/leaderboards/group-leaderboards.md)
- [排行榜配额](/zh-CN/services/playfab/community/leaderboards/quota-leaderboards.md)
- [排行榜计量](/zh-CN/services/playfab/pricing/meters/leaderboard-meters.md)
- [好友排行榜](/zh-CN/services/playfab/community/leaderboards/tournaments-leaderboards/friends-leaderboards.md)
