Эта страница ещё не переведена — показана английская версия.
Таблица лидеров в 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, пока не решено (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[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, пока не решено (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) обновляет таблицу на экране, пока идёт матч, но присылает только место локального игрока.
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, пока не решено (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.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, которую расширяет этот урок.
- Основные понятия — словарь, на который опирается каждая страница модуля.