Esta página ainda não foi traduzida: mostrando a versão em inglês.
Um ranking no Tanks
O Tanks, a arena de exemplo de Primeiros passos, não tem ranking (leaderboard). Nesta lição você adiciona um ranking semanal de abates (kill) em três passos: declarar o ranking, enviar (submit) os abates a partir de um hook e ler o ranking no cliente. Cada passo leva à página do módulo responsável pelo que ele usa.
Declare o ranking
Um ranking é uma declaração: ela diz qual campo define a posição, como vários envios se combinam, quando o ranking reinicia e quem pode enviar. Aggregation.Increment soma cada envio ao total acumulado, então um abate vale um ponto. Submit.ServerOnly é o padrão e fecha o ranking para os clientes — por isso o passo 2 é o único caminho de entrada.
tanks-weekly-kills — abates em ordem decrescente, acumulados, reinício na segunda-feira, envio só pelo servidor[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)Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.
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;
}Publique com playserv push, e o ranking aparece no painel, vazio, com o ciclo de segunda-feira já agendado. Os eixos que você não definiu ficam com o valor padrão. A lista completa de eixos — dono, chave de ordenação, campos exibidos, regras de torneio — está em Leaderboards.
Os horários são em UTC
O reinício semanal acontece na segunda-feira às 00:00 UTC, não no fuso horário dos jogadores.
Envie a partir do hook de abate
O Tanks já encerra uma vida pelo limite de HP declarado no tanque: com HP zerado, a transição death dispara e, logo depois, a plataforma chama o hook. O hook é uma cloud function com entrada e saída tipadas, e enviar um abate dentro dele ocupa uma linha.
[After] no esgotamento do HP envia um abate para quem matou[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)Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.
Um hook é uma cloud function: ele roda na plataforma, não na engine. Escreva em C#, TypeScript ou Python — seu código Unreal assina o evento rank changed que resulta dele. Funções escritas na engine estão planejadas: grafos Blueprint e C++ (traduzido para um formato de script validado pela plataforma). Os dois serão analisados e publicados como qualquer declaração, e a execução continua na plataforma.
Um hook é uma cloud function: ele roda na plataforma, não na engine. Escreva em C#, TypeScript ou Python — seu código Unity assina o evento rank changed que resulta dele.
e.By é o atacante que veio junto com o dano, então você não precisa controlar quem atirou em quem. e.Id é o id do próprio evento, e é disso que um ranking Increment precisa como chave de idempotência: um evento de abate entregue de novo conta uma vez, não duas. O ponto do hook, as garantias de ordem e o contrato de veto estão em Extensibility; o limite que dispara o hook é um preset de stat do tanque, e a linha de pontuação que ele grava são dados comuns, que você pode consultar.
Leia o ranking no cliente
Duas leituras cobrem toda a interface: o topo do ranking e a janela em torno do jogador local — cinco linhas acima, cinco abaixo, mais a sua. As duas voltam como entradas ordenadas, com abates e nome de exibição, prontas para ligar a uma lista. Durante a partida, uma assinatura (subscription) mantém o painel atualizado e entrega só a posição do jogador local.
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))Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.
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));O reinício de segunda-feira fecha o ciclo em vez de apagá-lo, então a tabela da semana passada continua legível pelo rótulo — é a mesma chamada Top com o argumento cycle:. Um hook de recompensa no fechamento do ciclo é o quarto passo natural, descrito em Leaderboards.
Próximos passos
- Leaderboards — os eixos, os ciclos, os torneios e o hook anterior ao envio que limita pontuações suspeitas.
- Extensibility — todos os pontos de hook, em ordem, com o contrato de veto.
- Entity Presets — o limite de stat que disparou o abate no passo 2.
- Kits médicos no Tanks — o outro exemplo do Tanks: duas declarações e um hook.
- Primeiros passos — a arena do Tanks que esta lição amplia.
- Conceitos principais — o vocabulário que toda página de módulo pressupõe.