Steamworks ドキュメンテーション
ステップバイステップ:実績

はじめに

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

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

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

ステップ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()を使用してリクエストされます。 このメソッドはSteamから返ってきた最新のデータと実績を反映するために、メンバ変数のm_pAchievementsを更新します。
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(); ...

実績をトリガーする

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

シャットダウン

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

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

デバッグ情報をデバッグコンソールに出力するこのサンプルコードは、どの呼び出しが成功、または失敗するかを理解するのに役立ちます。 以下は典型的なエラーメッセージと修正例です。

This application has failed to start because steam_api.dll was not found. Re-installing the application may fix this problem.(steam_api.dllが見つからなかったのでこのアプリケーションの開始は失敗しました。アプリケーションの再インストールで問題が解決することがあります。)
steam_api.dllが実行ファイルと同じディレクトリーにあることを確認してください。

[S_API FAIL] SteamAPI_Init() failed; unable to locate a running instance of Steam, or a local steamclient.dll(実行中のSteamのインスタンス、またはローカルのsteamclient.dllの場所を特定できませんでした。)
Steamクライアントが実行されていない可能性があります。 Steamを起動してログインしてください。

[S_API FAIL] SteamAPI_Init() failed; no appID found.(appIDが見つかりません。)
steam_appid.txtファイルが所定の場所にない可能性があります。 同ファイルをソースフォルダーに配置して、その中にあなたのAppID番号があることを確認してください。