Steamworks ドキュメンテーション
ステップバイステップ:実績
概略
実績を使用することで、ゲーム内で一定のマイルストーンに達成したり、特定の行動をとったりしたプレイヤーへの報酬を与えられます。
統合のレベル
10行以下のコードで10分以内に可能。 Steamworks SDK統合が必須。

はじめに

実績は、プレイヤーがゲーム内で特定の行動を取ることや、マイルストーンの達成を奨励し、報酬を与える方法として使用できます。 キル数、走行距離、開いた宝箱や、ゲーム内での他の一般的なアクションを記録するためによく使用されます。 また、プレイヤーがゲームのさまざまなプレイ方法を発見するのにも役立ちます。 解除された実績はプレイヤーのウィンドウの隅に表示され、そのプレイヤーの実績ページ内に記録されます。

技術概要

以下のステップバイステップガイドでは、あなたのアプリケーションのコードベースに10分以内に10行以下のコードで非常に基本的なSteamの実績を実装する方法を解説します。 Steamworks SDK内のSpacewarは、様々なSteam機能を含む非常に役立つサンプルアプリケーションです。Steamのすべての機能の動作を確認するためにも、ぜひ最初にご覧ください。 このチュートリアルでは、Spacewarやデータと実績のAPI情報の中から、Steamのデータを理解するのに必要な分だけの情報に絞り込んで説明します。 実績とデータには重複するものが多数あるため、両方を実装する場合、整理可能な呼び出しが多数存在する点にご注意ください。

ステップ1-ゲームの実績の定義

実績はアプリケーションごとにSteamworksアプリ管理の実績の設定ページで設定できます。 以下は、SteamworksのサンプルアプリSpacewarからの実績リストです:

spacewar_achievement_examplescreenshot.jpg

ステップ2-実績のカプセル化

以下のコードはゲームから独立したもので、必要に応じてゲームに追加することができます。 クラスはこのままでも機能しますが、必要であれば簡単に拡張することも可能です。 これらのコードはすべて、Spacewarサンプルファイル、StatsAndAchievements.cpp/hからの抜粋です。

ヘッダーファイル

最初に、Steamから受け取った実績データを保存する構造体を定義し、次にそのタイプのオブジェクトを作成するためのマクロを提供します。 このデータは、実績の設定ページに直接マップされます。
#define _ACH_ID( id, name ) { id, #id, name, "", 0, 0 } struct Achievement_t { int m_eAchievementID; const char *m_pchAchievementID; char m_rgchName[128]; char m_rgchDescription[256]; bool m_bAchieved; int m_iIconImage; };

