Документация Steamworks
Статистика и достижения

Введение

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

Назначение

В дополнение к наградам для игроков, достижения используются для поощрения командной игры и взаимодействия между игроками, а также добавляют дополнительное измерение к целям игры, вознаграждая игроков за пребывание в игре в течение долгого времени.

Статистика отслеживает точно определённые фрагменты информации, такие как игровое время, число потраченных бонусов и т. д. Их можно использовать и просто для отслеживания внутриигровых данных. К примеру, пользователю можно выдать достижение на основании статистики, собранной во время игры в течение нескольких сессий на разных компьютерах.

Обзор реализации

Определение статистики и достижений

Достижения определяются для конкретного приложения и устанавливаются на странице управления приложением на партнёрском сайте Steamworks.

Есть три типа статистики, которые может хранить игра:
  • INT - 32-битное целое число со знаком (к примеру, число сыгранных игр)
  • FLOAT - 32-битное число с плавающей точкой (к примеру, число пройденных автомобилем миль)
  • AVGRATE - скользящее среднее. См. AVGRATE-статистика

На сайте Steamworks доступен интерфейс для определения и обновления статистик и достижений, с помощью которого выполняются следующие действия:
  • Определение начальных статистик и достижений
  • Добавление статистик и достижений
  • Обновление названий, описаний и иконок достижений
  • Обновление параметров статистик и их ограничений (максимальное и минимальное значения, размер окна для скользящего среднего и т. д.)
Статистики обладают следующими свойствами:
  • ID — автоматически генерируемый номер для каждого достижения.
  • Type — INT, FLOAT или AVGRATE.
  • Название для API — строка, используемая API при доступе к этой статистике.
  • Установлено — показывает, кто может задавать статистику. По умолчанию — клиент. Больше информации здесь.
  • Только приращение — если установлено, этой статистике позволено только увеличиваться с течением времени.
  • Максимальное изменение — если установлено, накладывает ограничение на изменение значения в результате одного вызова SetStat.
  • Минимальное значение — если установлено, определяет минимум для значения статистики. По умолчанию равно минимуму для соответствующего типа числа (INT_MIN или -FLT_MAX).
  • Максимальное значение — если установлено, определяет максимум для значения статистики. По умолчанию равно максимуму для соответствующего типа числа (INT_MAX или FLT_MAX).
  • Стандартное значение — если установлено, новый пользователь получит это значение по умолчанию для данной статистики. Если не установлено, равно нулю.
  • Суммарное — если установлено, Steam будет хранить общее суммарное значение для этой статистики. См. ниже раздел «Общие статистики».
  • Название для отображения — это название отображается в вашем приложении.
Статистики AVGRATE могут обладать дополнительными свойствами:
  • Окно — размер скользящего окна, используемого для усреднения.
Статистика типа AVGRATE автоматически усредняется. См. раздел AVGRATE ниже.

Достижения обладают следующими свойствами:
  • ID — автоматически генерируемый номер для каждого достижения.
  • Название для API — строка, используемая API при доступе к этому достижению.
  • Прогресс статистики — отсылает к статистике, используемой в сообществе как индикатор прогресса для этого достижения. Достижение автоматически разблокируется, если статистика достигает значения разблокировки.
  • Название для отображения — это название показывается в уведомлениях клиента и в сообществе. Может быть локализовано.
  • Описание — описание достижения, которое показывается в сообществе. Может быть локализовано.
  • Установлено — показывает, кто может разблокировать достижение. По умолчанию — клиент. Больше информации здесь.
  • Скрытое? — если установлено, «скрытое» достижение (совсем) не показывается на странице пользователя в сообществе, пока он не разблокирует его.
  • Иконка полученного — показывается, когда достижение получено.
  • Иконка неполученного — показывается до тех пор, пока достижение не получено.

Далее показаны достижения из Spacewar (образец приложения)::
achievements_spacewar.png

Особые положения

  • Названия и иконки достижений должны подходить всем возрастам.
  • По умолчанию изначально в играх не может быть более 100 достижений. Когда приложению станут доступны функции профиля, вы сможете добавить новые достижения.

Использование статистик и достижений

