Документация 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; // Our current AppID Achievement_t *m_pAchievements; // Achievements data int m_iNumAchievements; // The number of Achievements bool m_bInitialized; // Have we called Request stats and received the callback? 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" ), }; // глобальный доступ к объекту Achievements 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>