Документация Steamworks
Достижения: пошаговое руководство

Введение

Далее излагаются инструкции по интеграции самых простых достижений в приложение за менее чем 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 статистики.
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. Тестирование и устранение неполадок

Этот пример кода выводит информацию отладки в консоль отладки, которая нужна для понимания, какие вызовы выполнены, а какие нет. Далее приведены типичные сообщения об ошибках и способы их устранения:

This application has failed to start because steam_api.dll was not found. Re-installing the application may fix this problem. (Приложение не запустилось, поскольку файл d3dx_??.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 отсутствует в нужной папке. Поместите его в нужное место и убедитесь, что в нем есть номер приложения.