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

Введение

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

1. Определение списков лидеров

Списки лидеров определяются для каждого приложения отдельно и настраиваются на странице Steamworks Настройка списков лидеров.

Следующие поля должны быть заполнены для создания списка лидеров:
  • Название. Может быть названием, которое имеет смысл с точки зрения разработки.
  • Название в сообществе. Если список лидеров будет отображаться в центре сообщества, задайте здесь общедоступное название. Если ничего не ввести, список лидеров не появится в центре сообщества.
  • Метод сортировки. Выберите подходящий для данного списка лидеров: для списков, основанных на месте игрока, используйте «по возрастанию», а для списков, основанных на счёте, используйте «по убыванию».
  • Тип отображения. Определяет тип данных, показываемых со списком лидеров. Варианты: числа, секунды или миллисекунды.
  • Запись данных. Если установить значение «доверенная», клиенты не смогут отправлять данные об очках в списки лидеров, это можно будет сделать только с помощью SetLeaderboardScore (веб-API). По умолчанию — false.
  • Считывание данных. Если установить значение «друзья», игра сможет считывать данные списка лидеров только для друзей пользователя, но все данные списка всегда можно будет прочесть с помощью веб-API. По умолчанию — false.

spacewar_leaderboards

2. Инкапсуляция списков лидеров

Этот код не зависит от игры и может быть добавлен в любую из них. Класс уже полностью функционален, но его можно легко расширить, если потребуется. Весь код взят непосредственно из файла Leaderboards.cpp/h из Spacewar.

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

