Документация Steamworks
Достижения: пошаговое руководство
Вкратце
Используйте достижения, чтобы вознаградить игроков за преодоление определённых этапов или за особое взаимодействие с вашей игрой.
Уровень интеграции
10 минут и меньше 10 строк кода. Необходима интеграция SDK Steamworks.

Общая информация

Достижения можно использовать в качестве поощрения и вознаграждения игроков за активное участие и успехи в игре. Они часто используются для обозначения числа убийств, пройденных миль, открытых ящиков или других общих действий в вашей игре. Кроме того, достижения помогут игрокам открыть для себя различные подходы к вашей игре. Разблокированные достижения будут появляться в углу экрана и будут отмечены на странице достижений пользователя.

Технический обзор

Ниже представлены инструкции по интеграции самых простых достижений в приложение за менее чем 10 минут и менее чем 10 строк кода, включённых в основной код игры. В SDK Steamworks представлен отличный пример приложения, которое называется Spacewar. В нём показан весь спектр функций Steam в действии, поэтому сначала мы рекомендуем ознакомиться с ним. Информация, представленная в Spacewar, изложена максимально просто
и доступно для понимания API статистик и достижений. Обратите внимание, что статистики и достижения значительно пересекаются, поэтому если вы интегрируете обе функции, большинство вызовов могут быть объединены.

1. Определение достижений

Достижения определяются для конкретного приложения и устанавливаются в настройках достижений на странице управления приложениями Steamworks. Далее представлены достижения из Spacewar (тестового приложения Steamworks):

spacewar_achievement_examplescreenshot.jpg

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.
class CSteamAchievements { private: int64 m_iAppID; // Текущий AppID Achievement_t *m_pAchievements; // Данные о достижениях int m_iNumAchievements; // Число достижений bool m_bInitialized; // Был ли выполнен вызов RequestStats() для запроса статистики, а затем получен обратный вызов? 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 не инициализирован. Перед вызовом необходимо убедиться, что клиент открыт и что ранее была вызвана функция 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()

Параметры — не применимо.
Возвращаемые значения — нет.
Что делает — этот метод представляет собой обратный вызов, который отправляется всякий раз, когда игра пытается запросить статистики в Steam. Статистики и достижения запрашиваются с помощью 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. Интеграция в игру

Далее следует полный перечень фрагментов кода, которые необходимо интегрировать в игру в соответствующих местах.

Определения и глобальные переменные

Далее приведён список подключений, необходимых для объекта Achievements, перечисление с достижениями, относящимися к данной игре, и глобальный указатель на вспомогательный объект. Достижения при этом совпадают с перечисленными на сайте 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. Тестирование и устранение неполадок


Вы можете задать или очистить достижения или статистику без внесения изменений в код игры с помощью консоли клиента Steam. Запустите steam.exe с помощью -console, а затем:
  • achievement_clear <appid> <achievement name>
  • reset_all_stats <appid>