Получение статистик и достижений в игре:

  • После инициализации API Steamworks можно начать использовать API статистик и достижений, который находится в ISteamUserStats.
  • В начале игровой сессии для получения данных пользователя с сервера Steam вызывается ISteamUserStats::RequestCurrentStats. Когда данные будут готовы, будет получен обратный вызов ISteamUserStats::UserStatsReceived_t.
  • Используйте ISteamUserStats::GetStat и ISteamUserStats::GetAchievement для обработки данных и инициализации состояния игры.
  • Если вы хотите отображать достижения в игре, вы можете использовать ISteamUserStats::GetAchievementDisplayAttribute для извлечения свойств достижений, удобных для воприятия человеком, включая название ("name") и описание ("desc"). Эти свойства можно локализовать на партнёрском сайте Steamworks, и тогда возвращаемые данные будут зависеть от языка, на котором запущена игра. Также возможно получить иконку достижения (ISteamUserStats::GetAchievementIcon) или время, в которое было разблокировано то или иное достижение (ISteamUserStats::GetAchievementAndUnlockTime).
  • Когда та или иная статистика изменяется, ещё до того как показать изменения пользователю, вызывается ISteamUserStats::SetStat или ISteamUserStats::UpdateAvgRateStat. Эти вызовы изменяют только состояние памяти Steam и требуют мало ресурсов. Это позволяет Steam сохранять изменения между сессиями даже в случае сбоя игры.
  • В соответствующих точках в игре (чекпоинты, переходы между уровнями) вызывается ISteamUserStats::StoreStats для передачи изменений на сервер. После выполнения будет получен обратный вызов ISteamUserStats::UserStatsStored_t.
  • В случае достижений с индикаторами прогресса для отображения всплывающего уведомления в тех точках, где нужно показать продвижение игрока, используется ISteamUserStats::IndicateAchievementProgress. К примеру, если пользователю для достижения нужно 20 побед, можно вызвать IndicateAchievementProgress после 10 побед, чтобы показать ему, что он на полпути.
  • Когда одно или несколько достижений разблокированы, вызывается ISteamUserStats::SetAchievement для каждого из них, а потом сразу ISteamUserStats::StoreStats для передачи на сервер. Игра получит обратный вызов ISteamUserStats::UserAchievementStored_t для каждого полученного достижения. В оверлее пользователю будет показано соответствующее уведомление.

AVGRATE-статистика

Статистика этого типа предоставляет доступ к уникальным и полезным возможностям, которые объясняются здесь чуть подробнее.

Предположим, нужно отслеживать среднюю статистику, такую как «Число очков в час». Можно подойти к этому так: хранить две статистики, INT "TotalPoints" (всего очков) и FLOAT "TotalPlayTimeHours" (всего часов игрового времени), и затем поделить очки на время.

Недостаток данного подхода заключается в том, что как только игрок наиграл значительное количество времени, изменения среднего значения будут меняться чрезвычайно медленно. Чем больше играет пользователь, тем меньше меняется среднее значение. Если он наиграл 100 часов, исчисляемое среднее будет «отставать» на 50 часов. При этом если навыки игрока улучшились, он не увидит ожидаемый прирост показателя «Число очков в час».

AVGRATE позволяет задать «скользящее окно» для среднего значения. К примеру, для расчётов можно брать только несколько последних часов игры, чтобы статистика должным образом отражала текущий уровень навыка игрока.

Для того чтобы задать AVGRATE-статистику «Число очков в час», где будут учитываться только 20 последних часов игры, потребуется следующее:
  • Обратите внимание на то, что поскольку среднее будет принимать значение «в час», единицей времени всех переменных, связанных с этой статистикой, должны быть «часы». Это применимо как к свойству «Окно» статистики, так и к параметру dSessionLength, передаваемому в UpdateAvgRateStat ниже.
  • Создаётся AVGRATE-статистика под названием AvgPointsPerHour, свойству «Окно» устанавливается значение 20 (в часах).
  • В подходящие моменты игры вызывается ISteamUserStats::UpdateAvgRateStat со следующими параметрами:
    • pchName — AvgPointsPerHour (среднее количество очков в час).
    • flCountThisSession — число очков, заработанных игроком с последнего вызова UpdateAvgRateStat.
    • dSessionLength — игровое время, прошедшее с последнего вызова UpdateAvgRateStat. Единица измерения та же, что и «Окно», т. е., часы.
  • К примеру, если игрок заработал 77 очков в последнем раунде, который продлился 0,225 часов (13,5 минут), это будет SteamUserStats()->UpdateAvgRateStat( "AvgPointsPerHour", 77, 0.225 )
