Comment garder les horloges serveur-client synchronisées pour des jeux en réseau de précision comme Quake 3?

15

Je travaille sur un jeu de tir 2D descendant et je fais de mon mieux pour copier les concepts utilisés dans les jeux en réseau comme Quake 3.

  • J'ai un serveur faisant autorité.
  • Le serveur envoie des instantanés aux clients.
  • Les instantanés contiennent un horodatage et des positions d'entité.
  • Les entités sont interpolées entre les positions des instantanés afin que le mouvement soit fluide.
  • Par nécessité, l'interpolation d'entité se produit légèrement "dans le passé" de sorte que nous avons plusieurs instantanés dans lesquels interpoler entre.

Le problème auquel je suis confronté est la "synchronisation d'horloge".

  • Par souci de simplicité, supposons un instant qu'il n'y a aucune latence lors du transfert de paquets vers et depuis le serveur.
  • Si l'horloge du serveur a 60 secondes d'avance sur l'horloge du client, un horodatage d'instantané aura 60000 ms d'avance sur l'horodatage local du client.
  • Par conséquent, les instantanés d'entité seront collectés et assis pendant environ 60 secondes avant que le client ne voit une entité donnée effectuer ses mouvements, car il faut autant de temps à l'horloge du client pour rattraper son retard.

J'ai réussi à surmonter cela en calculant la différence entre l'horloge du serveur et celle du client chaque fois qu'un instantané est reçu.

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

Lors de la détermination de la distance dans l'interpolation de l'entité, j'ajoute simplement la différence à l'heure actuelle du client. Le problème avec cela, cependant, est qu'il provoquera des saccades, car la différence entre les deux horloges fluctuera brusquement en raison des instantanés arrivant plus rapidement / plus lentement que les autres.

Comment synchroniser les horloges suffisamment étroitement pour que le seul retard perceptible soit celui qui est codé en dur pour l'interpolation et celui qui est causé par la latence du réseau ordinaire?

En d'autres termes, comment empêcher l'interpolation de démarrer trop tard ou trop tôt lorsque les horloges sont considérablement désynchronisées, sans introduire de secousses?

Edit: Selon Wikipedia , NTP peut être utilisé pour synchroniser les horloges sur Internet en quelques millisecondes. Cependant, le protocole semble compliqué, et peut-être exagéré pour une utilisation dans les jeux?

Joncom
la source
comment est-ce compliqué ? C'est une demande et une réponse chacune avec des horodatages de transmission et d'arrivée, puis un peu de calcul pour obtenir le delta
ratchet freak
@ratchetfreak: Selon ( mine-control.com/zack/timesync/timesync.html ), "Malheureusement, NTP est très compliqué et, plus important encore, il converge lentement sur le delta temporel précis. Cela rend NTP moins qu'idéal pour le réseau jeu où le joueur s'attend à ce qu'un jeu commence immédiatement ... "
Joncom

Réponses:

10

Après avoir cherché, il semble que la synchronisation des horloges de 2 ordinateurs ou plus ne soit pas une tâche triviale. Un protocole comme NTP fait du bon travail mais est censé être lent et trop complexe pour être pratique dans les jeux. En outre, il utilise UDP qui ne fonctionnera pas pour moi car je travaille avec des sockets Web, qui ne prennent pas en charge UDP.

J'ai trouvé une méthode ici cependant, qui semble relativement simple:

Il prétend synchroniser les horloges à moins de 150 ms (ou mieux) les unes des autres.

Je ne sais pas si cela suffira à mes fins, mais je n'ai pas pu trouver d'alternative plus précise.

Voici l'algorithme qu'il fournit:

Une technique de synchronisation d'horloge simple est requise pour les jeux. Idéalement, il devrait avoir les propriétés suivantes: raisonnablement précis (150 ms ou mieux), rapide à converger, simple à implémenter, capable de s'exécuter sur des protocoles basés sur des flux tels que TCP.

Un algorithme simple avec ces propriétés est le suivant:

  1. Le client marque l'heure locale actuelle sur un paquet de "demande de temps" et l'envoie au serveur
  2. Dès réception par le serveur, le serveur tamponne l'heure du serveur et retourne
  3. À la réception par le client, le client soustrait l'heure actuelle de l'heure envoyée et la divise par deux pour calculer la latence. Il soustrait l'heure actuelle de l'heure du serveur pour déterminer le delta de temps client-serveur et ajoute la demi-latence pour obtenir le delta d'horloge correct. (Jusqu'à présent, cet algothim est très similaire à SNTP)
  4. Le premier résultat doit être immédiatement utilisé pour mettre à jour l'horloge car il mettra l'horloge locale au moins dans le bon stade (au moins dans le bon fuseau horaire!)
  5. Le client répète les étapes 1 à 3 cinq fois ou plus, en faisant une pause de quelques secondes à chaque fois. D'autres trafics peuvent être autorisés dans l'intervalle, mais devraient être minimisés pour de meilleurs résultats
  6. Les résultats des réceptions de paquets sont accumulés et triés dans l'ordre de latence la plus faible à la latence la plus élevée. La latence médiane est déterminée en choisissant l'échantillon médian dans cette liste ordonnée.
  7. Tous les échantillons supérieurs à environ 1 écart-type de la médiane sont rejetés et les échantillons restants sont moyennés à l'aide d'une moyenne arithmétique.

