Documentazione di Steamworks
Tutorial sugli achievement
In breve
Usa gli achievement come ricompensa per i giocatori che raggiungono certi traguardi o che interagiscono con il tuo gioco in modi particolari.
Livello d'integrazione
10 minuti e meno di 10 linee di codice. È necessaria l'integrazione con l'SDK di Steamworks.

Introduzione

Gli achievement possono essere usati per incoraggiare i giocatori a interagire con il tuo gioco e raggiungere determinati traguardi. Spesso vengono usati per segnalare il numero di uccisioni, i chilometri percorsi, i forzieri aperti o altre azioni tipiche nel tuo gioco, ma possono anche aiutare i giocatori a scoprire nuovi modi di giocare al tuo titolo. Quando sbloccati, gli achievement compariranno in un angolo della finestra di gioco e verranno registrati nella pagina degli achievement del giocatore.

Panoramica tecnica

Quella che segue è una guida rapida per aiutarti a integrare gli achievement 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 degli achievement del gioco

Gli achievement sono diversi per ogni applicazione e possono essere configurati nella pagina di configurazione degli achievement, disponibile nel back-end dell'amministrazione dell'applicazione su Steamworks. Di seguito è illustrato l'elenco degli achievement di Spacewar, l'applicazione esemplificativa di Steamworks:

spacewar_achievement_examplescreenshot.jpg

Passaggio 2: incapsulamento degli achievement

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 degli achievement ricevuti da Steam e una macro per la creazione di oggetti di questo tipo. Questi dati corrispondono esattamente a quelli nella pagina di configurazione degli 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; };

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 CSteamAchievements { private: int64 m_iAppID; // Il nostro appID attuale Achievement_t *m_pAchievements; // I dati degli achievement int m_iNumAchievements; // Il numero di achievement bool m_bInitialized; // Abbiamo chiamato RequestStats e ricevuto la 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 ); };

File del codice

Costruttore

Parametri: il costruttore riceve un puntatore a un array di achievement 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.
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.
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 e gli achievement 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 CSteamAchievements::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(); }

SetAchievement()

Parametri: una stringa che identifica l'achievement che vuoi impostare (ad esempio: "ACH_WIN_ONE_GAME").
Restituisce: una variabile booleana indicante la riuscita o il fallimento della chiamata. Il fallimento della chiamata indica che Steam non è stato inizializzato o che la callback dalla chiamata iniziale a RequestStats non è ancora stata elaborata. Non potrai impostare alcun achievement fino alla ricezione di tale callback.
Funzione: questo metodo imposta un dato achievement come completato e invia i risultati a Steam. Un achievement può essere impostato più volte, quindi non devi preoccuparti di impostare solo quelli non ancora completati. Questa è una chiamata asincrona che attiverà due callback: OnUserStatsStored() e OnAchievementStored().
bool CSteamAchievements::SetAchievement(const char* ID) { // Abbiamo già ricevuto una callback da Steam? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } //In caso di risposta negativa, non possiamo ancora impostare gli achievement return false;

OnUserStatsReceived()

Parametri: nessuno.
Restituisce: nessun valore.
Funzione: questo metodo è una callback chiamata a ogni tentativo di richiesta delle statistiche. Le statistiche e gli achievement vengono richiesti utilizzando RequestStats(). Il metodo aggiorna la variabile membro m_pAchievements affinché rispecchi i dati più recenti delle statistiche e degli achievement di Steam.
void CSteamAchievements::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"); m_bInitialized = true; // Carica gli 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: 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.
void CSteamAchievements::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( "Statistiche salvate su Steam\n" ); } else { char buffer[128]; _snprintf( buffer, 128, "StatsStored: errore, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnAchievementStored()

Parametri: nessuno.
Restituisce nessun valore.
Funzione: questo metodo è una callback chiamata ogni volta che gli achievement vengono salvati correttamente su Steam.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // Ignoriamo eventuali callback in arrivo per le statistiche di altri giochi if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Achievement salvato su Steam\n" ); } }

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 gli achievement, un'enumerazione con gli achievement specifici del gioco e un puntatore globale al nostro oggetto helper. Ricorda che gli achievement corrisponderanno a quelli definiti nella pagina di amministrazione 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, }; // Un array che conterrà tutti i dati sugli achievement 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 l'array degli achievement e le dimensioni dell'array stesso.
... // 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 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(); ...

Attivazione degli achievement

L'attivazione di un achievement richiede la chiamata a una singola funzione con l'identificativo dell'achievement come parametro.
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

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 SteamAchievements if (g_SteamAchievements) delete g_SteamAchievements; ...

Passaggio 4: test e risoluzione dei problemi


Per impostare o eliminare le statistiche o gli achievement senza aggiungere codice al tuo gioco, puoi usare la console del client di Steam. Esegui l'avvio con il comando steam.exe -console, seguito da:
  • achievement_clear <appID> <nome dell'achievement>
  • reset_all_stats <appID>