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

はじめに

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

ステップ1-ゲームのデータの定義

データはアプリケーションごとにSteamworksアプリ管理のSteamworks管理>データ&実績タブから設定できます。 以下は、SteamworksのサンプルアプリであるSpacewarからのデータのリストです:
stats_spacewar.png

ステップ2-データ作業のカプセル化

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

ヘッダーファイル

はじめにSteamから受信したデータを保持する構造体を定義し、次にデータ型を便利な列挙型で定義し、そしてデータオブジェクト生成のためのマクロを提供します。 このデータは、データ設定ページ上に直接マップされます。
#define _STAT_ID( id,type,name ) { id, type, name, 0, 0, 0, 0 } enum EStatTypes { STAT_INT = 0, STAT_FLOAT = 1, STAT_AVGRATE = 2, }; struct Stat_t { int m_ID; EStatTypes m_eStatType; const char *m_pchStatName; int m_iValue; float m_flValue; float m_flAvgNumerator; float m_flAvgDenominator; };

次に、Steamのコールバックをすべて作成し、すべてのSteam Stats APIの呼び出しをラップするヘルパークラスを定義します。
class CSteamStats { private: int64 m_iAppID; // 現在のAppID Stat_t *m_pStats; // 統計データ int m_iNumStats; // 統計の数 bool m_bInitialized; // データをリクエストしコールバックを受け取り済み? public: CSteamStats(Stat_t *Stats, int NumStats); ~CSteamStats(); bool RequestStats(); bool StoreStats(); STEAM_CALLBACK( CSteamStats, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived ); STEAM_CALLBACK( CSteamStats, OnUserStatsStored, UserStatsStored_t, m_CallbackUserStatsStored ); };

コードファイル

コンストラクター

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

RequestStats()

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

StoreStats()

パラメーター -なし
戻り値 -呼び出しが成功したかどうかを表すブール値です。 呼び出しが失敗した場合、その原因としてSteamが初期化されなかった可能性が考えられます。 この呼び出しを試みる際には、Steamクライアントが開いていること、そして事前にSteamAPI_Initが呼び出されていることを確認してください。
動作 -このメソッドは基本的に、サーバー上の現在のユーザーのデータを要求するSteamへの非同期呼び出しであるISteamUserStats::StoreStatsへの呼び出しをラップします。 この呼び出しはユーザーのデータを更新する度に行われる必要があります。
bool CSteamStats::StoreStats() { if ( m_bInitialized ) { // データを読み込む for ( int iStat = 0; iStat < m_NumStats; ++iStat ) { Stat_t &stat = m_pStats[iStat]; switch (stat.m_eStatType) { case STAT_INT: SteamUserStats()->SetStat( stat.m_pchStatName, stat.m_iValue ); break; case STAT_FLOAT: SteamUserStats()->SetStat( stat.m_pchStatName, stat.m_flValue ); break; case STAT_AVGRATE: SteamUserStats()->UpdateAvgRateStat(stat.m_pchStatName, stat.m_flAvgNumerator, stat.m_flAvgDenominator ); // 平均化された結果が計算されます SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue ); break; default: break; } } return SteamUserStats()->StoreStats(); } }

OnUserStatsReceived()

パラメーター -なし
戻り値 -なし
動作 -このメソッドはデータをリクエストするたびに呼び出されるコールバックです。 データはRequestStats()を使用してリクエストされます。 このメソッドはSteamから返ってきた最新のデータを反映するために、メンバ変数のm_Statsを更新します。
void CSteamStats::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // we may get callbacks for other games' stats arriving, ignore them if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Received stats and achievements from Steam\n" ); // load stats for ( int iStat = 0; iStat < m_iNumStats; ++iStat ) { Stat_t &stat = m_Stats[iStat]; switch (stat.m_eStatType) { case STAT_INT: SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_iValue); break; case STAT_FLOAT: case STAT_AVGRATE: SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue); break; default: break; } } m_bInitialized = true; } else { char buffer[128]; _snprintf( buffer, 128, "RequestStats - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnUserStatsStored()

パラメーター -なし
戻り値 -なし
動作 -このメソッドはSteam上にデータを保存するたびに呼び出されるコールバックです。 設定しようとするデータのいずれかが制約に違反する場合、元の値に戻してその値をリロードします。
void CSteamStats::OnUserStatsStored( UserStatsStored_t *pCallback ) { // 他のゲームからの統計データのコールバックを受け取る場合は無視する if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "StoreStats - success\n" ); } else if ( k_EResultInvalidParam == pCallback->m_eResult ) { // 設定した1つ以上のデータが制約に違反しました。 それらは元の値に戻されましたが、 // 同期するためには、値の読み込みを繰り返す必要があります。 OutputDebugString( "StoreStats - some failed to validate\n" ); // 値を再度読み込むために、ここで疑似的にコールバックを呼び出します。 UserStatsReceived_t callback; callback.m_eResult = k_EResultOK; callback.m_nGameID = m_iAppID; OnUserStatsReceived( &callback ); } else { char buffer[128]; _snprintf( buffer, 128, "StoreStats - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

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

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

定義とグローバル

以下はデータを構築する際に必要なインクルードのリスト、ゲーム特有のデータ配列と、ヘルパーオブジェクトへのグローバルポインターです。 データがSteamworks上の管理ページ上のものと一致する点に留意してください。
... #include "steam_api.h" #include "isteamuserstats.h" #include "SteamStats.h" // データとその状態に関するデータを保持するデータ配列 Stat_t g_Stats[] = { _STAT_ID( 1, STAT_INT, "NumGames"), _STAT_ID( 2, STAT_INT, "NumWins"), _STAT_ID( 3, STAT_INT, "NumLosses"), _STAT_ID( 4, STAT_FLOAT, "FeetTraveled"), _STAT_ID( 5, STAT_AVGRATE, "AverageSpeed"), _STAT_ID( 7, STAT_FLOAT, "MaxFeetTraveled"), }; // データオブジェクトへのグローバルアクセス CSteamStats* g_SteamStats = NULL; ...

初期化

SteamAPI_Initへの呼び出しは、Steam全体を初期化するもので、何よりも先に実行する必要があります。 呼び出しが成功したら配列のサイズとともにデータ配列を渡すことで、ヘルパーオブジェクトが生成されます。
... // Steamを初期化 bool bRet = SteamAPI_Init(); // Steamが初期化に成功した場合、SteamStatsオブジェクトを作成 if (bRet) { g_SteamStats = new CSteamStats(g_Stats, 6); } ...

コールバックの処理

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

データの保存

データはStoreStats()を1度呼び出すだけで保存されます。
... if (g_SteamStats) g_SteamStats->StoreStats(); ...

シャットダウン

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

ステップ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番号があることを確認してください。