Réseautage pour les jeux de stratégie en temps réel

16

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?

maxov
la source
Vous pouvez peut-être mettre en œuvre ce que fait Eve-online et "ralentir" le temps pour que tout se déroule correctement.
Ryan Erb
3
Voici un lien obligatoire vers le modèle client / serveur de Planetary Annihilation: forrestthewoods.ghost.io/… Ceci est une alternative au modèle lockstep qui semble très bien fonctionner pour eux.
DallonF
Envisagez de réduire le nombre d'événements en envoyant une seule mise à jour pour toutes les tuiles prises au lieu de mises à jour pour chaque tuile ou, comme l'a répondu Ilmari, en décentralisant les actions des non-joueurs.
Lilienthal

Réponses:

12

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).

Lennart Rolland
la source
6

chaque action du joueur est déterministe, cependant, il y a des événements qui se produisent à intervalles planifiés

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 pouvez comparer en l'exécutant le plus rapidement possible
  • vous pouvez déboguer en ralentissant le jeu pour voir les événements fugitifs, et comme mentionné
  • le jeu reste déterministe ce qui est très très important pour le multijoueur.

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.

congusbongus
la source
1
+1: basé sur l'image est le bon choix, pas basé sur le temps. Vous pouvez bien sûr essayer de conserver N images par seconde. Un léger accroc vaut mieux qu'une désynchronisation complète.
PatrickB
@PatrickB: Je vois que de nombreux jeux utilisent un temps "simulé" qui n'est pas lié aux images vidéo. World of Warcraft ne met à jour que le mana toutes les 100 ms, et Dwarf Fortress par défaut à 10 ticks par image vidéo.
Mooing Duck
@Mooing Duck: Mon commentaire était spécifique aux RTS. Pour quelque chose où de petites erreurs peuvent être tolérées et corrigées ultérieurement (par exemple MMORPG, FPS), l'utilisation de valeurs continues est non seulement correcte, mais critique. Cependant, des simulations déterministes qui doivent être synchronisées sur plusieurs machines? Respectez les cadres.
PatrickB
4

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ù.

Ilmari Karonen
la source
1
Synchronisez également le RNG pour démarrer et ne tirez du RNG synchronisé que lorsque le serveur vous le demande. Starcraft1 avait un bug pendant longtemps où la graine RNG n'était pas enregistrée pendant les replays, donc les replays s'écarteraient lentement des jeux réels.
Mooing Duck
1
@MooingDuck: Bon point. En fait, je suggère de transmettre la graine RNG actuelle à chaque tour, afin que la désynchronisation RNG soit immédiatement détectée. De plus, si votre code d'interface utilisateur a besoin d'être aléatoire, ne le tirez pas de la même instance RNG que celle utilisée pour la logique du jeu.
Ilmari Karonen
3

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.

Kromster dit soutenir Monica
la source
1

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.

Lesto
la source
Les problèmes mathématiques en virgule flottante peuvent généralement être évités en tant que tels - dans un RTS, vous pouvez généralement facilement faire la simulation et le mouvement avec un nombre entier / point fixe, et utiliser la virgule flottante uniquement pour la couche d'affichage qui n'affecte pas le comportement du jeu.
Peteris
Avec un entier, il est difficile de faire des carreaux horizontaux, sauf s'il s'agit d'un tableau octogonal. Il n'y a pas d'accélération matérielle pour le point fixe, il peut donc être plus lent que float ieee754
Lesto