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

Крок 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.