Pular para o conteúdo principal
Exemplos

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)
Em breve — Go

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
Unity C# is the same C# API here — os mesmos atributos C#; um cliente Unity lê o ranking no passo 3 e não consegue enviar nada para ele
[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.

Um hook [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)
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

Executa fora da engine

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.

Executa fora da engine

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.

Top 20 e cinco linhas em torno de mim, mais uma assinatura da posição ao vivo
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))
Em breve — Go

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.
Unity C# is the same C# API here — roda sem mudanças no Unity com os tipos gerados
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.