Introducción
La siguiente es una guía rápida paso a paso para integrar estadísticas muy básicas de Steam en las aplicaciones de los desarrolladores en menos de 10 minutos y con menos de 10 líneas de código integradas en el código base. El
SDK de Steamworks contiene una magnífica aplicación de ejemplo,
Spacewar], que no solo exhibe el espectro completo de funciones de Steam, sino que también debería ser la primera parada para todo desarrollador que desee verlas en acción. Este tutorial resume la información que se encuentra en Spacewar y en la API de estadísticas y logros, condensándola en lo que es estrictamente imprescindible para las estadísticas de Steam, sin mayores complicaciones. Ten en cuenta que hay una cantidad considerable de solapamiento entre estadísticas y logros, de forma que si se están integrando ambos, es necesario ser consciente de que pueden fusionarse multitud de llamadas.
Paso 1: Definir las estadísticas del juego
Las estadísticas son específicas de la aplicación y se configuran en la página
Configuración de estadísticas en el backend del administrador de la aplicación Steamworks. Esta es la lista de estadísticas de la aplicación de ejemplo Steamworks Spacewar:

Paso 2: Encapsular las estadísticas
El código que sigue no depende de ningún juego y los desarrolladores pueden agregarlo al suyo a su propia discreción. La clase es perfectamente funcional tal cual, pero se puede ampliar fácilmente para dar respuesta a cualquier necesidad a mayores. Todo este código se ha tomado directamente de los archivos de ejemplo de Spacewar
StatsAndAchievements.cpp/h
.
Archivo de encabezado
Primero definimos una estructura para contener los datos de estadísticas recibidos de Steam, definimos los tipos de estadísticas en un enum de utilidad y suministramos una macro para crear objetos de estadísticas. Estos datos se asignan directamente a lo que se encuentra en la página
Página de configuración de estadísticas.
#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;
};
A continuación, definimos una clase auxiliar que encapsulará todas las llamadas a la API de estadísticas de Steam, además de crear todas
las funciones callback de Steam.
class CSteamStats
{
private:
int64 m_iAppID; // Our current AppID
Stat_t *m_pStats; // Stats data
int m_iNumStats; // The number of Stats
bool m_bInitialized; // Have we called Request stats and received the callback?
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 );
};
Archivo de código
Constructor
Parámetros: El constructor lleva un puntero a una matriz de estadísticas junto con la longitud de la matriz. El formateado de ese vector será abordado más adelante, en el código principal del juego.
Devuelve: N/D
Lo que hace: el constructor inicializa un gran número de miembros, además de hacerse con el id. de aplicación con el que operamos actualmente. Además, enlaza los métodos de función callback para gestionar llamadas asíncronas a Steam. Finalmente, realiza una llamada inicial a
RequestStats()
para obtener estadísticas y logros para el usuario actual.
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()
Parámetros: No
Devuelve: un resultado booleano que representa si la llamada se completó. Si la llamada dio error, lo más probable es que no se haya inicializado Steam. Es necesario asegurarse de que se tiene un cliente de Steam abierto al intentar realizar esta llamada y de que previamente se ha llamado a
SteamAPI_Init.
Lo que hace: básicamente, este método realiza una llamada a
ISteamUserStats::RequestCurrentStats, que es una llamada asincrónica a Steam que pide las estadísticas y los logros del usuario actual. Es necesario que esta llamada se realice antes de poder establecer cualquier estadística o logro. La llamada inicial a este método se realiza en el constructor. Puede llamarse de nuevo a esta función en cualquier momento si se desea comprobar las estadísticas o logros actualizados.
bool CSteamStats::RequestStats()
{
// Is Steam loaded? If not we can't get stats.
if ( NULL == SteamUserStats() || NULL == SteamUser() )
{
return false;
}
// Is the user logged on? If not we can't get stats.
if ( !SteamUser()->BLoggedOn() )
{
return false;
}
// Request user stats.
return SteamUserStats()->RequestCurrentStats();
}
StoreStats()
Parámetros No
Devuelve: un resultado booleano que representa si la llamada se completó. Si la llamada dio error, lo más probable es que no se haya inicializado Steam. Es necesario asegurarse de que se tiene un cliente de Steam abierto al intentar realizar esta llamada y de que previamente se ha llamado a
SteamAPI_Init.
Lo que hace: básicamente, este método realiza una llamada a
ISteamUserStats::StoreStats, que es una llamada asincrónica a Steam que pide las estadísticas y los logros del usuario actual. Esta llamada debe realizarse siempre que se quieran actualizar las estadísticas del usuario.
bool CSteamStats::StoreStats()
{
if ( m_bInitialized )
{
// cargar estadísticas
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 );
// El resultado promedio se proporciona ya calculado
SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue );
break;
default:
break;
}
}
return SteamUserStats()->StoreStats();
}
}
OnUserStatsReceived()
Parámetros: N/D
Devuelve: Ninguna
Lo que hace: este método es una función callback que se realiza siempre que se intentan solicitar estadísticas. Las estadísticas se solicitan mediante el uso de
RequestStats()
. Este método actualiza la variable miembro m_Stats para reflejar los datos de las estadísticas más recientes devueltos por Steam.
void CSteamStats::OnUserStatsReceived( UserStatsReceived_t *pCallback )
{
// podemos empezar a recibir funciones callback para otros juegos, ignóralas
if ( m_iAppID == pCallback->m_nGameID )
{
if ( k_EResultOK == pCallback->m_eResult )
{
OutputDebugString( "Received stats and achievements from Steam\n" );
// load stats
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()
Parámetros: N/D
Devuelve: Ninguna
Lo que hace: este método es una función callback que se realiza siempre que se intentan almacenar estadísticas en Steam. Si cualquiera de las estadísticas que se intentan establecer rompe una limitación, se revierte a su antiguo valor, por lo que se vuelven a cargar estos valores.
void CSteamStats::OnUserStatsStored( UserStatsStored_t *pCallback )
{
// podemos empezar a recibir funciones callback para otros juegos, ignóralas
if ( m_iAppID == pCallback->m_nGameID )
{
if ( k_EResultOK == pCallback->m_eResult )
{
OutputDebugString( "StoreStats - success\n" );
}
else if ( k_EResultInvalidParam == pCallback->m_eResult )
{
// Una o más de las estadísticas establecidas rompió una restricción. Han sido canceladas,
// y ahora deberíamos reiterar los valores para mantenerlos sincronizados.
OutputDebugString( "StoreStats - some failed to validate\n" );
// Simula una función callback para volver a cargar los valores.
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 );
}
}
}
Paso 3: Integrar en el juego
La siguiente es una lista completa de segmentos de código que los desarrolladores necesitarán integrar en su juego en las ubicaciones adecuadas.
Definiciones y generalidades
La siguiente es la lista de inclusiones que se necesitan para trabajar con Stats, un vector para nuestras estadísticas de juego específicas y un puntero global a nuestro objeto auxiliar. Téngase en cuenta que las estadísticas se corresponden con las de la página del administrador de Steamworks.
...
#include "steam_api.h"
#include "isteamuserstats.h"
#include "SteamStats.h"
// El vector de estadísticas que almacenará datos sobre las estadísticas y su estado
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"),
};
// Acceso global al objeto Estadísticas (Stats)
CSteamStats* g_SteamStats = NULL;
...
Comienzo
La llamada a
SteamAPI_Init inicializa todo Steam y debe llamarse allí antes que nada. Si la llamada se completa, creamos el objeto auxiliar pasando el vector de estadísticas junto con el tamaño del mismo.
...
// Initialize Steam
bool bRet = SteamAPI_Init();
// Crear el objeto SteamStats si Steam se inicializó con éxito
if (bRet)
{
g_SteamStats = new CSteamStats(g_Stats, 6);
}
...
Procesar devoluciones de llamada
Para garantizar que procesamos todas las devoluciones de llamada de Steam, debemos obtener nuevos mensajes con regularidad. Para ello, agregamos esta llamada al bucle del juego.
...
SteamAPI_RunCallbacks();
...
Guardado de estadísticas
Las estadísticas se guardan haciendo una sola llamada a
StoreStats()
.
...
if (g_SteamStats)
g_SteamStats->StoreStats();
...
Apagar
La llamada a
SteamAPI_Shutdown es probablemente algo que ya tienes en tu código. La función cierra Steam y debe llamarse antes de que tu aplicación se cierre. Por último, se borra el objeto auxiliar que creamos.
...
// Shutdown Steam
SteamAPI_Shutdown();
// Delete the SteamStats object
if (g_SteamStats)
delete g_SteamStats;
...
Paso 4: Probar y solucionar problemas
Este código de muestra envía información a la consola de depuración que puede ayudar al desarrollador a entender qué llamadas están ejecutándose correctamente o están dando error. Los siguientes son mensajes de error típicos y sus soluciones:
No fue posible iniciar esta aplicación porque no se encontró steam_api.dll. Reinstalar la aplicación podría solucionar este problema.Asegúrate de que steam_api.dll esté en el mismo directorio que el ejecutable.
[S_API FAIL] SteamAPI_Init() falló; no se puede encontrar una instancia en ejecución de Steam o un steamclient.dll local.Lo más probable es que no se tenga el cliente de Steam en ejecución. Es necesario iniciar Steam e iniciar una sesión.
[S_API FAIL] SteamAPI_Init() falló; no se ha encontrado una appID.Probablemente, no tienes el archivo steam_appid.txt en la ubicación adecuada. Ubícalo en la carpeta de origen y asegúrate de que tenga el id. de aplicación.