Перейти к основному содержимому
Примеры

Эта страница ещё не переведена — показана английская версия.

Таблица лидеров в Tanks

В Tanks — учебной арене со страницы Первые шаги — таблицы лидеров (leaderboard) пока нет. В этом уроке мы добавим еженедельную таблицу фрагов. Шагов три: объявить таблицу, отправлять в неё фраги из хука и прочитать её на клиенте. Каждый шаг ссылается на страницу модуля, которому принадлежит то, чем вы в нём пользуетесь.

Объявите таблицу

Таблица — это объявление (declaration). В нём сказано, по какому полю (field) строится рейтинг, как складываются повторные отправки, когда таблица сбрасывается и кому разрешено в неё отправлять. Aggregation.Increment прибавляет каждую отправку к накопленной сумме, так что один фраг — одно очко. Submit.ServerOnly действует по умолчанию и закрывает таблицу для клиентов. Поэтому попасть в неё можно только так, как показано на шаге 2.

tanks-weekly-kills — фраги по убыванию, очки суммируются, сброс по понедельникам, отправляет только сервер
[Leaderboard("tanks-weekly-kills")]
public static class WeeklyKills
{
    public static Owner Owner = Owner.Player;
    public static Aggregation Agg = Aggregation.Increment;
    public static Reset Reset = Reset.Weekly(DayOfWeek.Monday);
    public static Submit Submit = Submit.ServerOnly;

    [Rank(1, Sort.Descending)] public static int Kills;
}
@Leaderboard('tanks-weekly-kills')
export class WeeklyKills {
  static owner = Owner.Player;
  static agg = Aggregation.Increment;
  static reset = Reset.weekly(DayOfWeek.Monday);
  static submit = Submit.ServerOnly;

  @rank(1, Sort.Descending) static kills: number;
}
@leaderboard("tanks-weekly-kills")
class WeeklyKills:
    owner = Owner.PLAYER
    agg = Aggregation.INCREMENT
    reset = Reset.weekly(DayOfWeek.MONDAY)
    submit = Submit.SERVER_ONLY

    kills: int = rank(1, Sort.DESCENDING)
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

USTRUCT(PSLeaderboard = (Name = "tanks-weekly-kills", Owner = "Player", Aggregation = "Increment",
                        Reset = "Weekly:Monday", Submit = "ServerOnly"))
struct FWeeklyKills
{
    GENERATED_BODY()

    UPROPERTY(PSRank = (Order = 1, Sort = "Descending")) int32 Kills;
};
// declarations compile into the same pushed model — playserv push from the UE project or CI
Unity C# is the same C# API here — те же атрибуты C#. Клиент на Unity читает таблицу на шаге 3, а отправлять в неё не может
[Leaderboard("tanks-weekly-kills")]
public static class WeeklyKills
{
    public static Owner Owner = Owner.Player;
    public static Aggregation Agg = Aggregation.Increment;
    public static Reset Reset = Reset.Weekly(DayOfWeek.Monday);
    public static Submit Submit = Submit.ServerOnly;

    [Rank(1, Sort.Descending)] public static int Kills;
}

Загрузите объявление командой playserv push — и в панели появится пустая таблица с уже запланированным недельным циклом до понедельника. Оси, которые вы не задали, остаются со значениями по умолчанию. Полный список осей — владелец, ключ сортировки, отображаемые поля, правила турнира — на странице Leaderboards.

Расписание — по UTC

Еженедельный сброс происходит в понедельник в 00:00 UTC, а не по часовому поясу игроков.

Отправляйте фраги из хука

Жизнь танка в Tanks уже заканчивается по порогу HP, который объявлен на самом танке. Когда HP падает до нуля, срабатывает переход death, и сразу после него платформа вызывает хук. Хук — это облачная функция с типизированными входом и выходом, поэтому отправка фрага занимает в нём одну строку.

Хук [After] на обнуление hp засчитывает один фраг тому, кто уничтожил танк
[After(Stats.Depleted, stat: "hp")]
public static Task SubmitKill(StatEvent e) =>
    PlayServ.Leaderboards.Submit("tanks-weekly-kills", e.By.PlayerId,
        kills: 1, idempotencyKey: e.Id);
export const submitKill = after(Stats.depleted, { stat: 'hp' }, (e: StatEvent) =>
  PlayServ.leaderboards.submit('tanks-weekly-kills', e.by.playerId,
    { kills: 1, idempotencyKey: e.id }));
@after(stats.depleted, stat="hp")
async def submit_kill(e: StatEvent):
    await playserv.leaderboards.submit("tanks-weekly-kills", e.by.player_id,
        kills=1, idempotency_key=e.id)
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

