Steamworks-Dokumentation
Schritt-für-Schritt: Statistiken

Einführung

Im Folgenden finden Sie eine kurze Schritt-für-Schritt-Anleitung, wie Sie einfache Steam-Statistiken in weniger als 10 Minuten und mit weniger als 10 Zeilen Code in Ihre Anwendung integrieren können. Im Lieferumfang des Steamworks SDKs ist eine große Beispielanwendung namens Spacewar enthalten, welche die gesamte Bandbreite der Steam-Funktionen demonstriert und Ihre erste Anlaufstelle sein sollte, wenn Sie Funktionen in Aktion erleben möchten. Dieses Tutorial fasst die Informationen aus Spacewar und der API für Statistiken und Errungenschaften auf die wesentlichen für Steam benötigten Informationen zusammen, damit alles so einfach wie möglich bleibt. Beachten Sie, dass es zwischen Statistiken und Errungenschaften erhebliche Überschneidungen gibt. Wenn Sie beides implementieren, können Sie daher zahlreiche Aufrufe konsolidieren.

Schritt 1 – Definieren der Statistiken für Ihr Spiel

Statistiken sind anwendungsspezifisch und werden auf der Seite zur Statistikkonfiguration im Backend der Steamworks-Anwendungsverwaltung eingerichtet. Im Folgenden finden Sie die Liste der Statistiken aus der Steamworks-Beispielapp Spacewar:
stats_spacewar.png

Schritt 2 – Kurzbeschreibung der Arbeit mit Statistiken

Der folgende Code ist spielübergreifend und kann entsprechend Ihrer Anforderungen in Ihr Spiel eingefügt werden. Die Klasse ist in dieser Form voll funktionsfähig, kann aber bei Bedarf problemlos erweitert werden. Der gesamte Code stammt direkt aus den Spacewar-Beispieldateien StatsAndAchievements.cpp/h.

Header-Datei

Zunächst definieren wir eine Struktur, in welche die vom Server empfangenen Statistikdaten aufgenommen werden, definieren Statistiktypen als praktische Aufzählung und stellen anschließend ein Makro zum Erstellen von Objekten dieses Typs bereit. Diese Daten entsprechen exakt denen auf der Seite Statistikkonfiguration.
#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; };

