Plateforme multijoueur - Des corrections de serveur sont-elles normalement requises avec un seul client sur le serveur?

10

Je travaille actuellement sur un jeu de plateforme multijoueur plutôt simple. J'ai lu pas mal d'articles sur les techniques utilisées pour masquer la latence, mais je n'arrive toujours pas à comprendre certains des concepts. Je trouve le sujet très intéressant et j'aime essayer des idées par moi-même, mais je pense que demander gamedev stackexchange sera plus efficace pour ma question. Je ferai de mon mieux pour décrire ma situation actuelle et quelle question s'est posée en cours de route.

Pour le moment, je veux seulement synchroniser un seul joueur avec le serveur. Théoriquement, j'ai supposé qu'un joueur avec une prédiction côté client ne nécessiterait pas de corrections du serveur, car il n'y a pas de facteurs externes qui influencent son mouvement. Par conséquent, mon prototype n'a actuellement qu'un seul joueur synchronisé avec un serveur sans que des corrections de serveur ne soient envoyées.

Si vous êtes familier avec la mise en réseau de jeux, je pense que vous pouvez sauter les sections contextuelles, même si j'ai peut-être fait quelque chose de mal en cours de route.

La boucle client (une fois par trame, une fois tous les ~ 16,67 ms)

La boucle client simplifiée ressemble à:

  1. Vérifiez l'entrée locale (WASD) et empaquetez-les sous forme d'actions (par exemple Type=MoveLeft, Time=132.0902, ID=15). Nous conservons les actions packagées pour éventuellement les envoyer plus tard. De plus, nous appliquons directement l'action souhaitée à la simulation physique locale du jeu. Par exemple, si nous avons une MoveLeftaction, nous appliquons une force vers la gauche sur la vitesse du joueur.

  2. Cochez pour envoyer des actions. Pour éviter d'abuser de la bande passante du client, envoyez uniquement les actions packagées à certains intervalles (par exemple 30 ms).

  3. Appliquer les modifications du serveur. À un certain moment, cela gérera les deltas et les corrections reçues par le serveur et les appliquera à la simulation locale du jeu. Pour cette question particulière, ce n'est pas utilisé.

  4. Mettez à jour la physique locale. Exécutez la boucle physique sur le lecteur principal. Fondamentalement, cela fait la prédiction côté client du mouvement du joueur. Cela ajoute de la gravité à la vitesse du joueur, applique la vitesse du joueur à sa position, corrige les collisions en cours de route, etc. Je dois préciser que la simulation physique est toujours exécutée avec des secondes delta fixes (appelées plusieurs fois en fonction des secondes delta réelles) .

Je saute quelques détails spécifiques sur la physique et d'autres sections parce que je pense qu'ils ne sont pas requis pour la question, mais n'hésitez pas à me faire savoir s'ils seraient pertinents pour la question.

La boucle du serveur (toutes les 15 ms)

La boucle de serveur simplifiée ressemble à:

  1. Gérer les actions. Vérifiez les packages d'actions reçus des clients et appliquez-les à la simulation physique du serveur. Par exemple, nous pourrions recevoir 5 MoveLeftactions, et nous appliquerions la force à la vitesse 5 fois . Il est important de noter qu'un package d'action complet est exécuté sur une "trame" , contrairement au client où il est appliqué dès que l'action se produit.

  2. Mettez à jour la logique du jeu. Nous mettons à jour la physique du jeu, déplaçant les joueurs et corrigeant les collisions, etc. Nous emballons également tous les événements importants qui ont été envoyés aux joueurs (par exemple, la santé d'un joueur a chuté, un joueur est décédé, etc.) plus tard.

  3. Envoyer des corrections. Nous envoyons régulièrement (par exemple une fois tous les 35 ms) des deltas à d'autres joueurs (par exemple, la position des joueurs, leur santé, etc.) s'ils ont récemment changé. Cette partie n'est pas actuellement implémentée, car je veux que la simulation d'un seul joueur donne les mêmes résultats sur le client et le serveur sans corrections, pour s'assurer que la prédiction côté client fonctionne correctement.

Le problème

Le système actuel fonctionne bien dans des circonstances simples, et j'ai été heureusement surpris de voir qu'il a donné des résultats très similaires avec de simples mouvements horizontaux (les inexactitudes sont dues à des erreurs de précision en virgule flottante, je crois):

La synchronisation fonctionne bien avec des collisions / mouvements simples

Veuillez ignorer les graphiques du prototype. Rectangle blanc = joueur, rectangles rouges = obstacles, bleu = fond

Cependant, je reçois des erreurs de synchronisation après avoir effectué des mouvements sensibles au temps, tels que sauter et se rapprocher d'un obstacle isolé:

La synchronisation ne fonctionne pas car j'ai sauté autour de l'obstacle spécifié à des moments sensibles au temps

En théorie, je m'attendrais à ce que les deux se retrouvent toujours avec les mêmes résultats, car aucun facteur externe n'influence le client sur sa position. Dans la pratique, cependant, je pense que je comprends le problème.

Étant donné que sauter autour d'un obstacle comme celui-ci dépend beaucoup du timing du joueur, de petites variations du moment où la vitesse est appliquée à la position auront des répercussions sur le résultat (par exemple, le client pourrait s'éloigner juste à temps pour éviter une collision avec le obstacle, alors que le serveur le ferait en recevant le package d'action plus tard et en restant coincé sur l'obstacle pendant un petit laps de temps, en modifiant le résultat final). La différence entre la façon dont le client et le serveur le gèrent réside principalement dans le fait que le client effectue toutes ses actions au fur et à mesure qu'elles se produisent, tandis que le serveur les fait toutes en bloc lorsqu'il les reçoit.