次に、Steamのコールバックをすべて作成し、すべてのSteam Stats APIの呼び出しをラップするヘルパークラスを定義します。
class CSteamAchievements { private: int64 m_iAppID; // 現在のAppID Achievement_t *m_pAchievements; // 実績データ int m_iNumAchievements; // 実績の数 bool m_bInitialized; // データをリクエストし、コールバック受け取り済み? public: CSteamAchievements(Achievement_t *Achievements, int NumAchievements); ~CSteamAchievements(); bool RequestStats(); bool SetAchievement(const char* ID); STEAM_CALLBACK( CSteamAchievements, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived ); STEAM_CALLBACK( CSteamAchievements, OnUserStatsStored, UserStatsStored_t, m_CallbackUserStatsStored ); STEAM_CALLBACK( CSteamAchievements, OnAchievementStored, UserAchievementStored_t, m_CallbackAchievementStored ); };

コードファイル

コンストラクター

パラメーター -コンストラクターは配列の長さと共に実績の配列へのポインターを受け取ります。 この配列のフォーマットは後ほどメインのゲームコードで説明します。
戻り値 -なし
動作 -コンストラクターは、いくつかのメンバーを初期化し、また現在動作中のAppIDを取得します。 さらに、Steamへの非同期呼び出しを処理するためのコールバックメソッドもフックします。 最後に、現在のユーザーのデータと実績を取得するためにRequestStats()の最初の呼び出しを行います。
CSteamAchievements::CSteamAchievements(Achievement_t *Achievements, int NumAchievements): m_iAppID( 0 ), m_bInitialized( false ), m_CallbackUserStatsReceived( this, &CSteamAchievements::OnUserStatsReceived ), m_CallbackUserStatsStored( this, &CSteamAchievements::OnUserStatsStored ), m_CallbackAchievementStored( this, &CSteamAchievements::OnAchievementStored ) { m_iAppID = SteamUtils()->GetAppID(); m_pAchievements = Achievements; m_iNumAchievements = NumAchievements; RequestStats(); }

RequestStats()

パラメーター -なし
戻り値 -呼び出しが成功したかどうかを表すブール値です。 呼び出しが失敗した場合、Steamが初期化されなかったことが考えられます。 この呼び出しを試みる際には、Steamクライアントが開いていること、そして事前にSteamAPI_Initが呼び出されたことを確認してください。
動作 -このメソッドは基本的に、現在のユーザーのデータと実績を要求するSteamへの非同期呼び出しであるISteamUserStats::RequestCurrentStatsへの呼び出しをラップします。 この呼び出しは、データ、または実績の設定前に実行される必要があります。 このメソッドへの初期呼び出しはコンストラクター内で行います。 その後、更新されたデータまたは実績を確認したい場合は、いつでも呼び出し可能です。
bool CSteamAchievements::RequestStats() { // Steamを読み込み済み? 読み込み済みではない場合、データを取得できません。 if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // ユーザーはログイン済み? ログイン済みではない場合、データを取得できません。 if ( !SteamUser()->BLoggedOn() ) { return false; } // ユーザーデータを要求。 return SteamUserStats()->RequestCurrentStats(); }

SetAchievement()

パラメーター -設定しておきたい実績の文字列識別子(例: "ACH_WIN_ONE_GAME")
戻り値 -呼び出しが成功したかどうかを表すブール値です。 呼び出しが失敗した場合の原因は、Steamが初期化されていないか、初期呼び出しからのRequestStatsに対するコールバックがまだ処理されていないかのいずれかです。 コールバックを受け取るまで、実績は設定できません。
動作 -このメソッドは実績を達成済みと設定し、その結果をSteamに送信します。 実績の設定は何度でもできるため、未設定の実績があっても心配の必要はありません。 これは次の2つのコールバックをトリガーする非同期式の呼び出しです:OnUserStatsStored()OnAchievementStored()
bool CSteamAchievements::SetAchievement(const char* ID) { // Steamからのコールバックを受け取り済み? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // 受け取っていない場合、実績をまだ設定できません return false; }

OnUserStatsReceived()

パラメーター -なし
戻り値 -なし
動作 -このメソッドはデータをリクエストするたびに呼び出されるコールバックです。 データと実績はRequestStats()を使用してリクエストされます。 このメソッドはメンバー変数のm_pAchievementsを更新して、Steamから返された最新のデータと実績を反映します。
void CSteamAchievements::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // 他のゲームからの統計データのコールバックを受け取る場合は無視する if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString("Received stats and achievements from Steam\n"); m_bInitialized = true; // 実績を読み込む for ( int iAch = 0; iAch < m_iNumAchievements; ++iAch ) { Achievement_t &ach = m_pAchievements[iAch]; SteamUserStats()->GetAchievement(ach.m_pchAchievementID, &ach.m_bAchieved); _snprintf( ach.m_rgchName, sizeof(ach.m_rgchName), "%s", SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID, "name")); _snprintf( ach.m_rgchDescription, sizeof(ach.m_rgchDescription), "%s", SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID, "desc")); } } else { char buffer[128]; _snprintf( buffer, 128, "RequestStats - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnUserStatsStored()

パラメーター -なし
戻り値 -なし
動作 -このメソッドはSteam上にデータを保存するたびに呼び出されるコールバックです。
void CSteamAchievements::OnUserStatsStored( UserStatsStored_t *pCallback ) { // 他のゲームからの統計データのコールバックを受け取る場合は無視する if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Stored stats for Steam\n" ); } else { char buffer[128]; _snprintf( buffer, 128, "StatsStored - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnAchievementStored()

パラメーター -なし
戻り値 -なし
動作 -このメソッドはSteam上に実績が正しく保存されるたびに呼び出されるコールバックです。
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // 他のゲームからの統計データのコールバックを受け取る場合は無視する if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Stored Achievement for Steam\n" ); } }

ステップ3-ゲームへの統合

以下はあなたのゲームの適切な位置に埋め込む必要があるコードスニペットの全リストです。

定義とグローバル

以下は実績を構築する際に必要なインクルードのリストであり、ゲーム特有の実績の列挙と、ヘルパーオブジェクトへのグローバルポインターです。 実績がSteamworks上の管理ページ上のものと一致する点に留意してください。
... #include "steam_api.h" // 実績の定義 enum EAchievements { ACH_WIN_ONE_GAME = 0, ACH_WIN_100_GAMES = 1, ACH_TRAVEL_FAR_ACCUM = 2, ACH_TRAVEL_FAR_SINGLE = 3, }; // 実績とそれらの状態のデータを保持する実績の配列 Achievement_t g_Achievements[] = { _ACH_ID( ACH_WIN_ONE_GAME, "Winner" ), _ACH_ID( ACH_WIN_100_GAMES, "Champion" ), _ACH_ID( ACH_TRAVEL_FAR_ACCUM, "Interstellar" ), _ACH_ID( ACH_TRAVEL_FAR_SINGLE, "Orbiter" ), }; // 実績オブジェクトへのグローバルアクセス CSteamAchievements* g_SteamAchievements = NULL; ...

初期化

SteamAPI_Initへの呼び出しは、Steam全体を初期化するもので、一番最初に呼び出される必要があります。 呼び出しが成功したら配列のサイズとともに実績の配列を渡すことで、ヘルパーオブジェクトが生成されます。
... // Steamを初期化 bool bRet = SteamAPI_Init(); // Steamが正常に初期化されていれば、SteamAchievementsオブジェクトを作成 if (bRet) { g_SteamAchievements = new CSteamAchievements(g_Achievements, 4); } ...

コールバックの処理

Steamのすべてのコールバックを確実に処理するために、新たなメッセージを定期的にチェックする必要があります。 これは、この呼び出しをゲームループに追加することで実現できます。
... SteamAPI_RunCallbacks(); ...

実績をトリガーする

実績のトリガーは、単一の呼び出しで実績の識別子を渡すだけで簡単に行えます。
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

シャットダウン

SteamAPI_Shutdownへの呼び出しは、すでにコード内に含まれていることでしょう。 これはSteamをシャットダウンするものであり、アプリケーションを閉じる前に呼び出す必要があります。 最後に、作成したヘルパーオブジェクトを削除します。
... // Steamをシャットダウン SteamAPI_Shutdown(); // SteamAchievementsオブジェクトを削除 if (g_SteamAchievements) delete g_SteamAchievements; ...

ステップ4-テストとトラブルシューティング


ゲームにコードを追加せずに、データまたは実績の設定や消去をするには、Steamクライアントコンソールを使用できます。 steam.exe を-consoleで実行し、以下のようにします:
  • achievement_clear <appid> <achievement name>
  • reset_all_stats <appid>