> ## 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 と Achievements

> Steam の実績と統計を XBOX GDK のタイトル管理 API に移植し、ゲーム内で進行状況を追跡して、XBOX services へのチェックインを同期します。

XBOX Game Development Kit (GDK) と Steam では、実績には名前、説明、アイコン、現在のユーザーに対して解除されているかどうか、およびオプションで完了に向けた進行状況などの情報があります。Steam には、ユーザーの進行状況を事前に決められたターゲットと比較し、ターゲットを超えたときに実績を解除するためにゲームが使用する stats API もあります。

実際、XBOX Game Development Kit (GDK) には 2 つの実績 API があります: *event-based*（以前は *Achievements 2013* と呼ばれていました）と *title-managed*（以前は *Achievements 2017* と呼ばれていました）です。どちらのシステムも実行可能なオプションですが、Steam から来た場合には title-managed の実績 / 統計の方がはるかに馴染みがあり、ゲームに既に存在する可能性が高い既存のロジックとマッチします。このため、このトピックではその API を説明します。2 つの API の違いの詳細については、[Event-based と title-managed Achievements](/services/xbox-services/player-data/achievements/index) を参照してください。次のセクションでは、Steamworks API と XBOX Game Development Kit (GDK) の実績の違いを説明します。

タイトル管理実績 API を使用するには、XBOX services Context ハンドルを作成する必要があります。これは、[XblContextCreateHandle](/reference/live/xsapi-c/xbox_live_context_c/xbox_live_context_c_members) 関数と、[XUserAddResult API](/build/core-features/common/user/xuser_howto_best_practice_signing_in) からの `XUserHandle`（[User authentication and ownership](/build/steam-porting-guide/features/user-authentication-and-ownership) トピックから）で行えます。

## 実績の進行状況を追跡する

Steam API では、`ISteamUserStats::GetStat`/`ISteamUser::SetStat` API を使用して実績に向けたユーザーの進行状況を追跡でき、Steam Stats API に保存された値が現在の進行状況値の Source of Truth になります。

タイトル管理実績では、ゲームが単一の Source of Truth であり、ユーザーの現在の進行状況を自身で追跡する必要があります。API 呼び出しでクラウド上の現在の進行状況を更新することは一種の「チェックイン」として行われ、XBOX エコシステム全体のいくつかのサーフェスでユーザーの進行状況を表示するために使用されます。現在の進行状況値をどこに置くべきかについての特定のガイダンスはありませんが、多くのゲームはそれをセーブ ファイル（およびクラウド）に保存することを選択し、他のゲームは独自のバックエンド サービスに保存する場合があります。クラウドとゲームの内部統計管理システム間で値の同期を保つように必ず行ってください。

## すべての実績を列挙する

Steamworks API でユーザーの全統計を取得するには、通常 `ISteamUserStats::RequestUserStats` を呼び出してサーバーから最新の統計を取得します。対応するコールバックが発火した後、`ISteamUserStats::GetStat`/`ISteamUserStats::GetAchievement` 関数を使って各実績を API 名で反復処理し、返されたデータを使ってゲームの状態を初期化できます。

XBOX Game Development Kit (GDK) では、単に [XblAchievementsGetAchievementsForTitleIdAsync](/reference/live/xsapi-c/achievements_c/achievements_c_members) 関数を使い、次に [XblAchievementsResultGetAchievements](/reference/live/xsapi-c/achievements_c/achievements_c_members) の Out パラメーターとして提供される配列の各実績オブジェクトのデータを使ってゲームの状態を初期化できます。各実績は [XblAchievement](/reference/live/xsapi-c/achievements_c/achievements_c_members) 構造体として提供されます。含まれるデータの詳細については、[そのリファレンス トピック](/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()
{
    // stats/achievements を反復処理し、必要なゲーム データを初期化する。
    // m_achievementIds が実績の「API 名」の配列と仮定する。
    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);
        // 必要な実績の初期化を行う...
    }
}
```

### XBOX Game Development Kit (GDK)

```cpp theme={null}
auto asyncBlock = std::make_unique<XAsyncBlock>();
asyncBlock->context = nullptr;
asyncBlock->callback = [](XAsyncBlock* asyncBlock)
{
    // 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);
            }
            // このデータを使って実績を初期化する...
        }

        // ハンドルの使用が終わったら閉じる。これは実績リストをメモリから解放する。
        XblAchievementsResultCloseHandle(resultHandle);
        achievements = nullptr; 
        // 代わりに、ハンドルを閉じずに保存することもできる。  
        // ハンドルをコピーする必要がある場合は、XblAchievementsResultDuplicateHandle() を呼び出す
    }
};

