Steamworks 文獻庫
逐步教學:成就

簡介

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

第一步 - 設定遊戲的成就

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

第二步 - 封裝成就作業

以下的程式碼皆不限用於哪個應用程式,您可放入自己遊戲中合適的地方。 此類別目前的狀態即可正常運作,但如有更多需求,也可輕易擴展。 所有的程式碼都是直接從 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 統計 API 的呼叫和建立所有 Steam 回呼
class CSteamAchievements { private: int64 m_iAppID; // 目前的 AppID Achievement_t *m_pAchievements; // 成就資料 int m_iNumAchievements; // 成就數量 bool m_bInitialized; // 是否已呼叫 RequestStats 並收到了回呼? 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()

參數 - 無
傳回 - 代表呼叫是否成功的 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(); }

SetAchievement()

參數 您希望進行設定的成就識別字串(例如 "ACH_WIN_ONE_GAME")
傳回 - 代表呼叫是否成功的 Bool 如果呼叫失敗,很可能是因為 Steam 尚未初始化,或是尚未處理一開始呼叫 RequestStats 時的回呼。 您在收到回呼之前無法設定任何成就
作用 - 將成就由未達成設定為已達成,並將結果傳送給 Steam。 您可重複設定同一項成就,便不用擔心只能設定尚未達成的成就。 這是個不同步的呼叫,會觸發這兩個回呼:OnUserStatsStored()OnAchievementStored()
bool CSteamAchievements::SetAchievement(const char* ID) { // 是否已收到 Steam 傳來的回呼? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // 若沒有,則尚無法設置成就 return false; }

OnUserStatsReceived()

參數 - 不適用
傳回 - 無
作用 - 每次嘗試要求統計時使用的回呼方法。 使用 RequestStats() 來要求統計與成就資料。 此方法將以 Steam 傳回的統計與成就最新資料,更新當中的成員參數 m_Achievements
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" ); } }

第三步 - 與遊戲整合

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

定義和全域

以下包含了建立成就需要的資料、針對此遊戲的成就列舉,和一個指向協助程式物件的全域指標。 請注意須與 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; ...

第四步 - 測試和疑難排解


您可以運用 Steam 用戶端主控台設定或清除統計資料或成就,而不用在遊戲中添加程式碼。 請以 steam.exe -console 指令來執行,然後:
  • achievement_clear <appid> <成就名稱>
  • reset_all_stats <appid>