Documentazione di Steamworks
Achievement: tutorial

Introduzione

Quella che segue è una breve guida per aiutarti a integrare gli achievement basilari di Steam nella tua applicazione, in meno di 10 minuti e integrando meno di 10 righe nel tuo codice di base. L'SDK di Steamworks include una fantastica applicazione demo denominata Spacewar, che mostra l'intera gamma di funzioni di Steam e consente di vederle in azione. In questo tutorial, le informazioni disponibili in Spacewar e quelle sull'API per le statistiche e gli achievement vengono sintetizzate e ridotte alle informazioni necessarie per rendere il procedimento più semplice possibile. Tieni presente che statistiche e achievement presentano un considerevole grado di sovrapposizione. Pertanto, se desideri integrare entrambi, potrai utilizzare molte delle stesse chiamate alle API.

Passaggio 1: definizione degli achievement del gioco

Gli achievement sono elementi specifici dell'applicazione e vengono impostati dalla pagina Configurazione achievement nel back-end Amministratore applicazione di Steamworks. Di seguito è illustrato l'elenco degli achievement dall'applicazione demo Spacewar di Steamworks:
spacewar_achievements.png

Passaggio 2: integrazione degli achievement

Il codice presentato di seguito non dipende dal gioco e può essere aggiunto al tuo titolo se lo ritieni opportuno. La classe è già completamente funzionale, ma può essere facilmente estesa per soddisfare qualsiasi ulteriore necessità. L'intero codice è estratto direttamente dai file di esempio di Spacewar StatsAndAchievements.cpp/h.

File di intestazione

