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

Введение

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

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

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

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

Достижения определяются для конкретного приложения и устанавливаются в настройках достижений на странице управления приложениями Steamworks. The following is the list of achievements from the Steamworks sample app Spacewar:

spacewar_achievement_examplescreenshot.jpg

2. Инкапсуляция достижений

The following code is game independent and can be added to your game as you see fit. The class is fully functional as is but can be easily extended to meet any further needs. All of this code was taken directly from the Spacewar example files StatsAndAchievements.cpp/h.

Заголовочный файл

We first define a structure to hold our achievement data received from Steam and provide a macro for creating objects of that type. This data maps directly to what is found on the Achievement Configuration page.
#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; };

Next we define a helper class that will wrap all of the Steam Stats API calls as well as creating all of the Steam callbacks.
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 ); };

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

Конструктор

Параметры - The constructor takes a pointer to an array of achievements along with the length of the array. The formating of that array will be covered in the main game code later.
Возвращаемые значения - N/A
Что делает - The constructor initializes a number of members along with grabbing the AppID we are currently running as. In addition it hooks up the call back methods to handle asynchronous calls made to Steam. Finally it makes an initial call to RequestStats() to get stats and achievements for the current user.
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()

Параметры - None
Возвращаемые значения - a bool representing if the call succeeded or not. If the call failed then most likely Steam is not initialized. Make sure you have a Steam client open when you try to make this call and that SteamAPI_Init has been called before it.
Что делает - This method basically wraps a call to ISteamUserStats::RequestCurrentStats which is an asynchronous call to steam requesting the stats and achievements of the current user. This call needs to be made before you can set any stats or achievements. The initial call to this method is made in the constructor. You can call it again any time after that if you want to check on updated stats or achievements.
bool CSteamAchievements::RequestStats() { // Steam загружен? Если нет, получить статистики не удастся. if ( NULL == SteamUserStats() || NULL == SteamUser() ) { return false; } // Пользователь залогинен? Если нет, получить статистики не удастся. if ( !SteamUser()->BLoggedOn() ) { return false; } // Запрос статистик. return SteamUserStats()->RequestCurrentStats(); }

SetAchievement()

Параметры - The string identifier of the Achievement that you want to set (ie. "ACH_WIN_ONE_GAME")
Возвращаемые значения - a bool representing if the call succeeded or not. If the call failed then either Steam is not initialized or you still haven't processed the callback from the initial call to RequestStats. You can't set any achievements until that callback has been received.
Что делает - This method sets a given achievement to achieved and sends the results to Steam. You can set a given achievement multiple times so you don't need to worry about only setting achievements that aren't already set. This is an asynchronous call which will trigger two callbacks: OnUserStatsStored() and OnAchievementStored().
bool CSteamAchievements::SetAchievement(const char* ID) { // получен ли обратный вызов от Steam? if (m_bInitialized) { SteamUserStats()->SetAchievement(ID); return SteamUserStats()->StoreStats(); } // Если нет, установить достижение не получится return false; }

OnUserStatsReceived()

Параметры - N/A
Возвращаемые значения - Nothing
Что делает - This method is a callback that is called anytime you attempt to request stats. Stats and achievements are requested by using RequestStats(). The method updates the member variable m_pAchievements to reflect the latest stats and achievement data returned from 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 { 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()

Параметры - N/A
Возвращаемые значения - Nothing
Что делает - This method is a callback that is called anytime you attempt to store stats on 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()

Параметры - N/A
Возвращаемые значения - Nothing
Что делает - This method is a callback that is called anytime Achievements are successfully stored on Steam.
void CSteamAchievements::OnAchievementStored( UserAchievementStored_t *pCallback ) { // могут быть получены обратные вызовы со статистиками из других игр, игнорируйте их if ( m_iAppID == pCallback->m_nGameID ) { OutputDebugString( "Stored Achievement for Steam\n" ); } }

3. Интеграция в игру

The following is a complete listing of code snippets that you would need to integrate into your game in the appropriate locations.

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

The following is the list of includes that are needed to build with Achievements, an enum of our game specific achievements and a global pointer to our helper object. Please note that the achievements match those of the Admin page on 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; ...

Инициализация

The call to SteamAPI_Init initializes all of Steam and must be called before anything else. If that call succeeds then we create the helper object by passing in the array of achievements along with the size of the array.
... // инициализируем Steam bool bRet = SteamAPI_Init(); // создаем объект SteamAchievements, если инициализация Steam удалась if (bRet) { g_SteamAchievements = new CSteamAchievements(g_Achievements, 4); } ...

Обработка обратных вызовов

To ensure that we process all Steam callbacks we need to regularly pump for new messages. This is achieved by adding this call to the game loop.
... SteamAPI_RunCallbacks(); ...

Активация достижений

Triggering an achievement is as simple as a single call passing along the achievement identifier.
... if (g_SteamAchievements) g_SteamAchievements->SetAchievement("ACH_WIN_100_GAMES"); ...

Выключение SteamAPI

The call to SteamAPI_Shutdown is probably something you already have in your code. It shuts down Steam and must be called before your application exits. Finally we delete the helper object we created.
... // Выключаем Steam SteamAPI_Shutdown(); // Удаляем SteamAchievements if (g_SteamAchievements) delete g_SteamAchievements; ...

4. Тестирование и устранение неполадок


To set or clear stats or an achievement without adding code to your game, you can use the Steam client console. Run with steam.exe -console, then:
  • achievement_clear
  • reset_all_stats