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.

Locomotion

Você declara como algo se move; ninguém escreve um integrador. Um modelo de movimento transforma input sequenciado em movimento autoritativo, integrado às colisões, gravado para a predição e modificado por buffs, debuffs e terreno.

Quando usar

  • Entidades se movem pelo input do jogador (tanques, personagens, veículos), e o servidor precisa ter a autoridade sobre o movimento.
  • Você prefere declarar limites de velocidade, aceleração e taxa de giro a escrever um integrador.
  • O gameplay empurra corpos de um lado para o outro: knockback com Impulse, Teleport e modificadores com duração, como lama.
  • O movimento precisa parecer instantâneo: o mesmo modelo declarado roda no servidor e no loop da predição.
  • Dispensável quando as posições mudam só em saltos discretos. Um campo sincronizado na entidade já resolve.

Quem faz o quê

AtorO que faz nesta página
schema-authordeclara modelos de movimento, restrições e bindings
room-owneraplica impulso, teleporte e modificadores a partir do host
playerenvia input sequenciado; lê o estado do movimento

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.

Resumo

Declaração do modelo de movimento Tank: Locomotion.Tank com limites de velocidade, aceleração e taxa de giro
[Entity("tank")]
public class Tank
{
    [Sync] public Vector3 Position;
    [Motion(Model.Tank, MaxSpeed = 8f, Acceleration = 14f, TurnRateDeg = 120f)]
    public Motion Motion;
}
@Entity('tank')
export class Tank {
  @Sync() position!: Vector3;
  @Motion({ model: 'tank', maxSpeed: 8, acceleration: 14, turnRateDeg: 120 }) motion: Motion;
}
@entity("tank")
class Tank:
    position: Vector3 = sync()
    motion = locomotion.motion(model="tank", max_speed=8.0, acceleration=14.0, turn_rate_deg=120.0)
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")
class UTank : public UObject
{
    GENERATED_BODY()

    UPROPERTY(PSSync) FVector3f Position;

    UPROPERTY(PSMotion = (Model = "Tank", MaxSpeed = "8.0", Acceleration = "14.0", TurnRateDeg = 120))
    FPSMotion Motion;
};
// 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")]
public class Tank
{
    [Sync] public Vector3 Position;
    [Motion(Model.Tank, MaxSpeed = 8f, Acceleration = 14f, TurnRateDeg = 120f)]
    public Motion Motion;
}

O input do cliente é uma intenção sequenciada. Quem calcula o movimento, passo a passo, é a plataforma:

Input do cliente como intenção sequenciada: Motion.Drive enviado na taxa de input e calculado no servidor
room.My<Tank>().Motion.Drive(throttle: 1f, steer: -0.4f);   // cl — sent at input rate
room.my<Tank>().motion.drive({ throttle: 1, steer: -0.4 });   // cl — sent at input rate
room.my(Tank).motion.drive(throttle=1.0, steer=-0.4)   # cl — a bot brain drives the same way
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.

Room->Entities->Of<UTank>()->Select().GetMine().Then(
    TPSOnResult<UTank*>::CreateWeakLambda(this, [this](const TPSResult<UTank*>& Result)
    {
        if (!Result.HasValue()) { return; }
        // client — sent at input rate, numbered so the platform can acknowledge
        Result.Value()->Motion->SubmitInput(FPSMoveInput{ .Throttle = 1.f, .Steer = -0.4f }, InputSequence);
    }));
// 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
room.My<Tank>().Motion.Drive(throttle: 1f, steer: -0.4f);   // cl — sent at input rate

Verbos do lado do servidor:

Verbos do servidor: um impulso de knockback, um modificador de lama de 3 segundos, um teleporte limpo
tank.Motion.Impulse(knockback);
tank.Motion.Modify("mud", speedMultiplier: 0.6f, duration: 3.Seconds());
tank.Motion.Teleport(spawn);
tank.motion.impulse(knockback);
tank.motion.modify('mud', { speedMultiplier: 0.6, duration: seconds(3) });
tank.motion.teleport(spawn);
tank.motion.impulse(knockback)
tank.motion.modify("mud", speed_multiplier=0.6, duration=seconds(3))
tank.motion.teleport(spawn)
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.

// on a dedicated server / master-client host
Tank->Motion->Impulse(EPSImpulseKind::Impulse, KnockbackVelocity);
Tank->Motion->Modify({ .Modifier = TEXT("mud"), .SpeedMultiplier = 0.6f, .For = FPSDuration::Seconds(3.f) });
Tank->Motion->Teleport(SpawnPosition, SpawnFacing);
// 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 — um build de cliente mestre tem os mesmos verbos de host; um cliente comum vê os resultados como movimento previsto e reconciliado
tank.Motion.Impulse(knockback);
tank.Motion.Modify("mud", speedMultiplier: 0.6f, duration: 3.Seconds());
tank.Motion.Teleport(spawn);