Далее определяется вспомогательный класс, который будет заключать в себе все вызовы к API списков лидеров.
class CSteamLeaderboards { private: SteamLeaderboard_t m_CurrentLeaderboard; // дескриптор списка лидеров public: int m_nLeaderboardEntries; // сколько позиций? LeaderboardEntry_t m_leaderboardEntries[10]; // позиции CSteamLeaderboards(); ~CSteamLeaderboards(){}; void FindLeaderboard( const char *pchLeaderboardName ); bool UploadScore( int score ); bool DownloadScores(); void OnFindLeaderboard( LeaderboardFindResult_t *pResult, bool bIOFailure); CCallResult m_callResultFindLeaderboard; void OnUploadScore( LeaderboardScoreUploaded_t *pResult, bool bIOFailure); CCallResult m_callResultUploadScore; void OnDownloadScore( LeaderboardScoresDownloaded_t *pResult, bool bIOFailure); CCallResult m_callResultDownloadScore; };

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

Конструктор

Параметры — отсутствуют.
Возвращаемые значения — не применимо.
Что делает — инициализирует переменные-члены.
CSteamLeaderboards::CSteamLeaderboards() : m_CurrentLeaderboard( NULL ), m_nLeaderboardEntries( 0 ) { }

FindLeaderboard()

Параметры — строковой идентификатор списка лидеров, который необходимо найти (к примеру, «Пройдено метров»)
Возвращаемые значения — нет.
Что делает — метод представляет собой обёртку для вызова ISteamUserStats::FindLeaderboard, который является асинхронным запросом дескриптора данного списка лидеров. Этот вызов должен быть выполнен перед получением или записью данных позиций списка лидеров. Этот метод также настраивает используемый метод обратного вызова.
void CSteamLeaderboards::FindLeaderboard( const char *pchLeaderboardName ) { m_CurrentLeaderboard = NULL; SteamAPICall_t hSteamAPICall = SteamUserStats()->FindLeaderboard(pchLeaderboardName); m_callResultFindLeaderboard.Set(hSteamAPICall, this, &CSteamLeaderboards::OnFindLeaderboard); }

OnFindLeaderboard()

Параметры — не применимо.
Возвращаемые значения — нет.
Что делает — этот метод представляет собой обратный вызов, который отправляется всякий раз, когда игра пытается найти список лидеров в Steam. Если запрошенный список лидеров был найден, тогда дескриптор списка назначается текущим списком лидеров.
void CSteamLeaderboards::OnFindLeaderboard( LeaderboardFindResult_t *pCallback, bool bIOFailure ) { // проверяем, не было ли ошибки при вызове if ( !pCallback->m_bLeaderboardFound || bIOFailure ) { OutputDebugString( "Leaderboard could not be found\n" ); return; } m_CurrentLeaderboard = pCallback->m_hSteamLeaderboard; }

UploadScore()

Параметры — int32, представляющее собой значение, которое нужно хранить в данном списке лидеров.
Возвращаемые значения — false, если список лидеров ещё не выбран, иначе возвращает true.
Что делает — метод представляет собой обертку для вызова ISteamUserStats::UploadLeaderboardScore, который является асинхронным запросом в Steam, который отправляет счёт текущего пользователя в выбранный в данный момент список лидеров. Этот метод также настраивает используемый метод обратного вызова. Это метод необходимо вызвать после того, как вы выбрали список лидеров с помощью FindLeaderboard().
bool CSteamLeaderboards::UploadScore( int score ) { if (!m_CurrentLeaderboard) return false; SteamAPICall_t hSteamAPICall = SteamUserStats()->UploadLeaderboardScore( m_CurrentLeaderboard, k_ELeaderboardUploadScoreMethodKeepBest, score, NULL, 0 ); m_callResultUploadScore.Set(hSteamAPICall, this, &CSteamLeaderboards::OnUploadScore); return true; }

OnUploadScore()

Параметры — не применимо.
Возвращаемые значения — нет.
Что делает — этот метод представляет собой обратный вызов, который отправляется всякий раз, когда игра пытается отправить счёт в список лидеров в Steam.
void CSteamLeaderboards::OnUploadScore(LeaderboardScoreUploaded_t *pCallback, bool bIOFailure) { if ( !pCallback->m_bSuccess || bIOFailure ) { OutputDebugString( "Score could not be uploaded to Steam\n" ); } }

DownloadScores()

Параметры — не применимо.
Возвращаемые значения — false, если список лидеров ещё не выбран, иначе возвращает true.
Что делает — метод представляет собой обёртку для вызова ISteamUserStats::DownloadLeaderboardEntries, который является асинхронным запросом в Steam для загрузки набора позиций текущего пользователя в выбранном в данный момент списке лидеров. В этом случае мы загружаем 10 позиций: 4 до текущего пользователя, текущий пользователь и 5 после текущего пользователя. Этот вызов можно изменить таким образом, чтобы он возвращал любое число позиций из любого места в списке лидеров. Этот метод также настраивает используемый метод обратного вызова. Этот вызов должен быть осуществлён после того, как вы выбрали список лидеров с помощью FindLeaderboard().
bool CSteamLeaderboards::DownloadScores() { if (!m_CurrentLeaderboard) return false; // загружаем указанные данные списка лидеров вокруг текущего пользователя SteamAPICall_t hSteamAPICall = SteamUserStats()->DownloadLeaderboardEntries( m_CurrentLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, -4, 5); m_callResultDownloadScore.Set(hSteamAPICall, this, &CSteamLeaderboards::OnDownloadScore); return true; }

OnDownloadScore()

Параметры — не применимо.
Возвращаемые значения — нет.
Что делает — этот метод представляет собой обратный вызов, который отправляется всякий раз, когда игра пытается загрузить позиции из списка лидеров в Steam. Если данные загружены, они копируются в массив позиций. Число загруженных позиций хранится в m_nLeaderboardEntries.
void CSteamLeaderboards::OnDownloadScore(LeaderboardScoresDownloaded_t *pCallback, bool bIOFailure) { if (!bIOFailure) { m_nLeaderboardEntries = min(pCallback->m_cEntryCount, 10); for (int index = 0; index < m_nLeaderboardEntries; index++) { SteamUserStats()->GetDownloadedLeaderboardEntry( pCallback->m_hSteamLeaderboardEntries,index,&m_leaderboardEntries[index],NULL,0); } } }

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

Далее следует полный перечень фрагментов кода, которые необходимо интегрировать в игру в соответствующих местах.

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

Далее приведён список подключений, необходимых для объекта Leaderboards, и глобальный указатель на вспомогательный объект.
... #include "steam_api.h" #include "SteamLeaderboards.h" // Глобальный доступ к объекту Leaderboards CSteamLeaderboards* g_SteamLeaderboards = NULL; ...

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

Вызов SteamAPI_Init инициализирует Steam и должен быть выполнен прежде всего. Если вызов был успешен, создаётся вспомогательный объект.
... // инициализируем Steam bool bRet = SteamAPI_Init(); // создаем объект SteamLeaderboards, если инициализация Steam удалась if (bRet) { g_SteamLeaderboards = new CSteamLeaderboards(); } ...

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

Чтобы обрабатывать все обратные вызовы Steam нужно их регулярно слушать. Для этого в игровой цикл добавляется следующий вызов.
... SteamAPI_RunCallbacks(); ...

Выключение

Вызов SteamAPI_Shutdown, который закрывает Steam, скорее всего, уже есть у вас в коде. Он должен быть отправлен до того, как закроется приложение. В конце удаляется ранее созданный вспомогательный объект.
... // Shutdown Steam SteamAPI_Shutdown(); // Delete the SteamLeaderboards object if (g_SteamLeaderboards) delete g_SteamLeaderboards; ...

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 отсутствует в нужной папке. Поместите его в нужное место и убедитесь, что в нём есть AppID.