Выполняется вне движка

Хук — это облачная функция: он выполняется на платформе, а не в движке. Напишите его на C#, TypeScript или Python, а ваш код на Unreal подпишется на событие rank changed, которое он порождает. В планах — функции, написанные прямо в движке: графы Blueprint и C++, который будет транслироваться в скрипт, проверяемый платформой. Их будут анализировать и загружать так же, как любые объявления, а выполняться они по-прежнему будут на платформе.

Выполняется вне движка

Хук — это облачная функция: он выполняется на платформе, а не в движке. Напишите его на C#, TypeScript или Python, а ваш код на Unity подпишется на событие rank changed, которое он порождает.

e.By — атакующий, которого урон уже несёт в себе, поэтому отдельно вести учёт того, кто в кого стрелял, не нужно. e.Id — идентификатор самого события (event). Таблице с Increment нужен именно он в роли ключа идемпотентности: если событие о фраге доставят повторно, оно засчитается один раз, а не два. Точки вызова хуков, гарантии порядка и правила вето описаны на странице Extensibility. Порог, по которому срабатывает хук, — это пресет характеристики (stat) на танке, а строка со счётом, которую хук записывает, — обычные данные, и к ним можно делать запросы.

Прочитайте таблицу на клиенте

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

Топ-20, пять строк вокруг меня и живая подписка на своё место
var top    = await playserv.Leaderboards.Top("tanks-weekly-kills", 20);
var around = await playserv.Leaderboards.AroundMe("tanks-weekly-kills", 5);

playserv.Leaderboards.OnRankChanged("tanks-weekly-kills", r => UpdateHud(r));
const top    = await playserv.leaderboards.top('tanks-weekly-kills', 20);
const around = await playserv.leaderboards.aroundMe('tanks-weekly-kills', 5);

playserv.leaderboards.onRankChanged('tanks-weekly-kills', (r) => updateHud(r));
top    = await playserv.leaderboards.top("tanks-weekly-kills", 20)
around = await playserv.leaderboards.around_me("tanks-weekly-kills", 5)

playserv.leaderboards.on_rank_changed("tanks-weekly-kills", lambda r: update_hud(r))
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

Client->Leaderboards->Of<FWeeklyKills>()->Get(
    TPSOnResult<FPSBoard*>::CreateWeakLambda(this, [this](const TPSResult<FPSBoard*>& Result)
    {
        if (!Result.HasValue()) { return; }
        OnBoard(Result.Value());
    }));

// in OnBoard(FPSBoard* Board):
Board->Entries->Select().Page(20).Then(
    TPSOnResult<TPSPage<FPSLeaderboardEntry>>::CreateWeakLambda(this, [this](const TPSResult<TPSPage<FPSLeaderboardEntry>>& Top)
    {
        if (!Top.HasValue()) { return; }
        Hud->ShowTop(Top.Value().Rows);
    }));

Board->Entries->SelectAround(MyPlayerId, /*Radius*/ 5,
    TPSOnResult<TArray<FPSLeaderboardEntry>>::CreateWeakLambda(this, [this](const TPSResult<TArray<FPSLeaderboardEntry>>& Around)
    {
        if (!Around.HasValue()) { return; }
        Hud->ShowWindow(Around.Value());
    }));

TPSSubscription MyRank = Board->Subscribe->Mine(
    [this](const FPSLeaderboardEntry& Mine) { UpdateHud(Mine); });
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — работает в Unity без изменений, со сгенерированными типами
var top    = await playserv.Leaderboards.Top("tanks-weekly-kills", 20);
var around = await playserv.Leaderboards.AroundMe("tanks-weekly-kills", 5);

playserv.Leaderboards.OnRankChanged("tanks-weekly-kills", r => UpdateHud(r));

Сброс в понедельник закрывает цикл, а не удаляет его, поэтому таблицу прошлой недели можно прочитать по её метке — тем же вызовом Top с аргументом cycle:. Естественный четвёртый шаг — хук, который выдаёт награды при закрытии цикла. Он описан на странице Leaderboards.

Что дальше

  • Leaderboards — оси, циклы, турниры и хук перед отправкой, который урезает подозрительные результаты.
  • Extensibility — все точки вызова хуков по порядку и правила вето.
  • Entity Presets — порог характеристики, из-за которого на шаге 2 засчитался фраг.
  • Аптечки в Tanks — второй пример на Tanks: два объявления и один хук.
  • Первые шаги — арена Tanks, которую расширяет этот урок.
  • Основные понятия — словарь, на который опирается каждая страница модуля.