Je développe un jeu de stratégie en temps réel pour un cours d'informatique que je prends. L'un des aspects les plus difficiles semble être la mise en réseau et la synchronisation client-serveur. J'ai lu sur ce sujet (y compris 1500 archers ), mais j'ai décidé d'adopter une approche client-serveur par opposition à d'autres modèles (sur LAN, par exemple).
Ce jeu de stratégie en temps réel pose quelques problèmes. Heureusement, chaque action du joueur est déterministe. Cependant, certains événements se produisent à intervalles planifiés. Par exemple, le jeu est composé de tuiles, et lorsqu'un joueur prend une tuile, le `` niveau d'énergie '', une valeur sur cette tuile, devrait augmenter d'une par seconde après sa prise. Ceci est une explication très rapide qui devrait justifier mon cas d'utilisation.
En ce moment, je fais des clients légers, qui envoient simplement des paquets au serveur et attendent une réponse. Cependant, il y a plusieurs problèmes.
Lorsque les jeux entre joueurs se transforment en fin de partie, il y a souvent plus de 50 événements par seconde (en raison des événements planifiés, expliqués plus tôt, de l'accumulation), et des erreurs de synchronisation commencent alors à apparaître. Mon plus gros problème est que même une petite déviation d'état entre les clients peut signifier différentes décisions que les clients prennent, ce qui fait boule de neige dans des jeux entièrement distincts. Un autre problème (qui n'est pas aussi important en ce moment) est qu'il y a une latence et qu'il faut attendre quelques millisecondes, même quelques secondes après avoir fait son mouvement pour voir le résultat.
Je me demande quelles stratégies et algorithmes je pourrais utiliser pour rendre cela plus facile, plus rapide et plus agréable pour l'utilisateur final. Ceci est particulièrement intéressant compte tenu du nombre élevé d'événements par seconde, ainsi que de plusieurs joueurs par match.
TL; DR réalisant un RTS avec> 50 événements par seconde, comment synchroniser les clients?
Réponses:
Votre objectif de synchroniser 50 événements par seconde en sons en temps réel pour moi comme si ce n'était pas réaliste. C'est pourquoi l'approche de verrouillage décrite dans l' article des 1500 archers est, bien, discutée!
En une phrase: la seule façon de synchroniser trop d'éléments en trop peu de temps sur un réseau trop lent est de NE PAS synchroniser trop d'éléments en trop peu de temps sur un réseau trop lent, mais de progresser de manière déterministe sur tous les clients et de synchroniser uniquement le nécessités nues (saisie par l'utilisateur).
la source
Je pense qu'il y a votre problème; votre jeu ne devrait avoir qu'une seule chronologie (pour les choses affectant le gameplay). Vous dites que certaines choses croissent à un rythme de X par seconde ; Découvrez le nombre d'étapes de jeu en une seconde et convertissez-le en un taux de X par Y étapes de jeu . Ensuite, même si le jeu peut ralentir, tout reste déterministe.
Le fonctionnement indépendant du jeu en temps réel présente d'autres avantages:
Vous avez également mentionné que vous rencontrez des problèmes lorsqu'il y a> 50 événements ou qu'il y a des retards pouvant aller jusqu'à quelques secondes. C'est beaucoup plus petit que le scénario décrit dans 1500 archers , alors voyez si vous pouvez profiler votre jeu et découvrir où se situe le ralentissement.
la source
Tout d'abord, pour résoudre le problème des événements planifiés, ne diffusez pas les événements lorsqu'ils se produisent , mais lorsqu'ils sont initialement planifiés. Autrement dit, au lieu d'envoyer un message "incrémenter l'énergie de la tuile ( x , y )" toutes les secondes, il suffit d'envoyer un seul message disant "incrémenter l'énergie de la tuile ( x , y ) une fois par seconde jusqu'à ce qu'elle soit pleine, ou jusqu'à interrompu". Chaque client est ensuite responsable de la planification locale des mises à jour.
En fait, vous pouvez aller plus loin dans ce principe, et ne transmettre que les actions des joueurs : tout le reste peut être calculé localement par chaque client (et le serveur, si nécessaire).
(Bien sûr, vous devriez probablement également transmettre occasionnellement des sommes de contrôle de l'état du jeu, pour détecter toute désynchronisation accidentelle, et disposer d'un mécanisme pour resynchroniser les clients si cela se produit, par exemple en renvoyant toutes les données de jeu de la copie faisant autorité du serveur aux clients. Mais cela devrait, espérons-le, être un événement rare, rencontré uniquement lors des tests ou lors de dysfonctionnements rares.)
Deuxièmement, pour garder les clients synchronisés, assurez-vous que votre jeu est déterministe. D'autres réponses ont déjà fourni de bons conseils pour cela, mais permettez-moi d'inclure un bref résumé de ce qu'il faut faire:
Faites votre jeu en interne au tour par tour, chaque tour ou "tick" prenant, disons, 1/50 seconde. (En fait, vous pourriez probablement vous en tirer avec 1/10 de tour ou plus.) Toutes les actions des joueurs se produisant pendant un seul tour doivent être traitées comme simultanées. Tous les messages, au moins du serveur vers les clients, doivent être étiquetés avec le numéro de tour, afin que chaque client sache à quel tour chaque événement se produit.
Étant donné que votre jeu utilise une architecture client-serveur, vous pouvez faire en sorte que le serveur agisse comme l'arbitre final de ce qui se passe à chaque tour, ce qui simplifie certaines choses. Notez cependant que cela signifie que les clients doivent également reconfirmer leurs propres actions à partir du serveur: si un client envoie un message disant "Je déplace l'unité X d'une tuile à gauche" et que la réponse du serveur ne dit rien sur le déplacement de l'unité X, le client doit supposer que cela ne s'est pas produit et peut-être annuler toute animation de mouvement prédictif qu'ils ont peut-être déjà commencé à jouer.
Définissez un ordre cohérent pour les événements "simultanés" se produisant au même tour, afin que chaque client les exécute dans le même ordre. Cet ordre peut être n'importe quoi, tant qu'il est déterministe et identique pour tous les clients (et le serveur).
Par exemple, vous pouvez d'abord augmenter toutes les ressources (ce qui peut être fait en une seule fois, si la croissance des ressources dans une tuile ne peut pas interférer avec celle d'une autre), puis déplacer les unités de chaque joueur dans une séquence prédéterminée, puis déplacer les unités PNJ. Pour être juste envers les joueurs, vous voudrez peut-être faire varier l'ordre de déplacement des unités d'un tour à l'autre, afin que chaque joueur puisse jouer en premier aussi souvent; c'est très bien, tant que c'est fait de façon déterministe (par exemple sur la base du nombre de tours).
Si vous utilisez des mathématiques à virgule flottante, assurez-vous de les utiliser en mode IEEE strict. Cela peut ralentir un peu les choses, mais c'est un petit prix à payer pour la cohérence entre les clients. Assurez-vous également qu'aucun arrondi accidentel ne se produit pendant les communications (par exemple, un client transmettant une valeur arrondie au serveur, mais utilisant toujours la valeur non arrondie en interne). Comme indiqué ci-dessus, avoir un protocole pour détecter et récupérer de la désynchronisation est également une bonne idée, juste au cas où.
la source
Vous devez rendre votre logique de jeu complètement indépendante du temps réel et en faire essentiellement un tour par tour. De cette façon, vous savez exactement à quel tour "se produit le changement d'énergie des tuiles". Dans votre cas, chaque tour ne dure que 1 / 50e de seconde.
De cette façon, vous ne devez vous soucier que des entrées des joueurs, tout le reste est géré par la logique des jeux et complètement identique sur tous les clients. Même si le jeu s'arrête un instant, en raison d'un décalage net ou d'un calcul extra-compliqué, les événements se produisent toujours en synchronisation pour tout le monde.
la source
Tout d'abord, vous devez comprendre que PC flottant / double math n'est PAS déterministe, sauf si vous spécifiez d'utiliser strictement IEEE-754 pour votre calcul (sera lent)
Ensuite, voici comment je l'implémenterais: le client se connecte au serveur et sincronise le temps (prenez soin de la latence du ping!) (Pour un long jeu, il peut être nécessaire de resynchroniser l'horodatage / tour)
maintenant, chaque fois qu'un client effectue une action, cela inclut un horodatage / tour et c'est au serveur de rejeter un mauvais horodatage / tour. Ensuite, le serveur renvoie l'action aux clients, et chaque fois qu'un tour est "fermé" (aka le serveur n'acceptera pas le tour / horodatage si ancien), le serveur envoie et l'action de fin de tour aux clients.
Les clients auront 2 "monde": l'un est synchronisé avec la fin du tour, l'autre est calculé à partir de la fin du tour, en additionnant l'action arrivée dans la file d'attente, jusqu'au tour / horodatage actuel du client.
parce que le serveur acceptera une action un peu ancienne, le client peut ajouter sa propre action directement dans la file d'attente, donc le temps d'aller-retour sur le réseau sera masqué, au moins pour votre propre action.
la dernière chose est de mettre en file d'attente plus d'actions afin de pouvoir remplir le paquet MTU, ce qui entraîne moins de surcharge de protocole; une bonne idée est de le faire sur le serveur, donc chaque événement de fin de tour contient une action sur la file d'attente.
j'utilise cet algorithme sur un jeu de tir en temps réel, et fonctionne très bien (avec et sans client gâchant sa propre action, mais avec un ping de serveur aussi bas que 20 / 50ms), également chaque serveur de fin de tour X envoie un "tout client map "packet, pour corriger les valeurs dérivées.
la source