O modelo

O que uma entidade declara para se mover.

DeclaraO que é
modelo de movimentoum do conjunto que já vem pronto — steering, tank, character, vehicle, flying — como várias implementações do mesmo passo, com condições de escolha declaradas e um padrão. O passo é uma função pura (pose, input, dt) → pose
parâmetrosvalores declarados que o cliente consegue ler; sem eles, a predição diverge de forma sistemática. São seed — um designer os ajusta, e um deploy não pode perder essas edições em silêncio —, enquanto os limites em que o anticheat se apoia podem ser managed, e aí uma edição feita no console de administração é recusada
limitesvelocidade, aceleração e frenagem máximas, giro máximo por input, o multiplicador de marcha à ré e uma velocidade de rotação separada para as partes. O giro por input é declarado separado da velocidade de rotação de propósito: um limita um salto instantâneo, o outro uma taxa contínua, e são defesas diferentes
comportamento com input obsoletostop, continue until a declared deadline ou continue indefinitely. Não existe um padrão “seguir como antes” — um jogador que perdeu a conexão continuaria dirigindo
tolerância de poseo quanto a pose informada pelo cliente pode se afastar da pose do servidor; pode variar por estado: parado, em movimento e logo após um respawn são três tolerâncias diferentes
taxa de passos e teto de recuperaçãocom que frequência o passo roda e quantos passos podem ser dados de uma vez quando o servidor está atrasado
tipos de impulsocada um com sua magnitude e sua forma de decaimento

O que vale para todo passo.

SempreO que é
o módulo é dono dea posição e a orientação de uma entidade ao longo do tempo, e de nada mais. O histórico dessas posições fica na entidade, não aqui, então “onde o jogador estava 300 ms atrás” tem exatamente uma resposta, e não dois buffers com períodos diferentes
autoridadeé do servidor: no modo simulação da plataforma, o cliente envia uma intenção, nunca um resultado
colisõesnão são resolvidas aqui: o módulo pergunta às colisões se uma posição é admissível e qual é a resposta, e não mantém uma tabela de respostas própria
inputé uma intenção: “para a frente”, “para a direita”, “vire a torre para lá” — aceito como chega, porque não afirma nada sobre o mundo
uma pose informadaé uma alegação, nunca um fato. Fora da tolerância declarada, ela é ajustada para a pose admissível mais próxima, o que gera um pose_clamped observável
sequenciamento do inputé obrigatório: o mesmo número de sequência nunca é aplicado duas vezes, e um número menor é descartado
um limitecorta, não recusa: “dez metros para a frente neste tick” vira o que for admissível, e não um erro. Assim o servidor não consegue produzir uma pose ilegal, e o cliente não é inundado de recusas a cada frame
um impulso obedece às mesmas restriçõesrecuo, empurrão, explosão e knockback vêm de fora do input, e nenhum deles ignora as colisões: o recuo não enfia um tanque dentro de uma rocha
regras idênticas, não bits idênticosnão há garantia de resultado idêntico bit a bit entre plataformas. O que se garante são as mesmas regras e a reprodutibilidade dentro de uma autoridade
o passoé puro e guiado pelo tick: o mesmo código calcula o movimento no servidor e dentro do loop de predição do cliente, e é isso que torna a reconciliação exata

Erros

  • Entidade sem modelo de movimento, preset preenchido pela metade e impulso sem decaimento declarado são todos falhas de validação no deploy, não em runtime.
  • Geração esperada não confere é uma falha de pré-condição, e vale repetir depois de ler de novo: um input enviado antes de um respawn não pode ser aplicado depois dele.
  • Taxa de input excedida responde na categoria de rate limit, com prazo.
  • Entidade não controlável é um conflito, e repetir só faz sentido depois que o estado mudar.
  • Três coisas não são recusa em nenhum sentido, e as três são observáveis. Input obsoleto é descartado, uma intenção além de um limite é cortada, e uma pose fora da tolerância é ajustada como pose_clamped. Se qualquer uma delas acontecesse em silêncio, o cliente acharia que foi aplicada e divergiria do servidor de vez.

Limites

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

  • Velocidade e aceleração máximas: cortadas, nunca recusadas.
  • Giro máximo por input: cortado.
  • Taxa de input por ator: rate limit com prazo.
  • Passos de recuperação: acima do teto, os passos são descartados com uma consequência declarada. O tempo da simulação fica para trás, de forma observável, em vez de se recuperar num salto que parece todo mundo se teleportando ao mesmo tempo.
  • Magnitude do impulso: cortada no máximo declarado.
  • Impulsos simultâneos por entidade: um novo expulsa o mais antigo, e a remoção é observável; não existe soma ilimitada em silêncio.
  • Validade de uma pose informada: uma pose mais antiga que o período declarado não é considerada.