Synchronisation client à faible trafic avec le serveur dans MMO

22

J'implémente un MMO où le joueur vole dans l'espace sur son vaisseau le contrôlant avec les touches fléchées et coopère avec d'autres joueurs.

Je veux l'implémenter pour que le joueur puisse esquiver son vaisseau de la fusée ou autre chose, alors j'essaie de prédire l'état du jeu entier côté client en utilisant le même algorithme de simulation du monde que celui du serveur. Ce monde de jeu est écrit sur C # et sera appelé directement dans le client (il est écrit sur Unity3D) et via CLR sur le serveur C ++ (sous Linux). Connexion via UDP.

Le problème est de savoir comment maintenir, par exemple, 1000 joueurs sur une seule carte (en excluant tous les autres objets de jeu, les mobs ...): Disons que je vais:

  • synchroniser le serveur avec les clients 50 fois par seconde
  • envoyer à chaque client les états du jeu (et des joueurs) qu'il peut voir (dans un rayon)
  • avoir à envoyer 100 objets à chaque joueur dans son rayon de vision
  • doit envoyer en moyenne 50 octets par objet de jeu (c'est id, x, y coords, rotation, état ...)

ainsi, il devra avoir une telle bande passante réseau: 1000 (clients) * 50 (fois par seconde) * 100 (objets à envoyer à chaque joueur) * 50 (octets par objet) = 250 000 000 octets par seconde! C'est impossible!

Est-il possible de réduire cette valeur d'une manière ou d'une autre? Par exemple, laissez les clients simuler complètement leurs mondes de jeu (pendant une longue période) et envoyez-leur uniquement les entrées d'autres clients et synchronisez les mondes de jeu, disons, toutes les quelques secondes, mais cela causera des problèmes de désynchronisation étranges en laisse à cause des calculs flottants .

Quoi qu'il en soit, comment ces jeux sont-ils programmés de manière commune? Merci.

slave
la source
1
J'envoie juste des informations logiques sur les objets (position du monde, état actuel (qui est un octet) et ainsi de suite) - pas de graphiques.
Slav
1
@Slav: Nice! Tout ce changement de bit me rappelle mes jours de programmation ASM.
Randolf Richardson
1
Pourquoi pas "aujourd'hui"? :) Quand j'écris sur AS3, Java, Lua, C # et que je fais face à de si mauvaises performances, C ++ me manque et je me souviens d'ASM.
Slav
1
@Slav: Heheh, je n'ai pas fait beaucoup d'ASM récemment. La plupart des choses pour moi sont en Java et en Perl (surtout pour mod_perl2), mais j'apprécie vraiment ces langages aussi.
Randolf Richardson
2
@Slav, vous avez écrit: "Quand j'écris sur AS3, Java, Lua, C # et que je fais face à de si mauvaises performances, C ++ me manque et je me souviens d'ASM". Vous devriez apprendre à utiliser Lua et C # correctement, peut-être trouveriez-vous les performances moins abyssales. De plus, se plaindre du langage de script (prétendument) le plus rapide est au mieux singulier ... Est-ce un jeu sur l'analyse du génome humain en temps réel?
Raine

Réponses:

20

Vous n'avez besoin que d'environ 30 mises à jour (ou encore moins peut-être 10 ou 20) par seconde. interpoler les positions des objets en mouvement côté client. En général, vous ne devez envoyer des données que lorsqu'elles sont VRAIMENT nécessaires. Dans WoW, vous pourriez recevoir plus de mises à jour des joueurs avec lesquels vous êtes dans un groupe que des joueurs qui sont au même endroit. De plus, si un autre joueur est loin de vous, vous ne recevez pas autant de mises à jour par seconde à son sujet.

Ensuite, n'envoyez qu'un instantané complet à chaque joueur lorsqu'il se connecte. Après cela, envoyez uniquement les modifications des objets du jeu. Si aucun changement ne s'est produit, ne l'envoyez pas.

Ensuite, faites un usage intensif de BitVectors ou comment vous pourriez les appeler pour réduire la quantité de données inutiles! Exemple: Vous pouvez également essayer d'écrire un flottant en utilisant un seul octet (dans une plage de 0 à 1 ou -1 à 1) afin de n'avoir que 256 ou 128 valeurs différentes. Mais le joueur ne remarquera aucun mouvement saccadé grâce aux interpolations.

