¿Cómo mantener sincronizados los relojes servidor-cliente para juegos en red de precisión como Quake 3?

15

Estoy trabajando en un tirador 2D de arriba hacia abajo y estoy haciendo todo lo posible para copiar conceptos utilizados en juegos en red como Quake 3.

  • Tengo un servidor autorizado.
  • El servidor envía instantáneas a los clientes.
  • Las instantáneas contienen una marca de tiempo y posiciones de entidad.
  • Las entidades se interpolan entre las posiciones de las instantáneas para que el movimiento se vea suave.
  • Por necesidad, la interpolación de entidades ocurre levemente "en el pasado" para que tengamos múltiples instantáneas en las que interpolar.

El problema al que me enfrento es la "sincronización del reloj".

  • En aras de la simplicidad, imaginemos por un momento que no hay latencia al transferir paquetes desde y hacia el servidor.
  • Si el reloj del servidor está 60 segundos por delante del reloj del cliente, entonces una marca de tiempo de la instantánea estará 60000ms por delante de la marca de tiempo local del cliente.
  • Por lo tanto, las instantáneas de la entidad se recopilarán y se sentarán durante unos 60 segundos antes de que el cliente vea que una entidad determinada realiza sus movimientos, ya que el reloj del cliente tarda tanto en ponerse al día.

Logré superar esto calculando la diferencia entre el servidor y el reloj del cliente cada vez que se recibe una instantánea.

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

Al determinar qué tan avanzada está la entidad en la interpolación, simplemente agrego la diferencia a la hora actual del cliente. Sin embargo, el problema con esto es que causará sacudidas porque la diferencia entre los dos relojes fluctuará abruptamente debido a que las instantáneas llegan más rápido / más lento que otros.

¿Cómo puedo sincronizar los relojes lo suficientemente cerca como para que el único retraso perceptible sea el codificado para la interpolación y el causado por la latencia de red ordinaria?

En otras palabras, ¿cómo puedo evitar que la interpolación comience demasiado tarde o demasiado pronto cuando los relojes se desincronizan significativamente, sin introducir sacudidas?

Editar: según Wikipedia , NTP se puede utilizar para sincronizar relojes a través de Internet en cuestión de unos pocos milisegundos. Sin embargo, el protocolo parece complicado, ¿y quizás excesivo para usar en juegos?

Joncom
fuente
¿cómo es complicado ? Es una solicitud y una respuesta, cada una con marcas de tiempo de transmisión y llegada, y luego un poco de matemática para obtener el delta
monstruo de trinquete
@ratchetfreak: Según ( mine-control.com/zack/timesync/timesync.html ), "Desafortunadamente, NTP es muy complicado y, lo que es más importante, lento para converger en el delta de tiempo preciso. Esto hace que NTP sea menos que ideal para la red juego donde el jugador espera que el juego comience de inmediato ... "
Joncom

Respuestas:

9

Después de buscar, parece que sincronizar los relojes de 2 o más computadoras no es una tarea trivial. Un protocolo como NTP hace un buen trabajo, pero supuestamente es lento y demasiado complejo para ser práctico en los juegos. Además, utiliza UDP, que no funcionará para mí porque estoy trabajando con sockets web, que no admiten UDP.

Sin embargo, encontré un método aquí , que parece relativamente simple:

Afirma sincronizar relojes a menos de 150 ms (o mejor) entre sí.

No sé si eso será lo suficientemente bueno para mis propósitos, pero no he podido encontrar una alternativa más precisa.

Aquí está el algoritmo que proporciona:

Se requiere una técnica de sincronización de reloj simple para los juegos. Idealmente, debería tener las siguientes propiedades: razonablemente precisa (150 ms o mejor), rápida para converger, fácil de implementar, capaz de ejecutarse en protocolos basados ​​en flujo como TCP.

Un algoritmo simple con estas propiedades es el siguiente:

  1. El cliente marca la hora local actual en un paquete de "solicitud de hora" y lo envía al servidor
  2. Al recibir el servidor, el servidor marca la hora del servidor y devuelve
  3. Al recibirlo el cliente, el cliente resta el tiempo actual del tiempo enviado y lo divide por dos para calcular la latencia. Resta la hora actual de la hora del servidor para determinar el delta de tiempo cliente-servidor y agrega la latencia media para obtener el delta de reloj correcto. (Hasta ahora este algothim es muy similar al SNTP)
  4. El primer resultado debe usarse inmediatamente para actualizar el reloj, ya que hará que el reloj local entre al menos en el estadio correcto (¡al menos en la zona horaria correcta!)
  5. El cliente repite los pasos 1 a 3 cinco o más veces, haciendo una pausa de unos segundos cada vez. Se puede permitir otro tráfico en el ínterin, pero se debe minimizar para obtener mejores resultados.
  6. Los resultados de los recibos de paquetes se acumulan y clasifican en orden de latencia más baja a más alta. La latencia media se determina seleccionando la muestra de punto medio de esta lista ordenada.
  7. Todas las muestras por encima de aproximadamente 1 desviación estándar de la mediana se descartan y las muestras restantes se promedian usando una media aritmética.

