Documentación de Steamworks
Paso a paso: Logros
Resumen
Utiliza los logros para recompensar a los jugadores que alcancen un cierto hito o interactúen de forma específica con tu juego.
Nivel de integración
10 minutos y menos de 10 líneas de código. Requiere integrar el SDK de Steamworks.

Introducción

Los logros se pueden utilizar para alentar y recompensar las interacciones de los jugadores y las metas dentro de tu juego. A menudo se usan para marcar el número de víctimas, kilómetros recorridos, cofres abiertos u otras acciones comunes en tu juego. Pero también se pueden utilizar para ayudar a los jugadores a descubrir diferentes formas de jugar tu juego. Cuando se desbloquean, estos logros aparecen en la esquina de la ventana del jugador y marcados en la página de logros de ese jugador.

Resumen técnico

La siguiente es una guía rápida paso a paso para integrar logros muy básicos de Steam en tu aplicación en menos de 10 minutos y con menos de 10 líneas de código integradas en tu código base. El SDK de Steamworks contiene una magnífica aplicación de ejemplo, Spacewar], que no solo muestra el espectro completo de funciones de Steam, sino que también debería ser tu primera parada para ver todas las características de Steam 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 superposición entre estadísticas y logros, de forma que si se están integrando ambos, es necesario considerar que muchas llamadas pueden fusionarse.

Paso 1: Definir los logros del juego

Los logros son específicos de la aplicación y se configuran en la página Configuración de logros en el backend del administrador de la aplicación Steamworks. La siguiente es una lista de logros de la aplicación de muestra de Steamworks, Spacewar:

spacewar_achievement_examplescreenshot.jpg

Paso 2: Encapsular el funcionamiento de los logros

El código siguiente 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 adicional. Todo este código se ha tomado directamente de los archivos de ejemplo de Spacewar StatsAndAchievements.cpp/h.

Archivo de encabezado

En primer lugar, definimos una estructura para contener los datos de logros recibidos de Steam y proporcionamos una macro para crear objetos de ese tipo. Estos datos se asignan directamente a lo que está disponible en la página Configuración de logros.
#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; };

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 CSteamAchievements { private: int64 m_iAppID; // Nuestro AppID actual Achievement_t *m_pAchievements; // Datos de logros int m_iNumAchievements; // El número de logros bool m_bInitialized; // ¿Hemos hecho la llamada de petición de estadísticas y recibido la función callback? 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 ); };

Archivo de código

Constructor

Parámetros: El constructor toma un puntero a un vector de logros junto con la longitud del mismo. 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.
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()

Parámetros: Ninguno.
Devuelve: un resultado booleano que representa si la llamada tuvo éxito o no. Si la llamada dio error, lo más probable es que no se haya inicializado Steam. Asegúrate de tener un cliente de Steam abierto cuando intentes hacer esta llamada y de que previamente se haya 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 CSteamAchievements::RequestStats() { // ¿Se ha cargado Steam? 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(); }

SetAchievement()

Parámetros: el identificador de cadena del logro que se quiere establecer (p. ej., "ACH_WIN_ONE_GAME")
Devuelve: un resultado booleano que representa si la llamada se completó. Si la llamada no se ha procesado con éxito, Steam no está inicializado o aún no ha procesado la devolución de la llamada inicial a RequestStats. No se pueden establecer logros hasta que se haya recibido esa función callback.
Lo que hace: este método establece un logro dado como desbloqueado y envía los resultados a Steam. Se puede establecer varias veces un mismo logro, así que no hay que preocuparse de configurar solo aquellos que todavía no se hayan configurado. Se trata de una llamada asincrónica que activará dos funciones callback: OnUserStatsStored() y OnAchievementStored().
bool CSteamAchievements::SetAchievement(const char* ID) { // ¿Ya hemos recibido una llamada de Steam? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // Si aún no se recibió, no podemos determinar los logros return false; }

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 y los logros se solicitan mediante el uso de RequestStats(). Este método actualiza la variable miembro "m_pAchievements," para reflejar los datos de las estadísticas y los logros más recientes devueltos por Steam.
void CSteamAchievements::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // podemos empezar a recibir funciones callback para otros juegos, ignóralas por ahora if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString("Received stats and achievements from Steam\n"); m_bInitialized = true; // cargar los logros 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()

Parámetros: N/D
Devuelve: 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.
void CSteamAchievements::OnUserStatsStored( UserStatsStored_t *pCallback ) { // podemos empezar a recibir devoluciones de llamadas para otros juegos, ignóralas por ahora 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()

Parámetros: N/D
Devuelve: Ninguna
Lo que hace: Este método es una devolución de llamada que se realiza siempre que se almacenen logros correctamente en Steam.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // podemos empezar a recibir devoluciones de llamadas para otros juegos, ignóralas por ahora if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Stored Achievement for Steam\n" ); } }

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 una lista completa de inclusiones que se necesitan para trabajar con logros, una enumeración de los logros específicos de nuestro juego y un puntero global a nuestro objeto auxiliar. Ten en cuenta que los logros se corresponden con los de la página del administrador de Steamworks.
... #include "steam_api.h" // Definir nuestros logros enum EAchievements { ACH_WIN_ONE_GAME = 0, ACH_WIN_100_GAMES = 1, ACH_TRAVEL_FAR_ACCUM = 2, ACH_TRAVEL_FAR_SINGLE = 3, }; // Vector de logros que incluirá los datos sobre los logros y su estado 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" ), }; // Acceso global al objeto de los logros CSteamAchievements* g_SteamAchievements = 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 logros junto con el tamaño del mismo.
... // Inicializar Steam bool bRet = SteamAPI_Init(); // Crear el objeto para los logros SteamAchievements si Steam se inició con éxito if (bRet) { g_SteamAchievements = new CSteamAchievements(g_Achievements, 4); } ...

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(); ...

Activar logros

Activar un logro es tan simple como ejecutar una única llamada con el identificador del logro.
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

Apagar

La llamada a SteamAPI_Shutdown es algo que probablemente ya tienes en tu código. Esta cierra Steam y debe llamarse antes de que la aplicación se cierre. Por último, borramos el objeto auxiliar que creamos.
... // Cerrar Steam SteamAPI_Shutdown(); // Borrar el objeto SteamAchievements if (g_SteamAchievements) delete g_SteamAchievements; ...

Paso 4: Pruebas y solución de problemas


Para configurar o borrar las estadísticas o un logro sin agregar código al juego, puedes usar la consola del cliente de Steam. Ejecuta steam.exe -console y luego escribe:
  • achievement_clear <appid> <achievement name>
  • reset_all_stats <appid>