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:

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>