Documentazione di Steamworks
Tutorial sulle statistiche

Introduzione

Quella che segue è una guida rapida per aiutarti a integrare le statistiche di Steam più basilari nella tua applicazione in meno di 10 minuti e con meno di 10 righe nel tuo codice di base. L'SDK di Steamworks include un'ottima applicazione esemplificativa denominata Spacewar che mostra l'intera gamma delle funzionalità di Steam e consente di vederle in azione. In questo tutorial, le informazioni disponibili in Spacewar e nell'API delle statistiche e degli achievement vengono sintetizzate e ridotte al necessario per rendere il procedimento il più semplice possibile. Ti facciamo notare che statistiche e achievement presentano forti sovrapposizioni: se desideri integrare entrambi, molte delle chiamate all'API potranno essere accorpate.

Passaggio 1: definizione delle statistiche del gioco

Le statistiche sono diverse per ogni applicazione e possono essere configurate nella pagina di configurazione delle statistiche, disponibile nel back-end dell'amministrazione dell'applicazione su Steamworks. Di seguito è illustrato l'elenco delle statistiche di Spacewar, l'applicazione esemplificativa di Steamworks:
stats_spacewar.png

Passaggio 2: incapsulamento delle statistiche

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

File di intestazione

In primo luogo, definiamo una struttura che conservi i dati delle statistiche ricevuti da Steam e un'enumerazione con tutti i tipi di statistica, dopodiché specifichiamo una macro per la creazione di oggetti di questo tipo. Questi dati corrispondono esattamente a quelli nella pagina di configurazione delle statistiche.
#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; };

Definiamo quindi una classe helper in grado di incapsulare tutte le chiamate all'API delle statistiche di Steam e di creare tutte le callback di Steam.
class CSteamStats { private: int64 m_iAppID; // Il nostro appID attuale Stat_t *m_pStats; // I dati delle statistiche int m_iNumStats; // Il numero di statistiche bool m_bInitialized; // Abbiamo chiamato RequestStats e ricevuto la 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 ); };

File del codice

Costruttore

Parametri: il costruttore riceve un puntatore a un array di statistiche e la lunghezza dell'array stesso. Il formato di questo array verrà trattato in seguito nel codice principale del gioco.
Restituisce: nessun valore.
Funzione: il costruttore inizializza alcuni membri e ottiene l'appID attualmente in esecuzione. Inoltre, associa i metodi di callback per gestire le chiamate asincrone effettuate a Steam. Infine, effettua una chiamata iniziale a RequestStats() per ottenere statistiche e achievement dell'utente attuale.
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()

Parametri: nessuno.
Restituisce: una variabile booleana indicante la riuscita o il fallimento della chiamata. Se la chiamata non è riuscita, molto probabilmente Steam non è stato inizializzato. Assicurati di aprire un client di Steam prima di effettuare la chiamata e di aver precedentemente chiamato la funzione SteamAPI_Init.
Funzione: questo metodo include una chiamata asincrona a ISteamUserStats::RequestCurrentStats per richiedere le statistiche dell'utente attuale a Steam. È 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 CSteamStats::RequestStats() { // Steam è avviato? In caso di risposta negativa, non sarà possibile ottenere le statistiche. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // L'utente ha effettuato l'accesso? In caso di risposta negativa, non sarà possibile ottenere le statistiche. if ( !SteamUser()->BLoggedOn() ) { return false; } // Richiedi le statistiche dell'utente. return SteamUserStats()->RequestCurrentStats(); }

StoreStats()

