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-Beispielanwendung 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; // Unsere App-ID Stat_t *m_pStats; // Statistikdaten int m_iNumStats; // Die Anzahl der Statistiken bool m_bInitialized; // Haben wir die Statistiken bereits abgerufen und einen Rückruf erhalten? 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() { // Ist Steam geladen? Falls nicht, können wir keine Statistiken abrufen. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // Ist der Nutzer angemeldet? Falls nicht, können wir keine Statistiken abrufen. if ( !SteamUser()->BLoggedOn() ) { return false; } // Abfrage der Nutzerstatistiken 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 ) { // Statistiken 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 Durchschnittsergebnis wird für uns 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 ) { // Eventuell erhalten wir Rückrufe für Statistiken von anderen Spielen; wir möchten diese ignorieren. if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Received stats and achievements from Steam\n" ); // load stats 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 ) { // Eventuell erhalten wir Rückrufe für Statistiken von anderen Spielen; wir möchten diese ignorieren. 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" // Statistik-Array, welches die Daten über die einzelnen Statistiken und den jeweils zugehörigen Status hä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"), }; // Global access to Stats Object 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(); // Erstellen eines SteamStats-Objekts, 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_SteamAchievements) g_SteamAchievements->SetAchievement(ACH_WIN__GAMES); ...

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 beenden SteamAPI_Shutdown(); // Löschen des SteamStats-Objekts 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.