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