In primo luogo, definiamo una struttura che conservi i dati degli achievement ricevuti da Steam e una macro per la creazione di oggetti di questo tipo. Questi dati vengono associati direttamente al contenuto della pagina Configurazione achievement.
#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 questo punto, definiamo una classe helper che raggruppi tutte le chiamate alle API delle statistiche di Steam e che crei anche tutte le richiamate di Steam.
class CSteamAchievements { private: int64 m_iAppID; // AppID attuale Achievement_t *m_pAchievements; // Dati degli achievement int m_iNumAchievements; // Numero di achievement bool m_bInitialized; // Request stats è stato chiamato e ha ricevuto la nuova chiamata? 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 ); };

File di codice

Costruttore

Parametri: il costruttore porta un puntatore su un array di achievement per tutta la lunghezza dell'array. L'ordine di questo array verrà trattato in seguito, quando affronteremo il codice del gioco principale.
Restituisce: non disponibile.
Funzione: il costruttore inizializza un numero di membri e rileva l'AppID attualmente in esecuzione. Inoltre, si aggancia ai metodi di richiamata per gestire le chiamate asincrone a Steam. Infine, effettua una chiamata iniziale a RequestStats() per ottenere statistiche e achievement dell'utente attuale.
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()

Parametri: nessuno.
Returns una variabile booleana indicante la riuscita o il fallimento della chiamata. Se la chiamata non è riuscita, molto probabilmente Steam non è inizializzato. Assicurati di avere un client di Steam aperto durante il tentativo di chiamata e di aver precedentemente effettuato la chiamata a SteamAPI_Init.
Funzione: questo metodo invia una chiamata a ISteamUserStats::RequestCurrentStats, ossia una chiamata asincrona a Steam, per richiedere statistiche e achievement dell'utente attuale. È necessario effettuare questa chiamata prima di poter impostare qualsiasi statistica o achievement. La chiamata iniziale a questo metodo viene effettuata attraverso il costruttore e può essere effettuata nuovamente ogni volta che si desidera controllare le statistiche o gli achievement aggiornati.
bool CSteamAchievements::RequestStats() { // Steam è avviato? In caso contrario, non è possibile ottenere statistiche. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // L'utente ha effettuato l'accesso? In caso contrario, non è possibile ottenere statistiche. if ( !SteamUser()->BLoggedOn() ) { return false; } // Richiedi statistiche dell'utente. return SteamUserStats()->RequestCurrentStats(); }

SetAchievement()

Parametri: l'identificatore di stringa dell'achievement che desideri impostare (ad esempio, "ACH_WIN_ONE_GAME").
Restituzioni: una variabile booleana indicante la riuscita o il fallimento della chiamata. Se la chiamata non è riuscita, Steam non è inizializzato oppure la richiamata non è ancora stata elaborata dalla chiamata iniziale a RequestStats. Non è possibile impostare gli achievement fino a quando non viene ricevuta questa richiamata.
Funzione: questo metodo imposta un dato achievement come completato e invia i risultati a Steam. È possibile impostare un dato achievement più volte in modo da non doversi preoccupare di impostare solo quelli ancora non configurati. Si tratta di una chiamata asincrona che attiverà due richiamate: OnUserStatsStored() e OnAchievementStored().
bool CSteamAchievements::SetAchievement(const char* ID) { // Abbiamo ricevuto una nuova chiamata da Steam? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // Se non l'abbiamo ricevuta, non possiamo ancora impostare gli achievement return false;

OnUserStatsReceived()

Parametri: non disponibile.
Restituzioni: nessuna.
Funzione: questo metodo consiste di una richiamata attivata a ogni tentativo di richiesta delle statistiche. Le statistiche e gli achievement vengono richiesti utilizzando RequestStats(). Il metodo aggiorna la variabile m_pAchievements del membro per restituire le ultime statistiche e i dati degli achievement ottenuti da Steam.
void CSteamAchievements::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // potremmo ricevere richiamate per statistiche in arrivo di altri giochi, ignorale if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString("Statistiche e achievement ricevuti da Steam\n"); m_bInitialized = true; // carica achievement 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()

Parametri: non disponibile.
Restituzioni: nessuna.
Funzione: questo metodo consiste di una richiamata attivata a ogni tentativo di archiviazione delle statistiche su Steam.
void CSteamAchievements::OnUserStatsStored( UserStatsStored_t *pCallback ) { // Potremmo ricevere richiamate per le statistiche in arrivo di altri giochi, ignorale if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Statistiche archiviate per Steam\n" ); } else { char buffer[128]; _snprintf( buffer, 128, "StatsStored - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnAchievementStored()

Parametri: non disponibile.
Restituzioni: nessuna.
Funzione: questo metodo consiste di una richiamata attivata ogni volta che gli achievement vengono correttamente archiviati su Steam.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // Potremmo ricevere richiamate per le statistiche in arrivo di altri giochi, ignorale if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Achievement archiviato per Steam\n" ); } }

Passaggio 3: integrazione nel gioco

Di seguito è riportato l'elenco completo dei frammenti di codice che è necessario integrare nelle posizioni corrette nel tuo gioco.

Definizioni e variabili globali

Di seguito sono elencate le inclusioni da compilare con gli achievement, un enum dei nostri achievement specifici del gioco e un puntatore globale al nostro oggetto helper. Tieni presente che gli achievement corrisponderanno a quelli impostati nella pagina Amministratore su Steamworks.
... #include "steam_api.h" // Definizione dei nostri achievement enum EAchievements { ACH_WIN_ONE_GAME = 0, ACH_WIN_100_GAMES = 1, ACH_TRAVEL_FAR_ACCUM = 2, ACH_TRAVEL_FAR_SINGLE = 3, }; // Matrice degli achievement con dati su di essi e sul loro stato Achievement_t g_Achievements[] = { _ACH_ID( ACH_WIN_ONE_GAME, "Vincitore" ), _ACH_ID( ACH_WIN_100_GAMES, "Campione" ), _ACH_ID( ACH_TRAVEL_FAR_ACCUM, "Interstellare" ), _ACH_ID( ACH_TRAVEL_FAR_SINGLE, "Orbiter" ), }; // Accesso globale all'oggetto degli achievement CSteamAchievements* g_SteamAchievements = NULL; ...

Inizializzazione

La chiamata a SteamAPI_Init inizializza Steam e deve essere effettuata prima di qualsiasi altra operazione. Se questa chiamata va a buon fine, creiamo l'oggetto helper passando nell'array degli achievement per tutta la lunghezza dell'array.
... // Inizializza Steam bool bRet = SteamAPI_Init(); // Crea l'oggetto SteamAchievements in caso di inizializzazione corretta di Steam if (bRet) { g_SteamAchievements = new CSteamAchievements(g_Achievements, 4); } ...

Elaborazione delle richiamate

Per garantire l'elaborazione di tutte le richiamate a Steam, è necessario inserire regolarmente nuovi messaggi. È possibile eseguire questa operazione aggiungendo questa chiamata al ciclo di gioco.
... SteamAPI_RunCallbacks(); ...

Attivazione degli achievement

Per l'attivazione di un achievement è sufficiente una singola chiamata che passi attraverso l'identificativo dell'achievement.
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

Arresto

La chiamata a SteamAPI_Shutdown probabilmente è già presente nel tuo codice. Consente l'arresto di Steam e deve essere eseguita prima di uscire dall'applicazione. Infine, eliminiamo l'oggetto helper creato.
... // Arresta Steam SteamAPI_Shutdown(); // Cancella l'oggetto SteamAchievements if (g_SteamAchievements) delete g_SteamAchievements; ...

Passo 4: test e risoluzione dei problemi

Questo codice di esempio invia le informazioni di debug alla console dedicata, che aiuta a capire quali chiamate sono riuscite e quali no. Di seguito sono riportati alcuni messaggi di errore e correzioni comuni:

Impossibile avviare l'applicazione in quanto il file steam_api.dll è mancante. Installare nuovamente l'applicazione per tentare di risolvere il problema.
Assicurarsi che steam_api.dll sia nella stessa cartella dell'eseguibile.

[S_API FAIL] SteamAPI_Init() non riuscita; impossibile individuare un'istanza di esecuzione di Steam o un file steamclient.dll locale.
Probabilmente il client di Steam non è in esecuzione. Avvia Steam ed effettua l'accesso.

[S_API FAIL] SteamAPI_Init() non riuscita; nessun AppID trovato.
Probabilmente il file steam_appid.txt non è nella posizione corretta. Assicurati che il file si trovi nella cartella di origine e che contenga il numero dell'AppID.