Pular para o conteúdo principal
O jogo ao vivo

Esta página ainda não foi traduzida: mostrando a versão em inglês.

Prediction & Lag Compensation

Predição para a frente e compensação para trás, sobre dados que carregam o momento real do evento. O cliente responde na hora, o servidor continua certo, e os acertos são julgados na linha do tempo do atirador.

Quando usar

  • O input precisa parecer instantâneo mesmo com latência, e o servidor continua autoritativo: prevê para a frente e reconcilia quando diverge.
  • Os acertos precisam ser julgados na linha do tempo do atirador: ResolveAt retrocede as hitboxes até o tick de visualização informado.
  • Arcos de mira e marcadores de impacto precisam bater com o resultado: cliente e servidor calculam a mesma Trajectory.
  • Campos críticos para o jogo nunca podem sofrer rollback: declare o que é previsto e o que espera o servidor.
  • O rubber banding precisa de ajuste: janelas por entidade, tolerâncias e telemetria de erros de predição.
  • Dispensável quando a latência não atrapalha. Jogos por turnos ou lentos funcionam bem só com os deltas do Data & Subscriptions.

Quem faz o quê

AtorO que faz nesta página
schema-authordeclara quais campos são previstos e quais são só autoritativos; define a janela de predição
room-ownerresolve acertos sobre um estado histórico; retrocede o mundo
playerprevê e reconcilia o movimento; assina as correções

A quais salas isso se aplica

Este módulo roda onde a plataforma avança a simulação: nas salas (room) declaradas com Host = "Backend". Se o seu próprio servidor de jogo é dono da simulação (PlayServ como metaservidor), movimento, colisões e predição ficam do lado da engine, e esta página descreve a alternativa hospedada pela plataforma, não uma exigência. Veja Quem executa o tick.

Resumo

A palavra cobre três mecanismos distintos, com autoridades e modos de falha diferentes, e cada um tem o seu próprio artigo:

MecanismoO que fazOnde rodaQuando erra
Prever o próprio movimentoaplica o modelo declarado ao seu próprio input sem esperar o servidoro clienteuma correção, reaplicada e suavizada
Como mostrar outros jogadoresdesenha as outras entidades entre os estados que chegamo clienteum tranco visível
Lag Compensationretrocede os alvos até o momento que o atirador viuo servidoralguém morre injustamente

Quatro presets, e “no prediction” é um deles.

PresetPrevê o seuCompensaSuaviza os outros
shootersimnuma janela de mais ou menos um segundo e meiosim
arcadesimnãosim
observernãonãosim
no predictionnãonãonão — o estado chega da autoridade com uma janela de interpolação declarada

Jogos por turnos, de estratégia e a maioria dos títulos para celular não querem predição nenhuma; ao declarar “no prediction”, você diz ao cliente que mostre o estado como ele é, sem adivinhar.

O módulo não tem modelo de movimento, tabela de reações, geometria nem janela de histórico próprios. Eles pertencem, respectivamente, a Locomotion, Collision, Map e Entity. A predição os aplica antes ou os lê de trás para a frente; nunca declara uma segunda cópia.

Predição declarada em Tank: campos previstos, Hp só autoritativo, janela de 8 ticks para a frente / 64 para trás
[Entity("tank")]
[Prediction(ForwardTicks = 8, MaxRewindTicks = 64)]
public class Tank
{
    [Sync, Predicted] public Vector3 Position;                        // rolls back and replays
    [Sync, Predicted] public Vector3 Velocity;

    [Stat(Max = 100), AuthoritativeOnly] public Stat Hp;   // never predicted
}
@Entity('tank')
@Prediction({ forwardTicks: 8, maxRewindTicks: 64 })
export class Tank {
  @Sync() @Predicted() position!: Vector3;                    // rolls back and replays
  @Sync() @Predicted() velocity!: Vector3;