// m_xblContext は適切に初期化された XBOX services Context ハンドルと仮定する。
uint64_t xuid;
XblContextGetXboxUserId(m_xblContext, &xuid);

HRESULT hr = XblAchievementsGetAchievementsForTitleIdAsync(
    m_xblContext,
    xuid, 
    m_titleId, // これはゲームのタイトル ID を保持する uint32_t と仮定する。
    XblAchievementType::Persistent, // achievementType: 通常は XblAchievementType::Persistent を使用する。
    false, //unlockedOnly: true の場合、解除された実績のみを返す。
    XblAchievementOrderBy::DefaultOrder, // orderBy: 入ってくる実績リストの並び順。 
    0, // skipItems: スキップする実績の数。 
    0, // maxItems: すべての実績を取得する / ページ分割を無効にするには 0 を設定する。
    asyncBlock.get()
);
if (SUCCEEDED(hr))
{
    // 呼び出しが成功したので、コールバックが所有権を引き継ぐため、XAsyncBlock* の std::unique_ptr の所有権を解放する。
    // 呼び出しが失敗した場合、std::unique_ptr は所有権を保持し、XAsyncBlock* を削除する。
    asyncBlock.release();
}
```

タイトル管理実績の詳細については、[Getting title-managed Achievements](/services/xbox-services/player-data/achievements/index) を参照してください。

## 実績の進行状況を更新し、実績を解除する

XBOX Game Development Kit (GDK) と Steam では、実績がいつ解除されるべきかはゲームのコードが決定します。XBOX Game Development Kit (GDK) では、実績の進行を 100 に設定すると解除されます。以下のセクションでは、両方の API で指定された実績のユーザーの進行状況を更新するために必要な手順を説明します。

以下は、両方の SDK で実装された `UpdateAchievement` メソッドのコード例です。実績 ID / API 名（および Steam の場合は stat 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) {
        // エラーを処理する。
    }

    result = SteamUserStats()->GetStat(statApiName, &value);
    if (!result) {
        // エラーを処理する。
    }

    SteamUserStats()->SetStat(statApiName, value + diff);
    
    if (!isCompleted && value + diff >= target)
    {
             // 実績を解除する。
        SteamUserStats()->SetAchievement(achievementApiName);
        SteamUserStats()->StoreStats();
    }
}

// 次のチェックポイントで...
if(!SteamUserStats()->StoreStats()) {
    // エラーを処理する。
}
```

### XBOX Game Development Kit (GDK)

```cpp theme={null}
void MyGame::UpdateAchievement(const std::string& achievementId, int diff, int target)
{
    // この関数は、保存されている場所から指定された実績の進行を返すと仮定する。
    auto previousValue = MyGetAchievementProgress(achievementId);
    // 進行状況を他の場所に保存している場合は、そこでも必ず更新すること！繰り返しになるが、これは 
    // 進行状況値を保存されている場所で更新するために書いた関数と仮定する。
    MySetAchievementProgress(previousValue + diff);
    // diff の値を追加した後にユーザーが到達したターゲットの割合を計算する。
    uint32_t percentComplete = (previousValue + diff) / target;

    auto async = std::make_unique<XAsyncBlock>();
    async->context = nullptr;
    async->callback = [](XAsyncBlock *async)
    {
        // XAsyncBlock* の所有権を引き継ぐ
        std::unique_ptr<XAsyncBlock> asyncBlockPtr{ async }; 

        HRESULT result = XAsyncGetStatus(async, true);

        if (SUCCEEDED(result))
        {
            // 成功！
        }
        else
        {
            if (result == HTTP_E_STATUS_NOT_MODIFIED)
            {
                // 実績はすでに完了しているか、以前により高い値に設定されていたため、
                // 変更されていない。
            }
            else
            {
                // エラーを処理する。
            }
        }
    };

    // m_xblContext は適切に初期化された XBOX services Context ハンドルと仮定する。
    uint64_t xuid;
    XblContextGetXboxUserId(m_xblContext, &xuid);
    HRESULT hr = XblAchievementsUpdateAchievementAsync(
        m_xblContext,
        xuid,
        achievementId.c_str(),
        percentComplete,
        async
    );

    if (SUCCEEDED(hr))
    {
        // 呼び出しが成功したので、コールバックが所有権を引き継ぐため、XAsyncBlock* の std::unique_ptr の所有権を解放する。
        // 呼び出しが失敗した場合、std::unique_ptr は所有権を保持し、XAsyncBlock* を削除する。
        asyncBlock.release();
    }
}
```

