Документація Steamworks
Крок за кроком: статистика

Вступ

Нижче подається покроковий посібник з ітерації простих статистик Steam до вашого застосунку, що займе менш як 10 хвилин і менше ніж 10 додаткових рядків коду в основному коді гри. У розділі про SDK Steamworks подано чудовий приклад застосунку, що називається Spacewar. У ньому показано весь діапазон функцій Steam, тож рекомендуємо із ним ознайомитися. Тут інформація зі Spacewar та API статистик і досягнень відфільтрована до необхідної для розуміння принципу роботи цих функцій. Зверніть увагу, що статистики й досягнення сильно перетинаються, тож якщо ви інтегруєте обидві функції, врахуйте, що багато викликів можна об’єднати.

Крок 1: визначення статистик для вашої гри

Статистики визначаються для конкретного застосунку й налаштовуються на сторінці конфігурації статистик в адмініструванні застосунку в Steamworks. Далі подано список статистик зі Spacewar:
stats_spacewar.png

Крок 2: інкапсуляція статистик

Наступний код працює для всіх ігор і може бути доданий у будь-яку з них. Клас повністю функціональний, але його можна легко розширити, якщо потрібно. Увесь цей код взятий безпосередньо з файлу StatsAndAchievements.cpp/h зі Spacewar.

Заголовний файл

Спершу ми визначаємо структуру, що призначається для збереження отриманих від Steam даних, у зручному переліченні визначаємо типи статистик, а потім надаємо макрос для створення об’єктів статистик. Ці дані напряму відсилають до того, що знаходиться на сторінці конфігурації статистик.
#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; };

