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:
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.