В этом примере Steam возьмёт среднее значение текущего раунда, которое равно 342,2 очка в час (77 / 0,225), и соединит с предыдущим значением. Результат будет отображать среднее за последние 20 часов игры. Если статистика является новой для данного игрока, текущее значение составит 342,2.

В данном примере за единицу времени принимаются часы, но можно использовать и любые другие. Помните, что эта единица должна использоваться для всех вычислений: как для dSessionLength, так и для «Окна».

Получение статистик других игроков

Для запроса статистик других игроков используется ISteamUserStats::RequestUserStats. Затем для получения данных используются ISteamUserStats::GetUserStat, ISteamUserStats::GetUserAchievement и ISteamUserStats::GetUserAchievementAndUnlockTime. При отправке другим игроком на сервер автоматически эти данные не обновляются, так что для обновления данных потребуется снова вызвать ISteamUserStats::RequestUserStats.

Чтобы избежать перегрузки памяти используется алгоритм вытеснения давно неиспользуемого, так что статистики других игроков будут периодически выгружаться. При этом будет автоматически отправлен обратный вызов ISteamUserStats::UserStatsUnloaded_t. Статистики указанного игрока будут недоступны, пока ISteamUserStats::RequestUserStats не будет вызван заново.

Автономный режим

Steam хранит локальный кэш статистик и достижений, чтобы данные могли использоваться даже в автономном режиме. Не отправленные на сервер статистики сохраняются до тех пор, пока пользователь не зайдёт в сеть. Если изменения произошли на нескольких компьютерах, Steam автоматически объединит достижения и выберет тот набор статистик, в котором произошло наибольшее продвижение. Поскольку Steam хранит локальный кэш статистик, игре создавать аналогичный кэш необязательно. Подобное дублирование может привести к конфликту, и если это произойдет, пользователь может подумать, что он отброшен назад, и расстроиться.

Статистики игровых серверов

Параллельно с ISteamUserStats существует ISteamGameServerStats для игровых серверов. Эти вызовы могут получать статистики пользователей так же, как и клиенты (как описано выше). Они также могут устанавливать статистики и давать пользователям достижения, но только если для параметра «установлено» выбраны значения «официальные игровые серверы» (Official GS) или «игровые серверы» (GS). Разница между ними в том, что официальные игровые серверы — это те, которые принадлежат вам и контролируются вами. Использование официальных игровых серверов позволяет увеличить уровень защиты против читеров, так как пользователи могут изменять собственные серверы или сфабриковать данные на них. Чтобы указать, какие серверы являются официальными, их IP-адреса вводятся здесь.

Статистики и достижения, устанавливаемые на игровых серверах, не могут быть установлены клиентами. Игровые серверы могут устанавливать статистики и достижения только для пользователей, играющих на сервере. Если пользователь покидает сервер, есть короткий период, в течение которого можно установить новые статистики, но после этого новые загрузки данных на сервер будут запрещены. Это обеспечивает непротиворечивость и помогает избежать ситуаций, когда неисправный сервер может установить статистику для кого угодно в любое время. Учитывая это ограничение, важно не дожидаться окончания раунда для обновления статистик, а устанавливать их последовательно, чтобы они сохранялись после выхода пользователей из игры.

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

Сброс статистик

Во время разработки может потребоваться полностью сбросить статистики и достижения на одном или всех аккаунтах. Для сброса статистики на одном аккаунте вызывается ISteamUserStats::ResetAllStats. Чтобы удалить и достижения, для bAchievementsToo устанавливается значение true. После вызова нужно пройти по всем достижениям и статистикам, чтобы сбросить их состояние в памяти игры. Для всех пользователей удалить статистики и достижения нельзя. Одна из причин, почему это нельзя сделать, заключается в том, что даже если бы подобный общий сброс и состоялся, активные на тот момент игры могли бы этого не заметить и записать имеющиеся у них значения заново. Однако есть простой способ встроить систему общего сброса в игру. Для этого потребуется следующее:
  • При определении статистики в её название добавляется «версия номер такая-то» ("Version").
  • Этот встроенный в код номер используется в игре.
  • Как только у игрока появляются изменения в статистике, его версия сравнивается с номером версии статистики в игре.
  • Если они не совпадают, вызывается ISteamUserStats::ResetAllStats и для игрока устанавливается «версия статистики с нужным номером».
