Введение
Далее излагаются инструкции по интеграции самых простых статистик в приложение за менее чем 10 минут и менее чем 10 строк кода, включённых в основной код игры. В
SDK Steamworks предоставлен отличный пример приложения, которое называется
Spacewar. В нем показана вся широта функций Steam в действии, поэтому рекомендуется ознакомиться с ним. В этом руководстве информация, представленная в Spacewar, отфильтрована до необходимой для понимания API статистик и достижений. Обратите внимание, что статистики и достижения сильно пересекаются, поэтому если вы интегрируете обе функции, учтите, что большинство вызовов могут быть объединены.
1. Определение статистик
Статистики определяются для конкретного приложения и устанавливаются на
соответствующей странице в админке Steamworks. Далее представлены статистики из Spacewar:
2. Инкапсуляция статистик
Последующий код работает для всех игр и может быть добавлен в любую из них. Класс уже полностью функционален, но его можно легко расширить, если потребуется. Весь код взят непосредственно из файла
StatsAndAchievements.cpp/h
из Spacewar.
Заголовочный файл
Сначала определяется структура, предназначенная для хранения полученных от Steam данных статистик, для определения их типов создается удобное перечисление, а также создается макрос для создания объектов статистики. Эти данные напрямую отсылают к тому, что находится на
странице настройки статистик.
#define _STAT_ID( id,type,name ) { id, type, name, 0, 0, 0, 0 }
enum EStatTypes
{
STAT_INT = 0,
STAT_FLOAT = 1,
STAT_AVGRATE = 2,
};
struct Stat_t
{
int m_ID;
EStatTypes m_eStatType;
const char *m_pchStatName;
int m_iValue;
float m_flValue;
float m_flAvgNumerator;
float m_flAvgDenominator;
};
Далее определяется вспомогательный класс, который будет заключать в себе все вызовы к API статистики.
class CSteamStats
{
private:
int64 m_iAppID; // Our current AppID
Stat_t *m_pStats; // Stats data
int m_iNumStats; // The number of Stats
bool m_bInitialized; // Have we called Request stats and received the callback?
public:
CSteamStats(Stat_t *Stats, int NumStats);
~CSteamStats();
bool RequestStats();
bool StoreStats();
STEAM_CALLBACK( CSteamStats, OnUserStatsReceived, UserStatsReceived_t,
m_CallbackUserStatsReceived );
STEAM_CALLBACK( CSteamStats, OnUserStatsStored, UserStatsStored_t,
m_CallbackUserStatsStored );
};
Программный код
Конструктор
Параметры — конструктор принимает указатель на массив статистик и длину массива. Формат этого массива будет освещен в основном игровом коде далее.
Возвращаемые значения — нет.
Что делает — конструктор инициализирует несколько членов, а также принимает AppID текущего приложения. В дополнение к этому он связывает методы обратных вызовов для обработки асинхронных вызовов к Steam. Наконец, он выполняет начальный вызов
RequestStats()
для получения статистик и достижений текущего пользователя.
CSteamStats::CSteamStats(Stat_t *Stats, int NumStats) :
m_iAppID( 0 ),
m_bInitialized( false ),
m_CallbackUserStatsReceived( this, &CSteamStats::OnUserStatsReceived ),
m_CallbackUserStatsStored( this, &CSteamStats::OnUserStatsStored )
{
m_iAppID = SteamUtils()->GetAppID();
m_pStats = Stats;
m_iNumStats = NumStats;
RequestStats();
}
RequestStats()
Параметры — отсутствуют.
Возвращает — значение bool, показывающее, удался ли вызов. Если вызов не удался, скорее всего, Steam не инициализирован. Перед вызовом необходимо убедиться, что клиент открыт и что ранее была вызвана функция
SteamAPI_Init.
Что делает — метод по сути представляет собой обёртку для вызова
ISteamUserStats::RequestCurrentStats, который является асинхронным запросом статистик и достижений текущего пользователя. Этот вызов должен быть выполнен перед установкой статистик и достижений. Начальный вызов этого метода происходит в конструкторе. Его можно вызвать заново в любое время при необходимости узнать обновленные статистики и достижения.
bool CSteamStats::RequestStats()
{
// Steam загружен? Если нет, получить статистики не удастся.
if ( NULL == SteamUserStats() || NULL == SteamUser() )
{
return false;
}
// Пользователь залогинен? Если нет, получить статистики не удастся.
if ( !SteamUser()->BLoggedOn() )
{
return false;
}
// Request user stats.
return SteamUserStats()->RequestCurrentStats();
}
StoreStats()
Параметры — отсутствуют.
Возвращает — значение bool, показывающее, удался ли вызов. Если вызов не удался, скорее всего, Steam не инициализирован. Перед вызовом необходимо убедиться, что клиент открыт и что ранее была вызвана функция
SteamAPI_Init.
Что делает — метод по сути представляет собой обёртку для вызова
ISteamUserStats::StoreStats, который является асинхронным запросом в Steam, который хранит статистики текущего пользователя на сервере. Этот вызов должен выполняться всякий раз, когда необходимо обновить статистики пользователя.
bool CSteamStats::StoreStats()
{
if ( m_bInitialized )
{
// load stats
for ( int iStat = 0; iStat < m_NumStats; ++iStat )
{
Stat_t &stat = m_pStats[iStat];
switch (stat.m_eStatType)
{
case STAT_INT:
SteamUserStats()->SetStat( stat.m_pchStatName, stat.m_iValue );
break;
case STAT_FLOAT:
SteamUserStats()->SetStat( stat.m_pchStatName, stat.m_flValue );
break;
case STAT_AVGRATE:
SteamUserStats()->UpdateAvgRateStat(stat.m_pchStatName, stat.m_flAvgNumerator, stat.m_flAvgDenominator );
// The averaged result is calculated for us
SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue );
break;
default:
break;
}
}
return SteamUserStats()->StoreStats();
}
}
OnUserStatsReceived()
Параметры — нет.
Возвращает — ничего.
Что делает — этот метод представляет собой обратный вызов, который отправляется всякий раз, когда игра пытается запросить статистики в Steam. Статистики запрашиваются с помощью
RequestStats()
. Метод обновляет переменную-член m_Stats, чтобы отразить последние данные о статистиках, возвращенных Steam.
void CSteamStats::OnUserStatsReceived( UserStatsReceived_t *pCallback )
{
// we may get callbacks for other games' stats arriving, ignore them
if ( m_iAppID == pCallback->m_nGameID )
{
if ( k_EResultOK == pCallback->m_eResult )
{
OutputDebugString( "Received stats and achievements from Steam\n" );
// load stats
for ( int iStat = 0; iStat < m_iNumStats; ++iStat )
{
Stat_t &stat = m_Stats[iStat];
switch (stat.m_eStatType)
{
case STAT_INT:
SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_iValue);
break;
case STAT_FLOAT:
case STAT_AVGRATE:
SteamUserStats()->GetStat(stat.m_pchStatName, &stat.m_flValue);
break;
default:
break;
}
}
m_bInitialized = true;
}
else
{
char buffer[128];
_snprintf( buffer, 128, "RequestStats - failed, %d\n", pCallback->m_eResult );
OutputDebugString( buffer );
}
}
}
OnUserStatsStored()
Параметры — нет.
Возвращает — ничего.
Что делает — этот метод представляет собой обратный вызов, который отправляется всякий раз, когда игра пытается сохранить статистики в Steam. Если при попытке установить статистики одна или несколько из них нарушают наложенные ограничения, их старые значения восстанавливаются, поэтому их нужно перезагрузить.
void CSteamStats::OnUserStatsStored( UserStatsStored_t *pCallback )
{
// we may get callbacks for other games' stats arriving, ignore them
if ( m_iAppID == pCallback->m_nGameID )
{
if ( k_EResultOK == pCallback->m_eResult )
{
OutputDebugString( "StoreStats - success\n" );
}
else if ( k_EResultInvalidParam == pCallback->m_eResult )
{
// Одна или несколько статистик нарушили ограничение. Значения восстановлены,
// и теперь нужно заново пройтись по значениям для синхронизации.
OutputDebugString( "StoreStats - some failed to validate\n" );
// симулируем обратный вызов для повторной загрузки значений.
UserStatsReceived_t callback;
callback.m_eResult = k_EResultOK;
callback.m_nGameID = m_iAppID;
OnUserStatsReceived( &callback );
}
else
{
char buffer[128];
_snprintf( buffer, 128, "StoreStats - failed, %d\n", pCallback->m_eResult );
OutputDebugString( buffer );
}
}
}
3. Интеграция в игру
Далее следует полный перечень фрагментов кода, которые необходимо интегрировать в игру в подходящих для этого местах.
Определения и глобальные переменные
Далее приведен список подключений, необходимых для объекта Stats, массив со статистиками, относящимися к данной игре, и глобальный указатель на вспомогательный объект. Статистики при этом совпадают с перечисленными на сайте Steamworks.
...
#include "steam_api.h"
#include "isteamuserstats.h"
#include "SteamStats.h"
// массив статистик, содержащий данные о статистиках и их состоянии
Stat_t g_Stats[] =
{
_STAT_ID( 1, STAT_INT, "NumGames"),
_STAT_ID( 2, STAT_INT, "NumWins"),
_STAT_ID( 3, STAT_INT, "NumLosses"),
_STAT_ID( 4, STAT_FLOAT, "FeetTraveled"),
_STAT_ID( 5, STAT_AVGRATE, "AverageSpeed"),
_STAT_ID( 7, STAT_FLOAT, "MaxFeetTraveled"),
};
// глобальный доступ к объекту Stats
CSteamStats* g_SteamStats = NULL;
...
Инициализация
Вызов
SteamAPI_Init инициализирует Steam и должен быть выполнен прежде всего. Если вызов удался, создается вспомогательный объект, которому передается массив статистик и размер массива.
...
// инициализируем Steam
bool bRet = SteamAPI_Init();
// создаем объект SteamStats, если инициализация Steam удалась
if (bRet)
{
g_SteamStats = new CSteamStats(g_Stats, 6);
}
...
Обработка обратных вызовов
Чтобы обрабатывать все обратные вызовы Steam нужно их регулярно слушать. Для этого в игровой цикл добавляется следующий вызов.
...
SteamAPI_RunCallbacks();
...
Сохранение статистик
Статистики сохраняются в вызове
StoreStats()
.
...
if (g_SteamStats)
g_SteamStats->StoreStats();
...
Выключение
Вызов
SteamAPI_Shutdown, который закрывает Steam, скорее всего, уже есть у вас в коде. Он должен быть отправлен до того, как закроется приложение. В конце удаляется ранее созданный вспомогательный объект.
...
// Выключаем Steam
SteamAPI_Shutdown();
// Удаляем объект SteamStats
if (g_SteamStats)
delete g_SteamStats;
...
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 отсутствует в нужной папке. Поместите его в нужное место и убедитесь, что в нем есть номер приложения.