Als Nächstes definieren wir eine Helper-Klasse, die alle Aufrufe der Steam-API zusammenfasst und alle Steam-Rückrufe erstellt.
class CSteamStats { private: int64 m_iAppID; // Our current AppID Stat_t *m_pStats; // Stats data int m_iNumStats; // The number of Stats bool m_bInitialized; // Wurde RequestStats() aufgerufen und der Rückruf empfangen? 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 ); };

Codedatei

Konstruktor

Parameter – Der Konstruktor umfasst einen Zeiger auf ein Array mit Statistiken sowie die Länge des Arrays. Das Format dieses Arrays wird im Hauptcode des Spiels weiter unten behandelt.
Rückgabewert – Keiner
Funktionsweise – Der Konstruktor initialisiert eine Reihe von Instanzvariablen und ruft die App-ID ab, mit der wir gerade spielen. Außerdem werden der Rückrufmethoden eingebunden, mit denen asynchrone Aufrufe an Steam verarbeitet werden. Schließlich erfolgt der Erstaufruf von RequestStats(), um Statistiken und Errungenschaften des aktuellen Nutzers abzurufen.
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()

Parameter – Keine
Rückgabewert – Boolescher Wert, der angibt, ob der Aufruf erfolgreich war oder nicht. Wenn der Aufruf fehlgeschlagen ist, wurde Steam höchstwahrscheinlich nicht initialisiert. Stellen Sie sicher, dass ein Steam-Client ausgeführt wird, wenn Sie diesen Aufruf ausführen, und dass zuvor SteamAPI_Init aufgerufen wurde.
Funktionsweise Funktionsweise – Diese Methode umschließt im Prinzip einen Aufruf von ISteamUserStats::RequestCurrentStats, bei dem es sich um einen asynchronen Aufruf an Steam handelt, durch den die Statistiken des aktuellen Nutzers angefordert werden. Dieser Aufruf muss ausgeführt werden, bevor Sie Statistiken und Errungenschaften festlegen bzw. freischalten können. Der Erstaufruf dieser Methode erfolgt im Konstruktor. Sie können die Methode später erneut aufrufen, wenn Sie auf aktualisierte Statistiken oder Errungenschaften prüfen möchten.
bool CSteamStats::RequestStats() { // Hat Steam geladen? Falls nein, können keine Statistiken abgerufen werden. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // Ist der Nutzer angemeldet? Falls nein, können keine Statistiken abgerufen werden. if ( !SteamUser()->BLoggedOn() ) { return false; } // Nutzerstatistiken anfordern. return SteamUserStats()->RequestCurrentStats(); }

StoreStats()

Parameter – Keine
Rückgabewert – Boolescher Wert, der angibt, ob der Aufruf erfolgreich war oder nicht. Wenn der Aufruf fehlgeschlagen ist, wurde Steam höchstwahrscheinlich nicht initialisiert. Stellen Sie sicher, dass ein Steam-Client ausgeführt wird, wenn Sie diesen Aufruf ausführen, und dass zuvor SteamAPI_Init aufgerufen wurde.
Funktionsweise – Diese Methode umschließt im Prinzip einen Aufruf von ISteamUserStats::StoreStats, bei dem es sich um einen asynchronen Aufruf an Steam handelt, durch den die Statistiken des aktuellen Nutzers auf dem Server gespeichert wird. Dieser Aufruf muss jedes Mal ausgeführt werden, wenn Sie die Statistiken des Nutzers aktualisieren möchten.
bool CSteamStats::StoreStats() { if ( m_bInitialized ) { // Status laden. 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 ); // Das gemittelte Ergebnis wird berechnet. SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue ); break; default: break; } } return SteamUserStats()->StoreStats(); } }

OnUserStatsReceived()

Parameter – Keine
Rückgabewert – Keiner
Funktionsweise – Diese Methode ist ein Rückruf, die immer aufgerufen wird, wenn Sie versuchen, Statistiken anzufordern. Statistiken fordern Sie mit RequestStats() an. Die Methode aktualisiert die Instanzvariable m_Stats, um die neuesten von Steam zurückgegebenen Daten zu Statistiken widerzuspiegeln.
void CSteamStats::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // Es werden möglicherweise Rückrufe für Statistiken anderer Spiele erhalten. Ignorieren Sie diese. if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Received stats and achievements from Steam\n" ); // Status laden. 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 - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnUserStatsStored()

Parameter – Keine
Rückgabewert – Keiner
Funktionsweise – Diese Methode ist ein Rückruf, die immer aufgerufen wird, wenn Sie versuchen, Statistiken auf Steam zu speichern. Falls von uns eingestellte Statistiken gegen eine Einschränkung verstoßen haben, werden sie auf die alten Werte zurückgesetzt und wir laden die Werte neu.
void CSteamStats::OnUserStatsStored( UserStatsStored_t *pCallback ) { // Es werden möglicherweise Rückrufe für Statistiken anderer Spiele erhalten. Ignorieren Sie diese. if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "StoreStats - success\n" ); } else if ( k_EResultInvalidParam == pCallback->m_eResult ) { // Eine oder mehrere Statistiken haben gegen eine Einschränkung verstoßen. Die Änderungen wurden zurückgesetzt // und wir sollten die Werte nun erneut durchgehen, um synchron zu bleiben. OutputDebugString( "StoreStats - some failed to validate\n" ); // Rückruf vortäuschen, um die Werte erneut zu laden. UserStatsReceived_t callback; callback.m_eResult = k_EResultOK; callback.m_nGameID = m_iAppID; OnUserStatsReceived( &callback ); } else { char buffer[128]; _snprintf( buffer, 128, "StoreStats - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

Schritt 3 – Integration in Ihr Spiel

Im Folgenden finden Sie eine vollständige Liste der Codeausschnitte, die Sie an den entsprechenden Stellen in Ihr Spiel integrieren müssen.

Definitionen und Globals

Im Folgenden finden Sie die Liste der Include-Dateien, die für die Erstellung von Statistiken erforderlich sind, ein Array für unsere spielspezifischen Statistiken und einen globalen Zeiger auf unser Helper-Objekt. Hinweis: Die Statistiken entsprechen denen auf der Verwaltungsseite von Steamworks.
... #include "steam_api.h" #include "isteamuserstats.h" #include "SteamStats.h" // Array von Statistiken, das deren Daten und Status enthält. 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"), }; // Globaler Zugriff auf das Statistikobjekt CSteamStats* g_SteamStats = NULL; ...

Initialisierung

Der Aufruf von SteamAPI_Init initialisiert Steam vollständig und muss vor allen anderen Schritten erfolgen. Wenn dieser Aufruf erfolgreich ist, erstellen wir das Helper-Objekt, indem wir den Array der Statistiken und die Größe des Arrays übergeben.
... // Steam initialisieren bool bRet = SteamAPI_Init(); // SteamStats-Objekt erstellen, wenn Steam erfolgreich initialisiert wurde. if (bRet) { g_SteamStats = new CSteamStats(g_Stats, 6); } ...

Verarbeiten von Rückrufe

Um sicherzustellen, dass alle Steam-Rückrufe verarbeitet werden, müssen wir regelmäßig auf neue Meldungen prüfen. Dies erreichen wir, indem wir der Spielschleife diesen Aufruf hinzufügen.
... SteamAPI_RunCallbacks(); ...

Speichern von Statistiken

Sie speichern Statistiken mit einem einzigen Aufruf von StoreStats().
... if (g_SteamStats) g_SteamStats->StoreStats(); ...

Beenden

Der Aufruf von SteamAPI_Shutdown ist wahrscheinlich bereits in Ihrem Code enthalten. Dadurch wird Steam beendet. Dieser Aufruf muss erfolgen, bevor Ihre Anwendung beendet wird. Schließlich löschen wir das erstellte Helper-Objekt.
... // Steam herunterfahren. SteamAPI_Shutdown(); // SteamStats-Objekt löschen. if (g_SteamStats) delete g_SteamStats; ...

Schritt 4 – Testen und Problembehebung

Dieser Beispielcode gibt Debug-Informationen an die Debug-Konsole aus, anhand derer Sie erkennen können, welche Aufrufe erfolgreich sind und welche nicht. Im Folgenden finden Sie einige typische Fehlermeldungen und die zugehörigen Lösungen:

Diese Anwendung konnte nicht gestartet werden, da die steam_api.dll nicht gefunden wurde. Möglicherweise können Sie das Problem beheben, indem Sie die Anwendung neu installieren.
Stellen Sie sicher, dass sich die steam_api.dll im selben Verzeichnis wie die ausführbare Datei befindet.

[S_API FAIL] SteamAPI_Init() fehlgeschlagen; keine ausgeführte Steam-Instanz oder keine lokale steamclient.dll gefunden.
Höchstwahrscheinlich wird kein Steam-Client ausgeführt. Starten Sie Steam und melden Sie sich an.

[S_API FAIL] SteamAPI_Init() fehlgeschlagen; keine App-ID gefunden.
Höchstwahrscheinlich ist die Datei steam_appid.txt nicht vorhanden. Legen Sie die Datei im Quellordner ab und stellen Sie sicher, dass sie Ihre App-ID enthält.