タイトル管理実績の詳細については、[Updating title-managed Achievements](/services/xbox-services/player-data/achievements/index) を参照してください。

## オフライン実績

XBOX の実績の解除にはネットワーク接続は不要です。ユーザーが XBOX network（XBOX Live としても知られる）に接続していない場合、追加のコードなしに後でサーバーで自動的に解除されます。

## 他のユーザーの実績を取得する

他のユーザーの実績を取得するには、実績を取得したいユーザーの XBOX User ID (XUID) を指定して [XblAchievementsGetAchievementsForTitleAsync](/reference/live/xsapi-c/achievements_c/achievements_c_members)/[XblAchievementsGetAchievementAsync](/reference/live/xsapi-c/achievements_c/achievements_c_members) 関数を呼び出すだけです。これは、Steam の `ISteamUser::RequestUserStats`、`ISteamUserStats::GetUserStat`、`ISteamUserStats::GetUserAchievement` 関数の使用に類似しています。

## 実績をリセットする

Steamworks API の `ISteamUserStats::ResetAllStats`（ユーザーのすべての stats と、指定されていれば実績の進行状況をリセットするために使用可能）とは異なり、XBOX Game Development Kit (GDK) には、実績の進行状況や解除ステータスをプログラム的にリセスする文書化された機能はありません。進行状況値 0 で [XblAchievementsUpdateAchievementAsync](/reference/live/xsapi-c/achievements_c/achievements_c_members) を呼び出そうとしても、進行状況値は変更されません。ただし、これを達成するためにテスト時に [Player Data Reset tool (XblPlayerDataReset.exe)](/tools/tools-services/live-player-data-reset) を使用できます。

## レート制限

実績の進行状況を更新するとき、XBOX Services API (XSAPI) をあまりに頻繁に呼び出さないように注意してください。呼び出しすぎると、ゲームがレート制限に達する可能性があります。これは、ユーザーにとって予期しない挙動と悪い体験を引き起こす可能性があります。

頻繁に更新される stats を追跡する実績については、ある数のイベントが発生した後に、または一定の時間間隔でサーバー上で更新するように、API 呼び出しをバッチ処理してみてください。

[XBOX services Trace Analyzer (XblTraceAnalyzer.exe)](/tools/tools-services/live-trace-analyzer) を使って API 呼び出しをデバッグできます。

XBOX services のレート制限の詳細については、[Fine-Grained Rate Limiting](/services/xbox-services/develop/best-practices/live-fine-grained-rate-limiting) を参照してください。


## Related topics

- [GDK と Steamworks の概念上の違い](/ja-jp/build/steam-porting-guide/conceptual-differences.md)
- [Steam 移植ガイド概要](/ja-jp/build/steam-porting-guide/overview.md)
- [イベント ベース Stats とタイトル管理型 Stats の比較](/ja-jp/services/xbox-services/player-data/stats-leaderboards/live-stats-eb-vs-tm.md)
- [トーナメントとリーダーボード](/ja-jp/services/playfab/community/leaderboards/tournaments-leaderboards/index.md)
- [PFPlatformSpecificServerAwardSteamAchievementAsync](/ja-jp/services/playfab/api-references/c/pfplatformspecific/functions/pfplatformspecificserverawardsteamachievementasync.md)
