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

第二步 - 封装统计
以下代码独立存在,可在适合时添加至游戏当中。 此类目前可正常运行,但如有更多需求,也可轻易扩展。 所有代码均直接来自于 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。