  @Stat({ max: 100 }) @AuthoritativeOnly() hp: Stat;   // never predicted
}
@entity("tank")
@prediction(forward_ticks=8, max_rewind_ticks=64)
class Tank:
    position: Vector3 = sync(predicted=True)                  # rolls back and replays
    velocity: Vector3 = sync(predicted=True)

    hp = stat(max=100, authoritative_only=True)   # never predicted
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.

UCLASS(PSEntity = "tank", PSPrediction = (ForwardTicks = 8, MaxRewindTicks = 64))
class UTank : public UObject
{
    GENERATED_BODY()

    UPROPERTY(PSSync = (Predicted = "true")) FVector3f Position;   // rolls back and replays
    UPROPERTY(PSSync = (Predicted = "true")) FVector3f Velocity;

    UPROPERTY(PSStat = (Max = 100, AuthoritativeOnly = "true")) FPSStat Hp;   // never predicted
};
// 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 compilam no Unity, na versão base 2021.3 — sem sintaxe de C# 12 aqui
[Entity("tank")]
[Prediction(ForwardTicks = 8, MaxRewindTicks = 64)]
public class Tank
{
    [Sync, Predicted] public Vector3 Position;                        // rolls back and replays
    [Sync, Predicted] public Vector3 Velocity;

    [Stat(Max = 100), AuthoritativeOnly] public Stat Hp;   // never predicted
}

A resolução com compensação de lag responde “onde estava todo mundo quando este tiro foi disparado”:

Validação de acerto em um hook: ResolveAt(shooterViewTick) retrocede as hitboxes até o que o atirador via
[After(Projectiles.HitReported)]
public static void Validate(HitReport hit) =>
    hit.ResolveAt(hit.ShooterViewTick);    // rewinds hitboxes, sub-tick interpolated
export const validate = after(Projectiles.hitReported, (hit: HitReport) =>
  hit.resolveAt(hit.shooterViewTick));    // rewinds hitboxes, sub-tick interpolated
@after(projectiles.hit_reported)
def validate(hit: HitReport):
    hit.resolve_at(hit.shooter_view_tick)    # rewinds hitboxes, sub-tick interpolated
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 executa na plataforma, não na engine. Escreva em C#, TypeScript ou Python — o Unreal assina os eventos resultantes. Funções escritas na engine estão planejadas: grafos de Blueprint e C++ (traduzido para um alvo de script validado pela plataforma). Os dois são analisados e publicados como qualquer declaração; a execução continua na plataforma.

Executa fora da engine

Um hook é uma cloud function: ele executa na plataforma, não na engine. Escreva em C#, TypeScript ou Python — o Unity assina os eventos resultantes.

Previsão de trajetória, compartilhada por servidor e cliente (arcos de mira, marcadores de impacto). A previsão é uma operação deste módulo, extrapolada contra o conjunto de obstáculos do mapa, então os dois lados desenham o mesmo arco a partir dos mesmos dados de entrada:

Uma chamada a Trajectory: uma previsão que considera colisões, compartilhada pelo servidor e pela prévia da mira
var arc = room.Prediction.Trajectory(from, velocity, steps: 30);   // collision-aware
const arc = room.prediction.trajectory(from, velocity, { steps: 30 });   // collision-aware
arc = room.prediction.trajectory(origin, velocity, steps=30)   # collision-aware
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.

// collision-aware: the arc the platform itself would walk
Room->Prediction->Trajectories->Get(LaunchPosition, LaunchVelocity, /*Steps*/ 30,
    TPSOnResult<FPSTrajectory>::CreateWeakLambda(this, [this](const TPSResult<FPSTrajectory>& Result)
    {
        if (!Result.HasValue()) { return; }
        DrawArc(Result.Value());
    }));
// 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 como está no Unity, com os tipos gerados
var arc = room.Prediction.Trajectory(from, velocity, steps: 30);   // collision-aware

O modelo

A previsibilidade é declarada em um aspecto — e um aspecto que só a autoridade altera, por regras que o cliente não tem, não pode ser marcado como previsível: isso é uma falha de validação no deploy, não uma surpresa em runtime.

