Como manter os relógios servidor-cliente sincronizados para jogos em rede de precisão como o Quake 3?

15

Estou trabalhando em um shooter 2D de cima para baixo e fazendo o possível para copiar conceitos usados ​​em jogos em rede como o Quake 3.

  • Eu tenho um servidor autorizado.
  • O servidor envia instantâneos para os clientes.
  • Os instantâneos contêm um registro de data e hora e posições da entidade.
  • As entidades são interpoladas entre as posições do instantâneo, para que o movimento pareça suave.
  • Por necessidade, a interpolação de entidade ocorre um pouco "no passado", de modo que temos vários instantâneos nos quais podemos interpolar.

O problema que estou enfrentando é "sincronização do relógio".

  • Por uma questão de simplicidade, vamos fingir por um momento que não há latência ao transferir pacotes de e para o servidor.
  • Se o relógio do servidor estiver 60 segundos antes do relógio do cliente, o registro de data e hora da captura instantânea será 60000ms antes do registro de data e hora local do cliente.
  • Portanto, os instantâneos da entidade serão coletados e permanecerão em repouso por cerca de 60 segundos antes que o cliente veja qualquer entidade fazer seus movimentos, porque leva muito tempo para o relógio do cliente recuperar o atraso.

Consegui superar isso calculando a diferença entre o servidor e o relógio do cliente cada vez que um instantâneo é recebido.

// For simplicity, don't worry about latency for now...
client_server_clock_delta = snapshot.server_timestamp - client_timestamp;

Ao determinar até que ponto a interpolação está na entidade, simplesmente adiciono a diferença ao horário atual do cliente. O problema com isso, no entanto, é que ele causará instabilidade, pois a diferença entre os dois relógios flutuará abruptamente devido aos instantâneos que chegam mais rápido / mais lentamente que os outros.

Como sincronizar os relógios com a precisão necessária para que o único atraso perceptível seja o codificado para interpolação e o causado pela latência comum da rede?

Em outras palavras, como posso impedir que a interpolação seja iniciada muito tarde ou muito cedo quando os relógios são dessincronizados significativamente, sem introduzir instabilidade?

Edit: De acordo com a Wikipedia , o NTP pode ser usado para sincronizar relógios pela Internet em questão de alguns milissegundos. No entanto, o protocolo parece complicado e talvez exagerado para uso em jogos?

Joncom
fonte
como é complicado ? É uma solicitação e resposta, cada uma com carimbos de data e hora de transmissão e chegada, e um pouco de matemática para obter o delta
catraca mania
@ratchetfreak: De acordo com ( mine-control.com/zack/timesync/timesync.html ), "Infelizmente, o NTP é muito complicado e, mais importante, lento para convergir no delta de tempo preciso. Isso torna o NTP menos do que o ideal para a rede jogo em que o jogador espera que um jogo comece imediatamente ... ""
Joncom 5/15/15

Respostas:

9

Depois de pesquisar, parece que sincronizar os relógios de 2 ou mais computadores não é uma tarefa trivial. Um protocolo como o NTP faz um bom trabalho, mas é supostamente lento e muito complexo para ser prático nos jogos. Além disso, ele usa UDP, que não funcionará para mim porque estou trabalhando com soquetes da Web, que não suportam UDP.

Eu encontrei um método aqui , no entanto, que parece relativamente simples:

Ele afirma sincronizar os relógios a 150ms (ou melhor) um do outro.

Não sei se isso será bom o suficiente para meus propósitos, mas não consegui encontrar uma alternativa mais precisa.

Aqui está o algoritmo que ele fornece:

É necessária uma técnica simples de sincronização do relógio para jogos. Idealmente, ele deve ter as seguintes propriedades: razoavelmente preciso (150ms ou melhor), rápido para convergir, simples de implementar, capaz de executar em protocolos baseados em fluxo, como o TCP.

Um algoritmo simples com essas propriedades é o seguinte:

  1. O cliente carimba a hora local atual em um pacote de "solicitação de hora" e envia para o servidor
  2. Após o recebimento pelo servidor, o servidor marca o horário do servidor e retorna
  3. Após o recebimento pelo cliente, o cliente subtrai o horário atual do horário enviado e divide por dois para calcular a latência. Ele subtrai a hora atual da hora do servidor para determinar o delta do horário cliente-servidor e adiciona a meia latência para obter o delta do relógio correto. (Até agora este algothim é muito semelhante ao SNTP)
  4. O primeiro resultado deve ser usado imediatamente para atualizar o relógio, pois ele colocará o relógio local no mínimo no estádio certo (pelo menos no fuso horário certo!)
  5. O cliente repete as etapas 1 a 3 cinco ou mais vezes, pausando alguns segundos a cada vez. Outro tráfego pode ser permitido nesse ínterim, mas deve ser minimizado para obter melhores resultados
  6. Os resultados dos recebimentos de pacotes são acumulados e classificados na ordem de menor latência para maior latência. A latência mediana é determinada escolhendo a amostra do ponto médio dessa lista ordenada.
  7. Todas as amostras acima de aproximadamente 1 desvio padrão da mediana são descartadas e as demais amostras são calculadas usando uma média aritmética.

