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?