Como funciona a previsão do lado do cliente?

33

Eu li Valve + Gafferon e centenas de páginas do Google, mas, por qualquer motivo, não consigo entender a previsão do cliente.

Para meu entendimento, o problema básico é:

  • O cliente A envia entrada em T0
  • O servidor recebe entrada em T1
  • Todos os clientes recebem a alteração em T2

No T2entanto, usando a previsão do cliente, o Cliente A agora está em uma posição apropriada para T4.

Como você garante que o Cliente A, ao prever que o servidor aceitará a solicitação de movimentação, não esteja à frente do servidor? Obviamente, sempre que estão à frente, isso resulta em voltar ao local em que o servidor os viu pela última vez. Com todas as correções que eu tentei, isso ainda é perceptível quando você para, porque o servidor para atrás de você

Chris Evans
fonte

Respostas:

35

Eu escrevi uma série de artigos sobre isso. É baseado nas mesmas idéias que você leu em outro lugar, mas explicado de uma maneira muito detalhada e (espero) acessível.

Em particular, o artigo sobre a previsão do lado do cliente é este .

ggambett
fonte
Artigos excelentes :-) Adoraria ver a quarta parte da série. Como uma pequena sugestão, um link para a próxima parte no final de cada um dos artigos certamente melhoraria a navegação.
OR Mapper
5
@ORMapper - eu finalmente escrevi o quarto artigo! gabrielgambetta.com/fpm4.html
ggambett
Parabéns pela sua série de artigos :-) Muito útil, obrigado :-)
OR Mapper
Todos os artigos (que pude encontrar) que falam sobre a reconstrução do passado usando instantâneos armazenados tomam o exemplo. Isso também se aplica ao movimento? Eu posso imaginar que a ressimulação de movimento pode levar a grandes diferenças para outros jogadores se eles puderem colidir entre si. Digamos que dois jogadores se movam um contra o outro e um deles para de mover alguns "degraus" para longe do ponto de colisão. Este comandos de parada chega atrasado por causa do lag por isso, se nós mundo resimulate, os dois jogadores estariam em posições muito diferentes
Lope
Essa é uma pergunta interessante. Infelizmente, não tenho uma resposta definitiva. Eu acho que depende de quão críticos são os movimentos para o jogo; você esbarra em outra pessoa e nada acontece? Nesse caso, o servidor provavelmente não se importa, é visto como um erro de previsão (todos nós vimos isso acontecer em pontos de estrangulamento, certo?). Você mata o outro jogador em contato? Nesse caso, acertar é muito mais importante e pode valer a pena revitalizá-lo. Observe que em algum momento você precisa descartar alguns pacotes como "muito antigos"; caso contrário, você estaria revivendo potencialmente de t = 0 a qualquer momento.
precisa saber é o seguinte
4

Na verdade, não implementei isso (pode haver alguns problemas que não estou vendo imediatamente), mas achei que tentaria ajudar.

Aqui está o que você disse que está acontecendo:

O cliente A envia entrada em T0

Servidor recebe entrada em T1

Todos os clientes recebem a alteração no T2

No T2, no entanto, usando a previsão do cliente, o Cliente A agora está em uma posição apropriada para T4.

Provavelmente seria útil pensar em termos de tempo do servidor. É (provavelmente) muito parecido com o funcionamento da interpolação .

Todo comando é enviado com um horário do servidor. O tempo do servidor é calculado no início de uma partida consultando o tique do servidor, compensando o tempo de ping. No cliente, você tem sua própria contagem de ticks local e cada comando enviado é convertido em ticks de servidor (é uma operação simples de subtração)

Além disso, o cliente está sempre processando "no passado". Portanto, você assume que o mundo que o cliente vê está, digamos, 100ms atrás do que realmente é o tempo do servidor.

Então, vamos reformular seu exemplo com o horário do servidor (designado por S).

O cliente envia entrada em T0 com o tempo do servidor S0 (o que eu acho é realmente "representação do cliente do tempo do servidor menos o tempo de interpolação"). O cliente não espera pela resposta do servidor e move-se imediatamente.

O servidor recebe entrada em T1. O servidor descobre a posição autoritativa do cliente no horário do servidor S0 fornecido pelo cliente. Envia isso para o cliente.