La única sutileza de este algoritmo es que los paquetes por encima de una desviación estándar por encima de la mediana se descartan. El propósito de esto es eliminar los paquetes que fueron retransmitidos por TCP. Para visualizar esto, imagine que se envió una muestra de cinco paquetes a través de TCP y no hubo retransmisión. En este caso, el histograma de latencia tendrá un solo modo (grupo) centrado alrededor de la latencia media. Ahora imagine que en otra prueba, un solo paquete de los cinco se retransmite. La retransmisión hará que esta muestra caiga muy a la derecha en el histograma de latencia, en promedio el doble de distancia que la mediana del modo primario. Simplemente cortando todas las muestras que caen más de una desviación estándar de la mediana, estos modos extraviados se eliminan fácilmente suponiendo que no comprenden la mayor parte de las estadísticas.

Esta solución parece responder a mi pregunta satisfactoriamente bien, porque sincroniza el reloj y luego se detiene, permitiendo que el tiempo fluya linealmente. Mientras que mi método inicial actualizaba el reloj constantemente, haciendo que el tiempo saltara un poco a medida que se reciben instantáneas.

Joncom
fuente
¿Cómo resultó esto para ti? Estoy en la misma situación ahora. Estoy usando un marco de servidor que solo admite TCP, por lo tanto, no puedo usar NTP, que envía datagramas UDP. Me cuesta encontrar algún Algoritmo de sincronización de tiempo que diga hacer una sincronización de tiempo confiable a través de TCP. Sin embargo, la sincronización en un segundo sería suficiente para mis necesidades.
dynamokaj
@dynamokaj Funciona bastante bien.
Joncom
Frio. ¿Es posible que puedas compartir la implementación?
dynamokaj
@dynamokaj Parece que no puedo encontrar tal implementación en ningún proyecto en el que pueda pensar en este momento. Alternativamente, lo que funciona lo suficientemente bien para mí es: 1) usar inmediatamente la latencia que calcula a partir de una solicitud / respuesta de ping y luego, 2) para todas las respuestas futuras entre el nuevo valor gradualmente, no al instante. Esto tiene un efecto de "promedio" que ha sido bastante preciso para mis propósitos.
Joncom
No hay problema. Estoy ejecutando mi servicio de back-end en Google App Engine, por lo tanto, en la infraestructura de Google donde los servidores se sincronizan con Google NTP Server: time.google.com ( developers.google.com/time ) Por lo tanto, utilizo el siguiente cliente NTP para mi cliente Xamarin Mobile para obtener el desplazamiento entre el cliente y el servidor. components.xamarin.com/view/rebex-time : gracias por tomarse el tiempo para responder.
dynamokaj
1

Básicamente, no puedes arreglar el mundo [entero] y, eventualmente, tendrás que dibujar la línea.

Si el servidor y todos los clientes comparten la misma velocidad de fotogramas, solo necesitan sincronizarse al conectarse, y ocasionalmente, a partir de entonces, especialmente después de un evento de latencia. La latencia no afecta el flujo de tiempo o la capacidad de la PC para medirlo, por lo que, en muchos casos, en lugar de interpolar, debe extrapolar. Esto crea efectos igualmente no deseados pero, de nuevo, es lo que es y debes elegir el menor de todos los males disponibles.

Considere que en muchos MMO populares, los jugadores rezagados son visualmente obvios. Si los ve corriendo en su lugar, directamente en una pared, su cliente está extrapolando. Cuando su cliente recibe los nuevos datos, el jugador (en su cliente) puede haberse movido una distancia considerable y se "teletransportará" o se teletransportará a una nueva ubicación (¿la "sacudida" que mencionó?). Esto sucede incluso en los principales juegos de marca.

Técnicamente, este es un problema con la infraestructura de red del jugador, no con su juego. El punto en el que va de uno a otro es la línea que tienes que dibujar. Su código, en 3 computadoras separadas, debería registrar más o menos la misma cantidad de tiempo transcurrido. Si no recibe una actualización, no debería afectar su velocidad de fotogramas Update (); en todo caso, debería ser más rápido ya que probablemente haya menos para actualizar.

"Si tienes Internet horrible, no puedes jugar este juego de manera competitiva".
Eso no es pasar el dinero o un error.

Jon
fuente