O que se declara.

DeclaraO que é
aspectos previsíveisquais o cliente pode calcular à frente da autoridade
limiar de divergênciaabaixo dele, a correção é suavizada; acima, o estado da autoridade é aceito como chega. Declarado e managed — não é um ajuste que o designer mexe todo dia
modo de exibição de entidades remotasinterpolação entre os estados que chegaram, ou extrapolação
atraso de interpolaçãoquanto a exibição dos outros fica atrasada; é declarado, não ajustado no olho
janela de extrapolaçãoalém dela, a entidade é marcada como desatualizada e a extrapolação para
janela de compensaçãoaté onde um retrocesso pode voltar; é managed

O que é retrocedido e o que, de propósito, não é.

O que é
retrocedidoas posições e orientações dos alvos, e a geometria dos obstáculos dinâmicos quando o tipo os declara históricos
não retrocedidoo estado de vida — quem morreu não revive para levar o tiro —, além de posse, pontuação e inventário
a regra por trás da divisãoa decisão é tomada no passado; o efeito é aplicado no presente
SempreO que é
o estado autoritativo informa o input que viuele traz o número do último input aplicado, e é isso que torna a reconciliação exata, e não aproximada
divergênciaé observável: o cliente sabe que a predição foi corrigida, em vez de ir se desviando em silêncio
um momento de visualizaçãoé uma alegação, não um fato: o momento que o ator diz ter visto. Além da janela, a plataforma recusa em vez de extrapolar: extrapolar em silêncio é um presente para o trapaceiro, que só precisaria enviar um momento mais antigo
um retrocesso não garante reprodutibilidade em ponto flutuantea mesma restrição que vale em todo o resto do contrato
um anel de histórico, dois consumidoresa reconciliação e as consultas com compensação de lag leem a mesma trilha de histórico instantâneo da entidade. O retrocesso fica aqui, e não nos módulos retrocedidos: o anel restaura as poses do tick em disputa, e então as colisões respondem à pergunta de sobreposição de sempre sobre essas poses — sem histórico próprio e sem saber nada de “tick de visualização”
client tick:  apply input locally (predict) → buffer it → send, tick-stamped
server tick:  step the same movement model → authoritative state → delta out
client recv:  authoritative state for tick T → if divergence beyond tolerance:
              rewind to T → replay buffered inputs T+1..now → smooth

Só os campos declarados como previstos sofrem rollback; um desvio mínimo é suavizado, e uma divergência real volta no tempo e reaplica os inputs.

Erros

  • Momento de visualização fora da janela é um conflito: envie um atual.
  • Pedido de histórico fora da janela também é um conflito.
  • Dois defeitos de declaração são pegos no deploy: uma janela de compensação maior que o buffer de histórico e um aspecto marcado como previsível sem que o cliente tenha regras para prevê-lo. Nenhum dos dois chega a uma partida ao vivo.
  • Duas coisas não são erros, e as duas são observáveis. Um buffer de input cheio suspende a predição até a confirmação, em vez de descartar inputs em silêncio. E uma divergência acima do limiar faz o estado da autoridade ser aceito como chega: essa é a correção declarada, não uma falha.

Limites

Os números chegam com o capítulo de limites da plataforma; o que acontece em cada teto já está definido.

  • Janela de compensação: além dela, recusa, nunca extrapolação.
  • Janela de extrapolação para os outros: a entidade é marcada como desatualizada e a extrapolação para.
  • Buffer de inputs não confirmados: a predição é suspensa até a confirmação; inputs nunca são descartados em silêncio.
  • Profundidade do histórico para um retrocesso: no mínimo a janela de compensação, verificado no deploy.
  • Taxa de ações com momento de visualização: rate limit com prazo.
  • Entidades previstas ao mesmo tempo por cliente: acima do teto, a predição não é feita, e isso é uma degradação declarada, não uma recusa.