Таким образом, для общего сброса требуется просто поменять встроенный номер версии. Когда пользователи получат новую сборку, произойдет общий сброс.

Непротиворечивость статистик

Рекомендуется принимать во внимание тот факт, что связанные статистики могут начать противоречить друг другу. К примеру, в игре могут быть три статистики: «Число выигранных игр», «Число проигранных игр», «Число сыгранных игр». Несмотря на старания, статистики всё равно могут рассинхронизироваться друг с другом. В данном примере это может привести к ситуации, когда сумма выигранных и проигранных игр не будет равна числу сыгранных игр. Если устранить эту проблему, удалив «Число проигранных игр» и заменив этот показатель разницей между числом сыгранных и выигранных игр, может возникнуть ситуация, когда число проигранных игр будет отрицательным. Поэтому лучше не использовать «Число сыгранных игр» и вычислять его как сумму выигранных и проигранных игр.

Общая статистика

Статистику можно пометить как суммарное значение на странице управления приложением, чтобы указать Steam на необходимость хранения общего суммарного значения для всех пользователей. Оно может использоваться для получения данных о количестве денег в экономике, числе убийств, предпочитаемом оружии, любимых локациях, а также о том, какие команды лучше играют. С другой стороны, этот параметр нельзя использовать для таких статистик как «Больше всего убийств», поскольку его суммирование для множества игроков не имеет смысла. Поскольку статистики находятся в руках пользователей, этими данными часто манипулируют. Как следствие, крайне важно при использовании суммарных значений установить рамки для минимального и максимального значений, максимального изменения, а также, если применимо, установить параметр «только приращение». Максимальное изменение имеет особое значение для суммарных статистик. Когда на сервер отправляется новое значение, общее значение изменится не больше, чем значение максимального изменения. Это ограничивает скорость, с которой читер может повлиять на общие статистики.

Для доступа к суммарным значениям вызывается ISteamUserStats::RequestGlobalStats и затем ISteamUserStats::GetGlobalStat для каждой общей статистики. При вызове ISteamUserStats::RequestGlobalStats также можно запросить данные за определённый период времени. Исторические данные показывают значение изменения данной статистики на тот или иной день. Их можно получить, вызвав ISteamUserStats::GetGlobalStatHistory.

Запрос на получение процентов выполнения всех достижений можно отправить и из клиента. Для этого сначала вызывается ISteamUserStats::RequestGlobalAchievementPercentages. Затем при вызове ISteamUserStats::GetMostAchievedAchievementInfo и ISteamUserStats::GetNextMostAchievedAchievementInfo выполняется итерация по порядку от наиболее выполненных к наименее выполненным. Также можно получить процент выполнения того или иного достижения, вызвав ISteamUserStats::GetAchievementAchievedPercent.

Тестирование


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

Вы можете очистить достижения или статистику в игре без внесения изменений в код с помощью консоли клиента Steam. Запустите steam.exe с -console, а затем введите:
  • achievement_clear <appid> <achievement name>
  • reset_all_stats <appid>

Сообщество Steam

После выхода игры информация об индивидуальном и общем продвижении и достижениях будет отображаться на страницах в сообществе. В профиле каждого игрока будет ссылка на страницу, при переходе на которую будет виден список выполненных и невыполненных достижений.
Обратите внимание: достижения для игры не будут показаны до тех пор, пока приложение не станет так или иначе видимым в сообществе.

Каждое достижение выводится с соответствующей иконкой, названием и описанием, указанными в Steamworks. Если название и описание были переведены на язык, который выбран пользователем, они показываются на этом языке.

С этой страницы и со страницы игры в магазине, также есть ссылки на общую статистику достижений игры. На ней показывается, какой процент игроков Steam получил то или иное достижение, при этом достижения будут отсортированы от самого часто выполняемого к самому редко выполняемому. Это интересно игрокам, а также является отличным ресурсом для разработчика: достаточно ли трудны особые задачи, поставленные перед игроками? Или, может быть, они чересчур тяжелы? Эта информация также доступна на сайте с отчётами о продажах и активациях.

Остались вопросы?

Задайте их в соответствующем разделе обсуждений группы разработчиков
  翻译: