Steamworks 文獻庫
逐步教學:成就
簡介
使用成就來獎勵達成某些里程碑,或以特定方式與您的遊戲互動的玩家。
整合程度
花費十分鐘,且少於十行程式碼。 需要 Steamworks SDK 整合。

簡介

將成就作為鼓勵玩家在遊戲內進行互動或達成里程碑的獎勵方式。 成就經常用來表揚擊殺次數、駕駛里程數、寶箱開啟數或其它遊戲內的常見行為, 也可用於協助玩家在您的遊戲中探索不同的遊玩方式。 成就解鎖後,將會在玩家視窗的角落彈出顯示,並在該玩家的成就頁面中標記。

技術總覽

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

第一步 - 定義遊戲的成就

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

spacewar_achievement_examplescreenshot.jpg

第二步 - 封裝成就作業

以下的程式碼皆不限用於哪個遊戲,您可放入自己遊戲中合適的地方。 此類別目前的狀態即可正常運作,但如有更多需求,也可輕易擴展。 所有的程式碼都是直接從 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_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" ); } }

第三步 - 與遊戲整合

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

定義和全域

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