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

Вступ

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

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

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

Крок 2: інкапсуляція досягнень

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

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

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

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

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

Конструктор

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

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

SetAchievement()

Параметри — текстовий ідентифікатор досягнення, яке необхідно встановити (наприклад, ACH_WIN_ONE_GAME).
Повернені значення — bool, що показує, чи вдався виклик. Якщо виклик не вдався або не ініціалізовано Steam чи ще не оброблено зворотний виклик початкового виклику RequestStats. Ви не зможете встановити жодне досягнення, доки не буде отримано цей зворотний виклик.
Що робить — цей метод установлює обране досягнення як виконане й надсилає результати в Steam. Досягнення можна встановити кілька разів, тож можна не турбуватися про те, щоби встановлювати лише ті досягнення, які ще не встановлені. Це асинхронний виклик, на який буде отримано два зворотних виклики: OnUserStatsStored() та OnAchievementStored().
bool CSteamAchievements::SetAchievement(const char* ID) { // Ми ще не отримували зворотний виклик від Steam? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // Якщо ні, то встановити досягнення ще не можна return false; }

OnUserStatsReceived()

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

OnUserStatsStored()

Параметри — немає даних.
Повернені значення — немає.
Що робить — цей метод є зворотним викликом, який викликається щоразу, коли ви пробуєте зберегти статистику в Steam.
void CSteamAchievements::OnUserStatsStored( UserStatsStored_t *pCallback ) { // ми можемо отримати зворотні виклики для статистики інших ігор, ігноруємо їх if ( m_iAppID == pCallback->m_nGameID ) { if ( k_EResultOK == pCallback->m_eResult ) { OutputDebugString( "Stored stats for Steam\n" ); } else { char buffer[128]; _snprintf( buffer, 128, "StatsStored - failed, %d\n", pCallback->m_eResult ); OutputDebugString( buffer ); } } }

OnAchievementStored()

Параметри — немає даних.
Повернені значення — немає.
Що робить — цей метод є зворотним викликом, який викликається щоразу, як досягнення успішно зберігається в Steam.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // ми можемо отримати зворотні виклики для статистики інших ігор, ігноруємо їх if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Stored Achievement for Steam\n" ); } }

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

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

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

Нижче наведено перелік включень, які необхідні для збірки з досягненнями, перерахування з досягненнями цієї гри та глобальний вказівник на допоміжний об’єкт. Майте на увазі, що досягнення збігаються з тими, що вказані на сторінці адміністрування в Steamworks.
… #include "steam_api.h" // Визначення наших досягнень enum EAchievements { ACH_WIN_ONE_GAME = 0, ACH_WIN_100_GAMES = 1, ACH_TRAVEL_FAR_ACCUM = 2, ACH_TRAVEL_FAR_SINGLE = 3, }; // Масив досягнень, який зберігає дані про досягнення та їхній стан Achievement_t g_Achievements[] = { _ACH_ID( ACH_WIN_ONE_GAME, "Winner" ), _ACH_ID( ACH_WIN_100_GAMES, "Champion" ), _ACH_ID( ACH_TRAVEL_FAR_ACCUM, "Interstellar" ), _ACH_ID( ACH_TRAVEL_FAR_SINGLE, "Orbiter" ), }; // Глобальний доступ до об’єкту досягнень CSteamAchievements* g_SteamAchievements = NULL; …

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

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

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

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

Активація досягнень

Активація досягнень проста — це один виклик, що передає ідентифікатор досягнення.
… if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); …

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

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

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