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

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>