Parametri: nessuno.
Restituisce: una variabile booleana indicante la riuscita o il fallimento della chiamata. Se la chiamata non è riuscita, molto probabilmente Steam non è stato inizializzato. Assicurati di aprire un client di Steam prima di effettuare la chiamata e di aver precedentemente chiamato la funzione SteamAPI_Init.
Funzione: questo metodo include una chiamata asincrona a ISteamUserStats::StoreStats per salvare le statistiche dell'utente attuale sul server. Questa chiamata deve essere effettuata ogni volta che si desiderano aggiornare le statistiche dell'utente.
bool CSteamStats::StoreStats() { if ( m_bInitialized ) { // Carica le statistiche 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 ); // Qui viene già calcolata la media dei risultati SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue ); break; default: break; } } return SteamUserStats()->StoreStats(); } }

OnUserStatsReceived()

Parametri: nessuno.
Restituisce: nessun valore.
Funzione: questo metodo è una callback chiamata a ogni tentativo di richiesta delle statistiche. Le statistiche vengono richieste utilizzando RequestStats(). Il metodo aggiorna la variabile membro m_Stats affinché rispecchi i dati più recenti delle statistiche di Steam.
void CSteamStats::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // Ignoriamo eventuali callback in arrivo per le statistiche di altri giochi if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Statistiche e achievement ricevuti da Steam\n" ); // Carica le statistiche 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: errore, %d\n", pCallback->m_eResult ); OutputDebugString (buffer); } } }

OnUserStatsStored()

Parametri: nessuno.
Restituisce: nessun valore.
Funzione: questo metodo è una callback chiamata a ogni tentativo di salvataggio delle statistiche su Steam. Se una delle statistiche che abbiamo cercato di impostare ha violato un vincolo, questa verrà riportata al valore precedente e dovrà essere caricata nuovamente.
void CSteamStats::OnUserStatsStored( UserStatsStored_t *pCallback ) { // Ignoriamo eventuali callback in arrivo per le statistiche di altri giochi if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "StoreStats: completata\n" ); } else if ( k_EResultInvalidParam == pCallback->m_eResult ) { // Una o più statistiche che abbiamo impostato hanno violato un vincolo. Tali statistiche sono state ripristinate // e dobbiamo effettuare un'altra iterazione sui valori per garantire la sincronizzazione. OutputDebugString( "StoreStats: qualche errore di validazione\n" ); // Simula una callback per ricaricare i valori. UserStatsReceived_t callback; callback.m_eResult = k_EResultOK; callback.m_nGameID = m_iAppID; OnUserStatsReceived( &callback ); } else { char buffer[128]; _snprintf( buffer, 128, "StoreStats: errore, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

Passaggio 3: integrazione nel gioco

Di seguito è riportato l'elenco completo dei frammenti di codice che è necessario integrare al posto giusto all'interno del tuo gioco.

Definizioni e variabili globali

Di seguito elenchiamo le direttive "include" necessarie per lavorare con le statistiche, un array con le statistiche specifiche del gioco e un puntatore globale al nostro oggetto helper. Ricorda che le statistiche corrisponderanno a quelle definite nella pagina di amministrazione su Steamworks.
... #include "steam_api.h" #include "isteamuserstats.h" #include "SteamStats.h" // Un array che conterrà tutti i dati sulle statistiche e sul loro stato 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"), }; // Accesso globale all'oggetto delle statistiche CSteamStats* g_SteamStats = 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 l'array delle statistiche e le dimensioni dell'array stesso.
... // Inizializza Steam bool bRet = SteamAPI_Init(); // Crea l'oggetto SteamStats in caso di inizializzazione corretta di Steam if (bRet) { g_SteamStats = new CSteamStats(g_Stats, 6); } ...

Elaborazione delle callback

Per garantire l'elaborazione di tutte le callback di Steam, è necessario verificare regolarmente se ci sono nuovi messaggi. Per farlo, aggiungiamo la seguente chiamata al ciclo del gioco.
... SteamAPI_RunCallbacks(); ...

Salvataggio delle statistiche

Le statistiche vengono salvate effettuando una singola chiamata a StoreStats().
... if (g_SteamStats) g_SteamStats->StoreStats(); ...

Arresto

Probabilmente la chiamata a SteamAPI_Shutdown è già presente nel tuo codice. Tale funzione consente l'arresto di Steam e deve essere eseguita prima di uscire dall'applicazione. Eliminiamo infine l'oggetto helper creato in precedenza.
... // Arresta Steam SteamAPI_Shutdown(); // Elimina l'oggetto SteamStats if (g_SteamStats) delete g_SteamStats; ...

Passaggio 4: test e risoluzione dei problemi

Questo codice di esempio scrive nella console di debug una serie di informazioni utili per capire quali chiamate hanno esito positivo e quali non vanno a buon fine. Di seguito riportiamo alcuni messaggi di errore e come correggerli:

This application has failed to start because steam_api.dll was not found. Re-installing the application may fix this problem.
Assicurati che steam_api.dll si trovi nella stessa cartella dell'eseguibile.

[S_API FAIL] SteamAPI_Init() failed; unable to locate a running instance of Steam, or a local steamclient.dll.
Probabilmente il client di Steam non è in esecuzione. Avvia Steam ed effettua l'accesso.

[S_API FAIL] SteamAPI_Init() failed; no appID found.
Probabilmente il file steam_appid.txt non è nella posizione corretta. Inseriscilo nella cartella di origine e assicurati che contenga il numero del tuo appID.