A única sutileza desse algoritmo é que os pacotes acima de um desvio padrão acima da mediana são descartados. O objetivo disso é eliminar os pacotes que foram retransmitidos pelo TCP. Para visualizar isso, imagine que uma amostra de cinco pacotes foi enviada por TCP e não houve retransmissão. Nesse caso, o histograma de latência terá um modo único (cluster) centralizado em torno da latência mediana. Agora imagine que em outro teste, um único pacote dos cinco seja retransmitido. A retransmissão fará com que esta amostra caia muito à direita no histograma de latência, em média duas vezes mais distante que a mediana do modo primário. Simplesmente cortando todas as amostras que estão a mais de um desvio padrão da mediana, esses modos dispersos são facilmente eliminados, assumindo que eles não compreendem a maior parte das estatísticas.

Essa solução parece responder bem à minha pergunta porque sincroniza o relógio e depois para, permitindo que o tempo flua linearmente. Enquanto meu método inicial atualizava o relógio constantemente, causando tempo para pular um pouco à medida que os instantâneos eram recebidos.

Joncom
fonte
Como isso provou funcionar para você, então? Eu estou na mesma situação agora. Estou usando uma estrutura de servidor que suporta apenas TCP, portanto, não posso usar NTP, que envia datagramas UDP. Eu luto para encontrar qualquer Algoritmo de Sincronização de Tempo que afirme fazer sincronização de tempo confiável por TCP. A sincronização dentro de um segundo seria suficiente para minhas necessidades.
dynamokaj
@dynamokaj Funciona razoavelmente bem.
Joncom
Legal. É possível que você possa compartilhar a implementação?
dynamokaj
@dynamokaj Parece que não consigo encontrar essa implementação em nenhum projeto em que possa pensar agora. Como alternativa, o que funciona bem o suficiente para mim é: 1) use imediatamente a latência calculada a partir de uma solicitação / resposta de ping e, em seguida, 2) para todas as respostas futuras futuras interpoladas em direção ao novo valor gradualmente, não instantaneamente. Isso tem um efeito de "média" que tem sido bastante preciso para meus propósitos.
Joncom
Sem problemas. Estou executando meu serviço de back-end no Google App Engine, portanto, na infraestrutura do Google, onde os servidores são sincronizados usando o servidor NTP do Google: time.google.com ( developers.google.com/time ) Por isso, uso o seguinte cliente NTP para o meu cliente Xamarin Mobile para obter o deslocamento entre o cliente e o servidor. components.xamarin.com/view/rebex-time - Obrigado por reservar um tempo para responder.
dynamokaj
1

Basicamente, você não pode consertar o mundo [inteiro] e, eventualmente, terá que traçar a linha.

Se o servidor e todos os clientes compartilharem a mesma taxa de quadros, eles só precisarão sincronizar na conexão e, ocasionalmente, depois disso, especialmente após um evento de latência. A latência não afeta o fluxo de tempo ou a capacidade do PC para medi-lo, portanto, em muitos casos, em vez de interpolar, você deve extrapolar. Isso cria efeitos igualmente indesejados, mas, novamente, é o que é e você deve escolher o menor de todos os males disponíveis.

Considere que, em muitos MMO populares, os jogadores atrasados ​​são visualmente óbvios. Se você vê-los funcionando no lugar, diretamente em uma parede, seu cliente está extrapolando. Quando o cliente recebe os novos dados, o reprodutor (no cliente) pode ter se deslocado uma distância considerável e irá "elástico" ou se teletransportar para um novo local (o "jerkiness" que você mencionou?). Isso acontece mesmo nos principais jogos de marca.

Tecnicamente, isso é um problema com a infraestrutura de rede do jogador, não com o seu jogo. O ponto em que ele vai de um para o outro é a própria linha que você deve desenhar. Seu código, em 3 computadores separados, deve registrar mais ou menos a mesma quantidade de tempo decorrido. Se você não receber uma atualização, isso não afetará sua taxa de quadros Update (); se houver, deve ser mais rápido, pois provavelmente há menos para atualizar.

"Se você tem internet ruim, não pode jogar este jogo de forma competitiva."
Isso não é uma farsa ou um bug.

Jon
fonte