Documentación de Steamworks
Paso a paso: estadísticas

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: definiendo 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:
stats_spacewar.png

Paso 2: descripción de la integración de 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 [url = https:// /partner.steamgames.com/apps/stats/]Página de configuración de estadísticas[/url].
#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 devoluciones de llamada 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.
Devoluciones 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 devolución de llamada 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
Devoluciones: 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
Devoluciones 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
Devoluciones: Ninguna
Lo que hace: este método es una devolución de llamada 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 devoluciones de llamadas 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
Devoluciones: Ninguna
Lo que hace: este método es una devolución de llamada 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 devoluciones de llamadas 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 devolución de llamada 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: integración 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; ...

Inicialización

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: testeo y solución de 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:

Esta aplicación no pudo iniciarse 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() ha fallado; 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() ha fallado; no se ha encontrado una appID.
Lo más probable es que no tenga el archivo steam_appid.txt en su lugar. Es necesario ubicarlo en la carpeta de origen y asegurarse de que esta contiene el id. de aplicación propio.