Steamworks 文献库
逐步指导:统计

简介

本文是一份快速指南,逐步指导您在 10 分钟内,对您的代码库添加 10 行以内的代码,将非常基础的 Steam 统计集成到您的应用程序中。 Steamworks SDK 中有一个名为 Spacewar 的绝佳应用程序实例,完整展示了所有 Steam 功能。若要全面了解 Steam 所有使用中的功能,请首先访问此处。 本教程将 Spacewar 和统计与成就 API 中的信息简化至 Steam 统计最基本必要的数据,让教程尽量简单明了。 请注意,统计和成就之间有很多重叠的部分,所以如果您要两者一起整合,请注意很多调用都可以合并使用。

第一步 - 定义游戏统计

统计因各应用程序而异,需要在后端的 Steamworks 应用程序管理的统计配置页面进行设置。 以下是 Steamworks 示例应用程序 Spacewar 的统计列表:
stats_spacewar.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 CSteamStats::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() 请求统计数据。 此方法将更新成员变量 m_Stats 以反映 Steam 返回的最新统计数据。
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 localsteamclient.dll ([S_API FAIL] SteamAPI_Init()失败;未找到运行的 Steam 实例或本地 steamclient.dll)
Steam 客户端很可能未在运行。 开启并登录 Steam。

[S_API FAIL] SteamAPI_Init() failed; no appID found.
steam_appid.txt 文件很可能未处于适当位置。 将它放入源文件夹,并确认里面有您的 appID。