Je travaille sur un jeu 2D isométrique multijoueur à échelle modérée, environ 20 à 30 joueurs connectés à la fois à un serveur persistant. J'ai eu quelques difficultés à mettre en place une bonne prédiction de mouvement.
Physique / Mouvement
Le jeu n'a pas de véritable implémentation physique, mais utilise les principes de base pour implémenter le mouvement. Plutôt que de scruter continuellement les entrées, les changements d'état (événements / souris bas / haut / déplacer) servent à modifier l'état de l'entité de personnage contrôlée par le joueur. La direction du joueur (c'est-à-dire / nord-est) est combinée à une vitesse constante et transformée en un véritable vecteur 3D - la vitesse de l'entité.
Dans la boucle principale du jeu, "Update" est appelé avant "Draw". La logique de mise à jour déclenche une "tâche de mise à jour physique" qui suit toutes les entités avec une vitesse non nulle et utilise une intégration très basique pour modifier la position des entités. Par exemple: entity.Position + = entity.Velocity.Scale (ElapsedTime.Seconds) (où "Seconds" est une valeur à virgule flottante, mais la même approche fonctionnerait pour des valeurs entières de la milliseconde).
Le point clé est qu'aucune interpolation n'est utilisée pour le mouvement - le moteur de physique rudimentaire n'a pas de concept d'un "état antérieur" ou d'un "état actuel", mais seulement d'une position et d'une vitesse.
Changement d'état et mise à jour des paquets
Lorsque la vitesse de l'entité de personnage contrôlée par le joueur change, un paquet "move avatar" contenant le type d'action de l'entité (stand, marche, course), la direction (nord-est) et la position actuelle est envoyé au serveur. Cela diffère de la façon dont les jeux 3D à la première personne fonctionnent. Dans un jeu 3D, la vélocité (direction) peut changer image par image lorsque le joueur se déplace. L'envoi de chaque changement d'état transmettrait effectivement un paquet par trame, ce qui serait trop coûteux. Au lieu de cela, les jeux 3D semblent ignorer les changements d'état et envoyer des paquets de "mise à jour d'état" selon un intervalle fixe, par exemple toutes les 80 à 150 ms.
Étant donné que les mises à jour de vitesse et de direction sont beaucoup moins fréquentes dans mon jeu, je peux me permettre d'envoyer chaque changement d'état. Bien que toutes les simulations physiques se déroulent à la même vitesse et soient déterministes, la latence reste un problème. Pour cette raison, j’envoie des paquets de mises à jour de position de routine (similaires à un jeu en 3D) mais beaucoup moins fréquemment - actuellement toutes les 250 ms, mais je pense qu’avec une bonne prédiction, je peux facilement l’accroître vers 500 ms. Le plus gros problème est que je me suis maintenant écarté de la norme: tous les autres documents, guides et exemples en ligne envoient des mises à jour régulières et interpolent entre les deux états. Cela semble incompatible avec mon architecture, et je dois mettre au point un meilleur algorithme de prédiction de mouvement, plus proche d'une architecture (très basique) de "physique en réseau".
Le serveur reçoit ensuite le paquet et détermine la vitesse du joueur à partir de son type de mouvement en fonction d'un script (le joueur est-il capable de courir? Obtenez la vitesse d'exécution du joueur). Une fois qu'il a la vitesse, il le combine avec la direction pour obtenir un vecteur - la vitesse de l'entité. Une détection de triche et une validation de base ont lieu et l'entité côté serveur est mise à jour avec la vitesse, la direction et la position actuelles. Une limitation de base est également effectuée pour empêcher les joueurs d’inonder le serveur de demandes de mouvement.
Après la mise à jour de sa propre entité, le serveur diffuse un paquet "Mise à jour de la position de l'avatar" à tous les autres joueurs à sa portée. Le paquet de mise à jour de position est utilisé pour mettre à jour les simulations physiques côté client (état mondial) des clients distants et effectuer la prévision et la compensation de décalage.
Prédiction et compensation du décalage
Comme mentionné ci-dessus, les clients font autorité pour leur propre position. Sauf en cas de triche ou d'anomalie, l'avatar du client ne sera jamais repositionné par le serveur. Aucune extrapolation ("déplacer maintenant et corriger plus tard") n'est requise pour l'avatar du client - ce que le joueur voit est correct. Cependant, une sorte d'extrapolation ou d'interpolation est requise pour toutes les entités distantes en mouvement. Une sorte de prédiction et / ou de compensation de décalage est clairement requise dans le moteur de simulation / physique local du client.
Problèmes
J'ai eu du mal avec divers algorithmes et j'ai un certain nombre de questions et de problèmes:
Devrais-je extrapoler, interpoler ou les deux? Mon instinct est que je devrais utiliser une extrapolation pure basée sur la vitesse. Le changement d'état est reçu par le client, ce dernier calcule une vitesse "prévue" qui compense le retard, et le système de physique habituel fait le reste. Cependant, tous les autres exemples de code et d'articles sont contraires à la règle: ils semblent tous stocker un certain nombre d'états et effectuer une interpolation sans moteur physique.
Lorsqu'un paquet arrive, j'ai essayé d'interpoler la position du paquet avec la vitesse du paquet sur une période de temps déterminée (par exemple, 200 ms). Je prends ensuite la différence entre la position interpolée et la position "d'erreur" actuelle pour calculer un nouveau vecteur et le place sur l'entité au lieu de la vitesse qui a été envoyée. Cependant, l'hypothèse est qu'un autre paquet arrivera dans cet intervalle de temps, et il est extrêmement difficile de "deviner" quand le prochain paquet arrivera - d'autant plus qu'ils n'arrivent pas tous à des intervalles fixes (c.-à-d. Que les changements d'état). Le concept est-il fondamentalement défectueux ou est-il correct mais nécessite quelques corrections / ajustements?
Que se passe-t-il lorsqu'un lecteur distant s'arrête? Je peux immédiatement arrêter l'entité, mais elle sera placée au "mauvais" endroit jusqu'à ce qu'elle se déplace à nouveau. Si j’estime un vecteur ou essaie d’interpoler, j’ai un problème car je n’enregistre pas l’état précédent. Le moteur physique n’a aucun moyen de dire "vous devez vous arrêter après avoir atteint la position X". Il comprend simplement une vitesse, rien de plus complexe. Je suis réticent à l'idée d'ajouter les informations "état de mouvement du paquet" aux entités ou au moteur physique, car elles enfreignent les principes de conception de base et saignent le code de réseau dans le reste du moteur de jeu.
Que devrait-il se passer lorsque des entités entrent en collision? Il existe trois scénarios: le joueur contrôlant se heurte localement, deux entités se heurtent sur le serveur lors d'une mise à jour de position ou une mise à jour d'entité distante se heurte sur le client local. Dans tous les cas, je ne sais pas comment gérer la collision - mis à part la triche, les deux états sont "corrects" mais à des périodes différentes. Dans le cas d’une entité distante, il n’a pas de sens de la dessiner en traversant un mur. C’est pourquoi je détecte les collisions sur le client local et le "stoppe". Sur la base du point 2 ci-dessus, je pourrais calculer un "vecteur corrigé" qui tente continuellement de déplacer l'entité "à travers le mur", ce qui ne réussira jamais - l'avatar distant est bloqué là jusqu'à ce que l'erreur devienne trop importante et qu'il "s'enclenche" position. Comment les jeux fonctionnent-ils autour de cela?
la source
Réponses:
La seule chose à dire est que 2D, isométrique, 3D, ils sont tous identiques quand il s'agit de ce problème. Parce que vous voyez de nombreux exemples en 3D et que vous utilisez uniquement un système de saisie 2D limité en octants avec une vitesse instantanée, cela ne signifie pas que vous pouvez vous débarrasser des principes de mise en réseau qui ont évolué au cours des 20 dernières années.
Les principes de conception doivent être damnés lorsque le jeu est compromis!
En éliminant les anciens et les actuels, vous vous débarrassez des quelques informations pouvant résoudre votre problème. J'ajouterais à ces données des horodatages et des décalages calculés afin que l'extrapolation puisse mieux prédire l'emplacement de ce joueur et que l'interpolation puisse mieux lisser les changements de vitesse dans le temps.
Ce qui précède est l'une des principales raisons pour lesquelles les serveurs semblent envoyer beaucoup d'informations d'état et non des entrées de contrôle. Une autre grande raison est basée sur le protocole que vous utilisez. UDP avec perte de paquets acceptée et livraison hors commande? TCP avec livraison assurée et tentatives? Quel que soit le protocole utilisé, vous obtiendrez des paquets à des moments bizarres, en retard ou empilés les uns sur les autres lors d'une vague d'activité. Tous ces paquets étranges doivent s'inscrire dans un contexte afin que le client puisse comprendre ce qui se passe.
Enfin, même si vos entrées sont très limitées dans 8 directions, le changement peut se produire à tout moment - imposer un cycle de 250 ms frustrera simplement les joueurs rapides. 30 joueurs n’ont rien de gros à gérer pour aucun serveur. Si vous parlez de milliers ... alors même que des groupes sont répartis sur plusieurs boxen, les serveurs individuels ne supportent qu'une charge raisonnable.
Avez-vous déjà profilé un moteur physique comme Havok ou Bullet en marche? Ils sont vraiment très optimisés et très, très rapides. Vous risquez de tomber dans le piège de supposer que l'opération ABC sera lente et d'optimiser quelque chose qui n'en a pas besoin.
la source
Donc, votre serveur est essentiellement un "arbitre"? Dans ce cas, je pense que tout dans votre client doit être déterministe; vous devez vous assurer que tout sur chaque client donnera toujours le même résultat.
Pour votre première question, une fois que le joueur local a reçu la direction des autres joueurs, mis à part le fait de pouvoir déceler son mouvement au fil du temps et d’appliquer des collisions, je ne vois pas comment vous pourriez prédire dans quelle direction le joueur va tourner, en particulier dans un 8 environnement de direction.
Lorsque vous recevez la mise à jour "de la position réelle" de chaque joueur (que vous pourriez peut-être essayer d’échouer sur le serveur), vous devrez interpoler la position et la direction du joueur. Si la position "supposée" est très fausse (le joueur a complètement changé de direction juste après l'envoi du dernier paquet de direction), vous aurez un énorme vide. Cela signifie que le joueur saute de position ou que vous pouvez interpoler vers la position estimée suivante . Cela permettra une interpolation plus douce dans le temps.
Lorsque des entités entrent en collision, si vous pouvez créer un système déterministe, chaque joueur peut simuler la collision localement et les résultats ne doivent pas être trop éloignés de la réalité. Chaque machine locale doit simuler la collision pour les deux joueurs, auquel cas il faut s’assurer que l’état final sera non bloquant et acceptable.
Pour le côté serveur, un serveur d’arbitre peut toujours effectuer des calculs simples pour vérifier, par exemple, la vitesse d’un joueur sur de courtes périodes, pour l’utiliser comme un simple mécanisme anti-fraude. Si vous surveillez chaque joueur plusieurs fois à la fois, votre détection de triche sera évolutive, mais il faudra plus de temps pour trouver des tricheurs.
la source
Ne pouvez-vous pas inclure la vélocité dans vos messages de changement d'état et l'utiliser pour prédire le mouvement? Par exemple, supposez que la vitesse ne change pas jusqu'à ce que vous obteniez un message disant que cela a changé? Je pense que vous envoyez déjà des positions, donc si quelque chose "dépasse" à cause de cela, vous avez de toute façon la bonne position de la prochaine mise à jour. Vous pouvez ensuite modifier les positions lors des mises à jour, comme vous le faites déjà, en utilisant la vélocité du dernier message et en écrasant la position chaque fois qu'un message est reçu avec une nouvelle position. Cela signifie également que si la position ne change pas, mais que la vélocité nécessite l'envoi d'un message (même si cela reste un cas valable dans votre jeu), cela n'affectera pas beaucoup votre consommation de bande passante, voire pas du tout.
L'interpolation ne devrait pas avoir d'importance ici, c'est-à-dire, par exemple, quand vous savez où quelque chose se trouvera à l'avenir, si vous l'avez, quelle méthode vous utilisez, etc. Êtes-vous confondu avec l'extrapolation peut-être? (pour lequel ce que je décris est un, simple, approche)
la source
Ma première question serait: qu'est-ce qui ne va pas avec un modèle où le serveur a autorité? Pourquoi est-il important que l'environnement soit 2D ou 3D? Si votre serveur faisait autorité, cela faciliterait beaucoup votre protection contre les tricheurs.
Lors de la prédiction, il est nécessaire de conserver plusieurs états (ou au moins des deltas) sur le client afin que, lorsque le / les état (s) faisant autorité est reçu du serveur, il puisse être comparé à ceux du client et vous permet de prendre les mesures nécessaires. corrections. L'idée est de garder autant que possible déterministe afin de minimiser le nombre de corrections nécessaires. Si vous ne conservez pas les états précédents, vous ne pouvez pas savoir si quelque chose de différent s'est passé sur le serveur.
Pourquoi avez-vous besoin d'interpoler? Le serveur faisant autorité doit annuler tout mouvement erroné.
Il existe des situations conflictuelles entre le serveur et le client, raison pour laquelle vous devez conserver des états sur le client afin que le serveur puisse corriger les erreurs éventuelles.
Désolé pour les réponses rapides, je dois partir. Lisez cet article , il mentionne les tireurs mais devrait fonctionner pour tout jeu nécessitant une mise en réseau en temps réel.
la source