La seule subtilité de cet algorithme est que les paquets supérieurs à un écart-type au-dessus de la médiane sont rejetés. Le but de ceci est d'éliminer les paquets qui ont été retransmis par TCP. Pour visualiser cela, imaginez qu'un échantillon de cinq paquets a été envoyé via TCP et qu'il n'y a eu aucune retransmission. Dans ce cas, l'histogramme de latence aura un seul mode (cluster) centré autour de la latence médiane. Imaginez maintenant que dans un autre essai, un seul paquet des cinq soit retransmis. La retransmission entraînera la chute de cet échantillon sur la droite de l'histogramme de latence, en moyenne deux fois plus loin que la médiane du mode primaire. En supprimant simplement tous les échantillons qui tombent de plus d'un écart-type de la médiane, ces modes parasites sont facilement éliminés en supposant qu'ils ne constituent pas la majeure partie des statistiques.

Cette solution semble bien répondre à ma question, car elle synchronise l'horloge puis s'arrête, permettant au temps de s'écouler linéairement. Alors que ma méthode initiale mettait constamment à jour l'horloge, ce qui faisait que le temps sautait un peu lorsque des instantanés étaient reçus.

Joncom
la source
Comment cela s'est-il avéré fonctionner pour vous alors? Je suis dans la même situation maintenant. J'utilise un framework de serveur qui ne supporte que TCP, donc je ne peux pas utiliser NTP, qui envoie des datagrammes UDP. J'ai du mal à trouver des algorithmes de synchronisation horaire qui prétendent faire une synchronisation horaire fiable sur TCP. La synchronisation en une seconde serait cependant suffisante pour mes besoins.
dynamokaj
@dynamokaj Fonctionne assez bien.
Joncom
Cool. Est-il possible que vous puissiez partager la mise en œuvre?
dynamokaj
@dynamokaj Il semble que je ne puisse trouver une telle implémentation dans aucun projet auquel je puisse penser en ce moment. Alternativement, ce qui fonctionne assez bien pour moi est: 1) utiliser immédiatement la latence que vous calculez à partir d'une demande / réponse de ping, puis, 2) pour toutes les réponses de ce type entre la nouvelle valeur progressivement, pas instantanément. Cela a un effet de "moyenne" qui a été très précis pour mes besoins.
Joncom
Aucun problème. J'exécute mon service backend sur Google App Engine, donc sur l'infrastructure Googles où les serveurs sont synchronisés à l'aide de Google NTP Server: time.google.com ( developers.google.com/time ) J'utilise donc le client NTP suivant pour mon client Xamarin Mobile pour obtenir le décalage entre le client et le serveur. components.xamarin.com/view/rebex-time - Merci d'avoir pris le temps de répondre.
dynamokaj
1

Fondamentalement, vous ne pouvez pas réparer le monde [entier] et, finalement, vous devrez tracer la ligne.

Si le serveur et tous les clients partagent la même fréquence d'images, ils ont juste besoin de se synchroniser lors de la connexion, et parfois par la suite, en particulier après un événement de latence. La latence n'affecte pas le flux de temps ni la capacité du PC à le mesurer. Dans de nombreux cas, plutôt que d'interpoler, vous devez extrapoler. Cela crée des effets tout aussi indésirables mais, encore une fois, c'est ce que c'est et vous devez choisir le moins de tous les maux disponibles.

Considérez que dans de nombreux MMO populaires, les joueurs en retard sont visuellement évidents. Si vous les voyez fonctionner en place, directement dans un mur, votre client extrapole. Lorsque votre client reçoit les nouvelles données, le joueur (sur son client) peut avoir parcouru une distance considérable et va se «coller» ou se téléporter vers un nouvel emplacement (la «secousse» que vous avez mentionnée?). Cela se produit même dans les grands jeux de marque.

Techniquement, c'est un problème avec l'infrastructure réseau du joueur, pas avec votre jeu. Le point auquel il passe de l'un à l'autre est la ligne même que vous devez tracer. Votre code, sur 3 ordinateurs distincts, devrait plus ou moins enregistrer le même temps écoulé. Si vous ne recevez pas de mise à jour, cela ne devrait pas affecter votre fréquence d'images Update (); au contraire, cela devrait être plus rapide car il y a probablement moins à mettre à jour.

"Si vous avez une mauvaise connexion Internet, vous ne pouvez pas jouer à ce jeu de manière compétitive."
Ce n'est pas de l'argent ou un bug.

Jon
la source