Regardez ceci pour un exemple avec LidgrenLibrary sur la façon de compresser les données: http://code.google.com/p/lidgren-network-gen3/wiki/Optimization

Suivant: Essayez de réduire le rayon de vision des joueurs lorsqu'ils se déplacent et ne transmettez que des informations importantes à ce moment-là. Puis, lorsqu'ils s'arrêtent, augmentez à nouveau leur rayon de vue. Vous pouvez utiliser un système de hachage spatial ou un arbre bsp pour réduire la surcharge de recherche d'objets qui sont "à portée". Ceci est une bonne lecture pour le sujet: http://en.wikipedia.org/wiki/Collision_detection

Compressez également les données VOUS-MÊME seulement VOUS connaissez la structure des données et la cohérence temporelle des données (qui peuvent et doivent être exploitées). Un algorithme général tel que Bzip2, Deflate, peu importe, devrait être utilisé, mais seulement comme étape finale de compression!

En outre, pour les informations non essentielles au jeu, vous pouvez également utiliser des techniques P2P supplémentaires. Exemple: Un joueur joue l'animation "bonjour" (juste un effet graphique) Le joueur envoie ces informations au serveur, mais le serveur ne transmet pas les informations aux autres joueurs. Au lieu de cela, cet effet non critique est envoyé par le joueur lui-même aux autres clients à portée.

EDIT (à cause du commentaire):

Méthodes supplémentaires pour diminuer le nombre moyen de bits par seconde pour chaque joueur:

  1. Vous avez écrit que vous envoyez "L'objet n'a pas changé". Il n'y a aucune raison de le faire. Si vous vous inquiétez de la perte de paquets (et de la désynchronisation de votre simulation à cause de cela), tenez compte des points suivants: à chaque pas de temps fixe (ex. 100, 200, 300, 400 ...) hachez l'état de simulation et envoyez-le au serveur . le serveur confirme ou renvoie un instantané complet de toutes les données.

  2. Pour des choses comme des roquettes ou même des joueurs, vous pouvez utiliser non seulement l'interpolation mais aussi l'extrapolation afin de rendre la simulation plus réaliste. Exemple 'Rocket': Au lieu de mettre à jour avec des messages comme "Est maintenant à la position x", il suffit d'envoyer un message contenant les informations suivantes: "Rocket Spawned: position (vector), Time (à quelle étape de simulation la fusée a été générée), velocity ( vecteur)". Vous n'avez donc même pas besoin d'inclure la rotation car la pointe sera toujours dans le sens "vitesse".

  3. Combinez plusieurs commandes dans un même message et n'envoyez jamais de messages inférieurs à 16-20 octets car l'en-tête udp sera plus grand que le message lui-même. N'envoyez pas non plus de paquets plus grands que le MTU de votre protocole car la fragmentation ralentira la vitesse de transmission.

Riki
la source
Oh, c'est une bonne idée de mettre à jour certains objets plus fréquemment que d'autres, d'utiliser le P2P, de dégrader la précision en virgule flottante, d'envoyer juste des modifications (ce qui n'est pas trivial pour moi car j'avais l'intention de synchroniser les objets périodiquement mais "l'objet n'a pas changé" est l'information aussi). Avec toutes ces modifications, l'image globale semble plus réaliste!
Slav
1
L'envoi de notifications de type «objet n'a pas changé» peut être une technique utile pour tester à des fins où vous voulez voir comment votre jeu fonctionne lorsque les joueurs sont actifs pendant les périodes de pointe, car il a le potentiel d'imposer des exigences sur le traitement ainsi que sur le réseau, mais il existe encore de meilleures solutions que cela (comme la création d'un démon autonome qui contrôle un personnage réel dans le jeu, puis l'exécution de ce démon plusieurs fois à partir de différentes machines).
Randolf Richardson
5

Voici deux approches:

Premièrement:
passez à la physique déterministe, envoyez des commandes aux joueurs, des actions ai, des objets qui apparaissent et tout ce qui ne peut pas être déterminé côté client pour les clients. Cela doit inclure des non-commandes, une confirmation que jusqu'à un certain point dans le temps, seules les commandes envoyées et reçues s'appliquent.