Далі ми визначаємо допоміжний клас, який охопить всі виклики до API статистик Steam, а також створення всіх зворотних викликів Steam.
class CSteamStats { private: int64 m_iAppID; // Наш поточний AppID Stat_t *m_pStats; // Дані статистик int m_iNumStats; // Кількість статистик bool m_bInitialized; // Чи ми викликали запит на статистику й отримували зворотний виклик? 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 ); };

Програмний код

Конструктор

Параметри — конструктор приймає вказівник на масив статистик і довжину масиву. Форматування цього масиву буде показано пізніше в основному ігровому коді.
Повернені значення — немає даних.
Що робить — конструктор ініціалізує кілька членів, а також приймає AppID запущеного застосунку. Окрім цього, він пов’язує методи зворотних викликів для обробки асинхронних викликів до Steam. Зрештою, він виконує початковий виклик RequestStats() для отримання статистик і досягнень поточного користувача.
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()

Параметри — немає.
Повернені значення — bool, що показує, чи вдався виклик. Якщо виклик не вдався, то, ймовірно, Steam не ініціалізовано. Перед викликом слід переконатися, що клієнт Steam відкрито й до цього була викликана функція SteamAPI_Init.
Що робить — метод загалом є обгорткою для виклику ISteamUserStats::RequestCurrentStats, що є асинхронним запитом статистик поточного користувача. Цей виклик слід виконати перед установленням статистик і досягнень. Початковий виклик цього метода відбувається в конструкторі. Його можна викликати заново в будь-який час, якщо потрібно дізнатися оновлені статистики й досягнення.
bool CSteamStats::RequestStats() { // Чи завантажено Steam? Якщо ні, то статистики отримати неможливо. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // Чи увійшов користувач? Якщо ні, ми не можемо отримати статистики. if ( !SteamUser()->BLoggedOn() ) { return false; } // Запит користувацьких статистик. return SteamUserStats()->RequestCurrentStats(); }

StoreStats()

Параметри — немає.
Повернені значення — bool, що показує, чи вдався виклик. Якщо виклик не вдався, то, ймовірно, Steam не ініціалізовано. Перед викликом слід переконатися, що клієнт Steam відкрито й до цього була викликана функція SteamAPI_Init.
Що робить — метод загалом є обгорткою для виклику ISteamUserStats::StoreStats, що є асинхронним викликом до Steam, який зберігає статистики поточного користувача на сервері. Цей виклик слід робити щоразу, коли вам потрібно оновити статистики користувача.
bool CSteamStats::StoreStats() { if ( m_bInitialized ) { // Завантажити статистики 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 ); // Для нас обраховується середній результат SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue ); break; default: break; } } return SteamUserStats()->StoreStats(); } }

OnUserStatsReceived()

Параметри — немає даних.
Повернені значення — немає.
Що робить — цей метод є зворотним викликом, який викликається щоразу, коли ви пробуєте надіслати запит на статистику. Статистики запитуються за допомогою RequestStats(). Метод оновлює змінну-член m_Stats для відображення останніх даних про статистики, що повертаються зі Steam.
void CSteamStats::OnUserStatsReceived( UserStatsReceived_t *pCallback ) { // ми можемо отримати зворотні виклики для статистик інших ігор, ігноруємо їх if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Received stats and achievements from Steam\n" ); // завантаження статистик 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()

Параметри — немає даних.
Повернені значення — немає.
Що робить — цей метод є зворотним викликом, який викликається щоразу, коли ви пробуєте зберегти статистику в Steam. Якщо якась зі статистик, що ми пробуємо встановити, порушує накладені обмеження, то їхні старі значення відновлюються й потрібно перезавантажити їхні значення.
void CSteamStats::OnUserStatsStored( UserStatsStored_t *pCallback ) { // ми можемо отримати зворотні виклики для статистик інших ігор, ігноруємо їх if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "StoreStats - success\n" ); } else if ( k_EResultInvalidParam == pCallback->m_eResult ) { // Одна чи більше встановлених статистик порушують обмеження. Відновлюються їхні попередні значення. // Потрібно оновити значення для синхронізації. OutputDebugString( "StoreStats - some failed to validate\n" ); // Зворотний виклик для перезавантаження значень. 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 ); } } }

Крок 3: інтеграція у вашу гру

Далі подано повний перелік фрагментів коду, які потрібно інтегрувати у вашу гру у відповідних місцях.

Визначення і глобальні змінні

Нижче наведено перелік включень, які необхідні для об’єкта Stats, масив зі статистиками цієї гри та глобальний вказівник на допоміжний об’єкт. Майте на увазі, що статистики збігаються з тими, що вказані на сторінці адміністрування в Steamworks.
… #include "steam_api.h" #include "isteamuserstats.h" #include "SteamStats.h" // Масив статистик, що зберігає дані про статистики і їхній стан 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"), }; // Глобальний доступ до об’єкту статистик CSteamStats* g_SteamStats = NULL; …

Ініціалізація

Виклик SteamAPI_Init ініціалізує Steam і повинен бути виконаний у найпершу чергу. Якщо виклик удався, то створюється допоміжний об’єкт, котрому передається масив статистик і розмір масиву.
… // Ініціалізація Steam bool bRet = SteamAPI_Init(); // Створення об’єкту SteamStats, якщо Steam успішно ініціалізовано if (bRet) { g_SteamStats = new CSteamStats(g_Stats, 6); } …

Обробка зворотних викликів

Для обробки всіх зворотних викликів Steam потрібно регулярно слухати нові повідомлення. Для цього в ігровий цикл додається наступний виклик.
… SteamAPI_RunCallbacks(); …

Збереження статистик

Статистики зберігаються за допомогою одного виклику StoreStats().
… if (g_SteamStats) g_SteamStats->StoreStats(); …

Вимкнення інтерфейсу SteamAPI

Виклик SteamAPI_Shutdown, котрий закриває Steam, імовірно, вже є у вашому коді. Його слід надіслати до того, як закриється застосунок. Наприкінці видаляється створений раніше допоміжний об’єкт.
… // Вимкнення Steam SteamAPI_Shutdown(); // Видалення об’єкту SteamStats if (g_SteamStats) delete g_SteamStats; …

Крок 4: тестування й вирішення проблем

Цей приклад коду виводить інформацію налагодження в консоль налагодження, котра потрібна для розуміння того, які виклики вдалися, а які — ні. Нижче наведено деякі типові сповіщення про помилки та способи виправлення:

This application has failed to start because steam_api.dll was not found. Re-installing the application may fix this problem. (Застосунок не запустився, оскільки файл steam_api.dll не знайдено. Повторна інсталяція застосунку може вирішити цю проблему).
Файл steam_api.dll має бути у тій самій директорії, що й виконуваний файл.

[S_API FAIL] SteamAPI_Init() failed; unable to locate a running instance of Steam, or a local steamclient.dll. (Виклик SteamAPI_Init() не вдався, не вдалося знайти запущений Steam або локальний файл steamclient.dll).
Найімовірніше, клієнт Steam не запущено. Запустіть Steam та увійдіть.

[S_API FAIL] SteamAPI_Init() failed; no appID found. (Виклик SteamAPI_Init() не вдався, не знайдено AppID).
Найімовірніше, файл steam_appid.txt відсутній у потрібній теці. Розташуйте його в потрібному місці й переконайтеся, що там є номер вашого AppID.