La question

Ce long contexte mène finalement à ma question (merci d'avoir lu jusqu'ici): est-il normal d'exiger des corrections de serveur même lorsqu'il n'y a qu'un seul joueur synchronisé avec le serveur, ou dois-je utiliser certaines techniques pour éviter la désynchronisation dans des situations sensibles au facteur temps ?

J'ai pensé à certaines solutions possibles, dont certaines me mettent moins à l'aise:

  1. Implémentez la correction du serveur. Supposez simplement qu'il s'agit d'un comportement normal et corrigez les erreurs à mesure qu'elles se produisent. Je voulais quand même le mettre en œuvre, mais je voulais juste m'assurer que ce que j'ai fait jusqu'à présent est acceptable.

  2. Utilisez le temps client fourni pour appliquer les actions souhaitées. Je suppose que cela serait similaire à la compensation de décalage, nécessitant de "remonter le temps" et de vérifier les mouvements. Un peu comme appliquer des corrections de serveur, remonter dans le temps et réappliquer les actions suivantes après cela. Je n'aime vraiment pas l'idée. Il semble complexe, coûteux en ressources et nécessite de faire confiance au temps donné au client (même si je prévois vraiment de vérifier que le temps semble relativement légitime).

  3. Demandez à GameDevelopment StackExchange une excellente nouvelle idée qui résoudra tous mes problèmes.

Je ne fais que commencer dans le monde du jeu en réseau, alors n'hésitez pas à corriger / critiquer / insulter l'un des concepts ci-dessus ou à donner des idées / ressources qui pourraient m'aider tout au long de mon voyage dans le monde merveilleux du réseautage. Pardonnez-moi si j'aurais pu trouver ma réponse ailleurs, j'ai échoué.

Merci beaucoup pour votre précieux temps.

Jesse Emond
la source
Votre serveur et votre client exécutent des trames à des taux différents. Que se passe-t-il si le client effectue deux actions sur des trames consécutives, mais que le serveur voit un écart d'une trame entre elles?
user253751
@immibis s'est basé sur gafferongames.com/game-physics/fix-your-timestep pour minimiser cet effet.
Jesse Emond

Réponses:

11

Dans de tels cas, il vaut mieux laisser le client faire légèrement autorité. Pour des contrôles aussi précis, il est extrêmement peu probable que vous obteniez un bon comportement, même avec une correction et une prédiction très avancées.

Le client doit passer de l'envoi de messages "J'ai sauté" à l'envoi de messages "J'ai sauté de X, Y au temps T". Le serveur vérifie ensuite que l'emplacement est à proximité de ce qu'il pense que le joueur était au moment T (que vous pouvez limiter à un temps raisonnablement court dans le passé) pour vous protéger contre la tricherie, puis simulez le saut à partir de la position du client expédié. Le serveur ne corrige le client que lorsqu'il est loin de se détraquer (généralement en raison d'un décalage ou similaire).

Ce type de technique est utilisé en conjonction avec la correction et l'interpolation afin de rendre le jeu réactif sur le client local et de paraître fluide pour les clients distants.

Sean Middleditch
la source
Très intéressant. Je vais essayer de mettre en œuvre cela et voir comment ça se passe. Merci beaucoup d'avoir pris le temps de répondre. Soit dit en passant, vous avez mentionné «à proximité» et «un temps relativement court dans le passé», vérifieriez-vous simplement avec une distance et un temps constants, respectivement? Ou vous utiliseriez des techniques plus sophistiquées comme garder un historique des positions et utiliser le temps d'aller-retour moyen du client (ou toute autre chose, vraiment)?
Jesse Emond
Tout ce qui fonctionne pour votre jeu. Commencez simplement, compliquez-le uniquement dans la mesure où vous le jugez nécessaire. Certains serveurs conservent un historique d'instantanés pour ~max(RTT)les ticks du serveur dans le passé, mais je ne sais pas si vous en avez besoin pour votre jeu en particulier. Cela peut être encore plus pratique pour les jeux de style tireur où vous voulez également faire un certain niveau de détection de coup / coup sur le client et devez non seulement savoir où était un joueur il y a 46 ms mais aussi où sa cible était il y a 46 ms et où tout déplacer les plates-formes d'occlusion où.
Sean Middleditch
Parfait. Je vais expérimenter et voir ce qui fonctionne le mieux. En la marquant comme réponse acceptée, merci encore!
Jesse Emond