O cliente recebe a posição autoritativa em T2 (ainda com a designação do horário do servidor S0). O cliente controla algum tempo passado de eventos anteriores (provavelmente apenas uma fila de todas as previsões não confirmadas).

Se a posição / velocidade prevista / o que o servidor enviar de volta para S0 for diferente do que o cliente armazenou em S0, o cliente lida com isso de alguma forma. Voltando ao player para a posição anterior, ou re-estimulando a entrada anterior, ou talvez algo que eu não tenha pensado.

Tetrad
fonte
3
Está tudo correto, exceto um pouco sobre a renderização do cliente no passado. Em relação ao servidor, o cliente está realmente renderizando no futuro! O servidor sabe que as informações que possui de cada cliente são antigas e que cada cliente já terá sido alterado desde então.
Kylotan
2

Na verdade, há uma implementação de código aberto no github que mostra como isso é feito. Confira Lance.gg

repositório github: https://github.com/lance-gg/lance

O código de previsão do cliente é implementado no módulo chamado src/syncStrategies/ExtrapolateStrategy.js

Além da extrapolação, existem dois conceitos que eu não vi mencionados acima:

  1. Dobra incremental. Basicamente, em vez de aplicar a correção do servidor de uma só vez, você permite que o delta seja aplicado em pequenos incrementos. Dessa forma, os objetos remotos ajustam gradualmente suas posições para corresponder às posições do servidor. Há flexão de posição, flexão de velocidade, flexão de ângulo e flexão de velocidade angular. Além disso, você pode querer diferentes fatores de flexão para diferentes objetos.
  2. Etapa Reconstituição. O fato de os dados estarem no passado significa que você pode reverter o tempo para o tempo dos dados do servidor e reiniciar a partir desse ponto. É claro que você ainda precisará se curvar para a posição recém-encontrada, em vez de pular para ela.
Gary Weiss
fonte
1

O cliente A está sempre à frente do servidor - mas isso não importa. Você só precisa recuperar o cliente se o servidor disser que houve um problema com a posição relatada; nesse momento, o cliente executa novamente todas as alterações feitas desde o erro com os valores corrigidos, para trazê-lo para um estado compatível com o servidor

Para fazer isso, o cliente precisa se lembrar de algumas de suas atualizações e estado anteriores. Pode haver apenas alguns valores simples, como posição, velocidade, orientação, esse tipo de coisa. O servidor enviará periodicamente uma confirmação de que várias atualizações do cliente eram legítimas, o que significa que agora podem ser esquecidas do cliente. Se, no entanto, o servidor relatar que uma atualização é inválida, o estado do cliente reverte para esse ponto e as alterações futuras são aplicadas a esse estado modificado.

Existem alguns links extras na parte inferior do artigo da Valve que valem a pena ler - este é um deles: https://developer.valvesoftware.com/wiki/Prediction

Kylotan
fonte
Então, eu estou certo ao pensar que o cliente (at t=4) recebe informações sobre t=2, então redefine o estado para t=2executar novamente as atualizações para trazer objetos de t=2para t=4?
George Duckett
Ainda não estou entendendo por algum motivo. O servidor não está sendo informado da posição do jogador, apenas das entradas. Então o jogador está saindo da última posição em que o servidor disse que estava. A entrada é aplicada. O servidor é informado. O servidor confirma a entrada para todos. Supondo que todos os comandos sejam aceitos, o servidor ainda estará atrás do Cliente A - portanto, quando o Cliente A parar, seu caractere parará imediatamente e voltará ao local dos servidores quando receber a confirmação de parada.
Chris Evans
@GeorgeDuckett: sim (apesar de não ter de ser t = 4, poderia ser sempre que for detectada uma discrepância, e pode haver qualquer número de actualizações aplicado-re.)
Kylotan
@ ChrisEvans: estado conhecido + alterações baseadas na entrada são equivalentes a enviar estado de qualquer maneira. Quanto ao exemplo de parada, isso por si só é uma entrada e o servidor ainda está simulando movimento até receber essa entrada. Assumindo latência constante, o servidor interromperá o jogador se movendo exatamente na mesma posição que o cliente viu quando parou de se mover, porque o cliente estava à frente do servidor. (No mundo real, a latência varia, assim que você interpolar um pouco para suavizá-lo.)
Kylotan