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,Teleporte 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ê
| Ator | O que faz nesta página |
|---|---|
schema-author | declara modelos de movimento, restrições e bindings |
room-owner | aplica impulso, teleporte e modificadores a partir do host |
player | envia 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
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)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[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:
Motion.Drive enviado na taxa de input e calculado no servidorroom.My<Tank>().Motion.Drive(throttle: 1f, steer: -0.4f); // cl — sent at input rateroom.my<Tank>().motion.drive({ throttle: 1, steer: -0.4 }); // cl — sent at input rateroom.my(Tank).motion.drive(throttle=1.0, steer=-0.4) # cl — a bot brain drives the same wayAinda 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.room.My<Tank>().Motion.Drive(throttle: 1f, steer: -0.4f); // cl — sent at input rateVerbos do lado do servidor:
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)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.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.
| Declara | O que é |
|---|---|
modelo de movimento | um 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âmetros | valores 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 |
limites | velocidade, 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 obsoleto | stop, 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 pose | o 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ção | com que frequência o passo roda e quantos passos podem ser dados de uma vez quando o servidor está atrasado |
tipos de impulso | cada um com sua magnitude e sua forma de decaimento |
O que vale para todo passo.
| Sempre | O que é |
|---|---|
o módulo é dono de | a 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ões | nã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 limite | corta, 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ções | recuo, 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ênticos | nã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.