Le client doit exécuter deux ou trois simulations simultanées.
1: s'arrête chaque fois qu'il manque des données pour l'étape suivante.
2: Continuez à utiliser les données de supposition et indiquez l'état utilisé pour le rendu. 3: Chaque fois que le no 1 s'arrête, cette simulation copie l'état du no 1, rattrape l'heure actuelle et prend le relais pour le no 2, qui est ensuite abandonné.

Si le rattrapage est assez rapide, vous pouvez laisser de côté les différences entre le n ° 2 et le n ° 3 et supprimer immédiatement les anciennes données.

Deuxièmement:
N'utilisez pas la physique déterministe, faites la même chose que ci-dessus, mais envoyez des "images complètes" une fois toutes les quelques secondes. Vous pouvez facilement supprimer complètement le transfert de choses temporaires comme des balles.

Dans les deux cas, vous voudrez peut-être vous méfier du fait que le client prédit que quelqu'un mourra, c'est un peu idiot de voir un adversaire non explosé.

Et +1 pour faire le calcul, trop de gens ne parviennent pas à faire de simples estimations de l'utilisation des ressources.

aaaaaaaaaaaa
la source
2
La «physique déterministe» signifie-t-elle que je ne peux pas utiliser de valeurs à virgule flottante ou différentes étapes de simulation? Je me demande que la désynchronisation critique peut se produire si, par exemple, une fusée passera par une tourelle ennemie sur le client mais la frappera sur le serveur (en raison d'une inexactitude en virgule flottante), ce qui amènera le joueur à continuer à combattre cette tourelle jusqu'au prochain paquet de synchronisation du serveur entrant (plusieurs secondes).
Slav
3
Cela signifie des entiers et un pas de temps fixe. Théoriquement, vous pouvez vous moquer des points flottants pour vous comporter, mais l'utilisation d'entiers est beaucoup plus simple. Vous avez un point avec l'exemple de missile manquant, si vous utilisez la physique non déterministe, il est probablement préférable de laisser le serveur gérer entièrement la mort et de transmettre rapidement les cas de mort / destruction.
aaaaaaaaaaaa
5

Quelques questions d'abord.

Les «roquettes ou autre chose» sont-elles intelligentes ou stupides? S'ils sont stupides, tout ce dont vous avez besoin est l'horodatage du feu, l'origine et le vecteur pour simuler leur chemin. S'ils sont intelligents, comment sont-ils intelligents? Pouvez-vous calculer au moment du tir qu'ils vont frapper ou manquer? Si c'est le cas, vous pouvez simuler l'intégralité du chemin sur le client. ("A T13, le missile frappera le navire parce que le jeu a perdu le jet d'esquive / le tireur a marqué un coup critique.")

En général, il n'y a pratiquement aucune raison de: A) avoir une fréquence d'horloge de 50 Hz (la plupart des tireurs s'en tirent avec 15-20 et MMO moins que cela.) B) envoyer l'état complet à chaque image. (La rotation d'un missile dans l'espace est-elle importante? Ou pouvez-vous simplement supposer que son «front» est orienté le long du vecteur qu'il parcourt?)

Passez du temps avec la prédiction et l'interpolation, et vous verrez votre bande passante chuter. Un projet sur lequel j'ai travaillé avait un taux de mise à jour de 10 Hz et une représentation d'état d'objet de 14 octets, je pense. (Compressez tout ce que vous pouvez! Je crois que nous avons utilisé 6 bits pour définir la rotation autour du plan x, puis encore 6 bits pour une inclinaison au-dessus / en dessous de ce plan, il semblait impossible de distinguer l'envoi d'une matrice / quaternion de rotation réelle.)

Une autre chose que vous pouvez faire est de hiérarchiser les objets. Montrez, il y a peut-être 100 objets dans l'ensemble concerné, mais connaissez-vous son point de vue sur le serveur? Si quelque chose ne lui semble pas, pouvez-vous réduire sa fréquence de mise à jour d'un ordre de grandeur?

L'idée générale n'est pas de faire une simulation parfaite sur le client, c'est impossible, l'idée est de faire un jeu amusant où les joueurs ne remarqueront pas que ce n'est pas une simulation parfaite.

Doug-W
la source