Steamworks 文獻庫
逐步教學:統計

簡介

這篇文件將會逐步帶領您在十分鐘以內在您的程式碼基底中用十行以下的程式,將非常基本的 Steam 統計整合至您的遊戲中。 Steamworks SDK 之中有一個名為 Spacewar 的應用程式範例,完整展示了所有 Steam 功能。如要親眼見識 Steam 的所有功能,這便會是您的第一站。 這篇教學將 Spacewar 和統計與成就 API 中的資訊簡化至 Steam 統計最基本必要的資料,讓教學盡量簡單明瞭。 統計和成就之間有很多重疊的部分,所以如果您要兩者一起整合,請注意很多呼叫都是通用的。

第一步 - 設定遊戲的統計

統計是專屬於各個應用程式的,必須前往後端的 Steamworks 應用程式管理員統計設定頁面進行設定。 以下是 Steamworks 範例應用程式 Spacewar 的統計列表:
spacewar_achievements.png

第二步 - 封裝統計作業

以下程式碼在不同遊戲中不會交互影響,只要您覺得合適,都能加進遊戲中。 此類別目前的狀態即可正常運作,但如有更多需求,也可輕易改寫。 所有的程式碼都是直接從 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 統計 API 的呼叫和建立所有 Steam 回呼
class CSteamStats { private: int64 m_iAppID; // 目前的 AppID Stat_t *m_pStats; // 統計資料 int m_iNumStats; // 統計數量 bool m_bInitialized; // 是否已呼叫 RequestStats 並收到了回呼? 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()

參數 - 無
傳回 - 代表呼叫是否成功的 Bool。 如果呼叫失敗,很可能是因為 Steam 尚未初始化。 進行呼叫時請確定 Steam 用戶端是開啟的,而且已呼叫了 SteamAPI_Init
作用 - 這個方法基本上只是將發向 ISteamUserStats::RequestCurrentStats 的呼叫包裝了起來。這是一個不同步呼叫,用來向 Steam 要求目前使用者的統計與成就。 您必須先進行此呼叫,才能設定統計或成就。 第一次呼叫這個方法的時候是在建構函數裡面。 往後需要檢查更新統計與成就的時候可再呼叫此方法。
bool CSteamAchievements::RequestStats() { // 是否已載入 Steam? 若沒有,則無法取得統計 if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // 使用者是否已登入? 若沒有,則無法取得統計 if ( !SteamUser()->BLoggedOn() ) { return false; } // 要求使用者統計 return SteamUserStats()->RequestCurrentStats(); }

StoreStats()

參數 - 無
傳回 - 代表呼叫是否成功的 Bool。 如果呼叫失敗,很可能是因為 Steam 尚未初始化。 進行呼叫時請確定 Steam 用戶端是開啟的,而且已呼叫了 SteamAPI_Init
作用 - 基本上只是將發向 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 ) { // 可能會收到其他遊戲將送來的統計回呼,請忽略 if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Received stats and achievements from Steam\n" ); // 載入統計 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 ) { // 設置的一個或以上的統計打破了限制, 現已還原 // 並且須重新逐一查閱各個值,以保持同步 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 ); } } }

第三步 - 與遊戲整合

以下是您需要放入遊戲中適當位置的程式碼片段的完整列表。

定義和全域

以下包含了建立統計需要的資料、此遊戲的統計列表,和一個指向協助程式物件的指標。 請注意須與 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() 呼叫即可儲存。
... if (g_SteamStats) g_SteamStats->StoreStats(); ...

關閉

您很可能在自己的程式碼之中已經用過 SteamAPI_Shutdown 呼叫了。 這會關閉 Steam,在離開應用程式前必須先進行此呼叫。 最後我們需要刪除先前建立的協助程式物件。
... // 關閉 Steam SteamAPI_Shutdown(); // 刪除 SteamStats 物件 if (g_SteamStats) delete g_SteamStats; ...

第四步 - 測試和疑難排解

這個範例程式碼將把偵錯訊息輸出至偵錯主控台,幫助您了解哪些呼叫成功或失敗了。 以下是一些常見的錯誤訊息和修正方法:

This application has failed to start because steam_api.dll was not found. Re-installing the application may fix this problem.
確認 steam_api.dll 和執行檔是否在同一個路徑。

[S_API FAIL] SteamAPI_Init() failed; unable to locate a running instance of Steam, or a local steamclient.dll
很可能是因為您的 Steam 用戶端沒有在運作。 開啟並登入 Steam。

[S_API FAIL] SteamAPI_Init() failed; no appID found.
很可能是因為您的 steam_appid.txt 不在正確的位置上。 將它放入來源資料夾,並確認裡面有您的 appID。