Comment fonctionne la prédiction côté client?

33

J'ai lu Valve + Gafferon et des centaines de pages de Google, mais pour une raison quelconque, je ne comprends pas très bien comment prévoir les clients.

À ma connaissance, le problème fondamental est le suivant:

  • Le client A envoie une entrée à T0
  • Le serveur reçoit les entrées à T1
  • Tous les clients reçoivent le changement à T2

A T2Cependant, en utilisant la prédiction du client, le client A est maintenant à une appropriée de position T4.

Comment vous assurez-vous que le client A, lorsqu'il prédit que le serveur acceptera la demande de mouvement, ne sera pas en avance sur le serveur? Évidemment, tout le temps où ils sont en avance, il en résulte un retour à l'endroit où le serveur les a vu pour la dernière fois. Avec toutes les corrections que j'ai essayées, ceci est toujours visible lorsque vous vous arrêtez, car le serveur s'arrête derrière vous.

Chris Evans
la source

Réponses:

35

J'ai écrit une série d'articles à ce sujet. Il est basé sur les mêmes idées que vous avez lues ailleurs, mais expliquées de manière très détaillée et (j'espère) accessible.

En particulier, l'article sur la prédiction côté client est celui-ci .

Ggambett
la source
Excellents articles :-) J'adorerais voir la quatrième partie de la série. Comme petite suggestion, un lien vers la partie suivante à la fin de chacun des articles améliorerait certainement la navigation.
OU Mapper
5
@ORMapper - J'ai finalement écrit le 4ème article! gabrielgambetta.com/fpm4.html
ggambett
Bravo pour votre série d'articles :-) Très utile, merci :-)
OU Mapper
Tous les articles (je pourrais trouver) qui parlent de reconstruction du passé en utilisant des instantanés stockés prennent la prise de vue à titre d'exemple. Cela s'applique-t-il également au mouvement? J'imagine que la resimulation des mouvements peut entraîner de grandes différences pour les autres joueurs s'ils peuvent entrer en collision. Supposons que deux joueurs se déplacent l'un contre l'autre et que l'un d'eux arrête de bouger à quelques pas d'un point de collision. Ces ordres d'arrêt arrivent en retard à cause d'un décalage, donc si nous resimulions le monde, les deux joueurs seraient dans des positions très différentes
Lope
C'est une question intéressante. Malheureusement, je n'ai pas de réponse définitive. Je suppose que cela dépend de la criticité des mouvements pour le jeu; est-ce que vous venez de tomber sur quelqu'un d'autre et rien ne se passe? Dans ce cas, le serveur ne s'en soucie probablement pas, c'est considéré comme une erreur de prédiction (nous l'avons tous vu se produire en points d'étranglement, n'est-ce pas?). Tuez-vous l'autre joueur au contact? Dans ce cas, il est beaucoup plus important de bien faire les choses et il peut être utile de les reconfigurer.Notez qu’à un moment donné, vous devez rejeter certains paquets comme "trop ​​anciens", sinon vous risqueriez d’être à nouveau simulés de t = 0 à tout moment.
Ggambett
4

Je n'ai pas encore implémenté ceci (donc il pourrait y avoir des problèmes que je ne vois pas immédiatement), mais je pensais que j'essaierais de vous aider.

Voici ce que vous avez dit se passe:

Le client A envoie une entrée à T0

Le serveur reçoit une entrée au T1

Tous les clients reçoivent le changement à T2

Cependant, à T2, en utilisant la prédiction client, le client A se trouve maintenant à une position appropriée pour T4.

Il serait probablement utile de penser en termes de temps serveur. Son (probablement) très similaire à la façon dont fonctionne l' interpolation .

Chaque commande est envoyée avec une heure de serveur. Cette heure de serveur est calculée au début d'une correspondance en interrogeant le tick du serveur, en compensant la durée du ping. Sur le client, vous avez votre propre compte de ticks local et chaque commande que vous envoyez est convertie en ticks de serveur (c'est une simple opération de soustraction).

De plus, le client restitue toujours "dans le passé". Donc, vous supposez que le monde que le client voit est, disons, 100 ms en retard sur le temps réel du serveur.

Donc reformulons votre exemple avec l'heure du serveur (désignée par S).

Le client envoie une entrée à T0 avec l'heure du serveur S0 (ce qui, je suppose, est "la représentation du temps du serveur par le client moins le temps d'interpolation"). Le client n'attend pas la réponse du serveur et se déplace immédiatement.

Le serveur reçoit une entrée en T1. Le serveur détermine la position faisant autorité du client à l'heure du serveur SO donnée par le client. Envoie cela au client.

Le client reçoit la position faisant autorité en T2 (toujours avec la désignation de l’heure du serveur SO). Le client garde une trace de la durée passée des événements précédents (probablement juste une file d'attente de toutes les prédictions non confirmées).

Si la position / vitesse / tout ce que le serveur renvoie à S0 est différent de ce que le client a stocké à S0, le client s'en charge en quelque sorte. Soit en ramenant le joueur à sa position passée, soit en ré-imitant l'entrée précédente, soit peut-être quelque chose d'autre auquel je n'ai pas pensé.

Tetrade
la source
3
Tout cela est correct sauf le bit concernant le rendu du client dans le passé. Par rapport au serveur, le client effectue le rendu dans le futur! Le serveur sait que les informations qu'il a de chaque client sont anciennes et que chaque client aura déjà changé depuis.
Kylotan
2

En fait, il y a une implémentation open-source dans github qui montre comment faire. Découvrez Lance.gg

github repo: https://github.com/lance-gg/lance

Le code de prédiction client est implémenté dans le module appelé src/syncStrategies/ExtrapolateStrategy.js

Outre l'extrapolation, il y a deux concepts que je n'ai pas vus mentionnés ci-dessus:

  1. Flexion incrémentielle. Fondamentalement, plutôt que d'appliquer la correction du serveur en une seule fois, vous laissez le delta s'appliquer par petits incréments. De cette façon, les objets distants ajusteront progressivement leurs positions pour correspondre aux positions du serveur. Il y a flexion de position, flexion de vitesse, flexion d'angle et flexion de vitesse angulaire. Vous pouvez également vouloir différents facteurs de flexion pour différents objets.
  2. Étape de reconstitution. Le fait que les données se trouvent dans le passé signifie que vous pouvez revenir à l'heure du serveur et remonter à partir de ce moment. Bien sûr, vous devrez toujours vous pencher vers la nouvelle position, plutôt que de sauter à la position.
Gary Weiss
la source
1

Le client A est toujours en avance sur le serveur - mais cela n'a pas d'importance. Vous devez seulement ramener le client en arrière si le serveur dit qu'il y a eu un problème avec la position signalée, à quel point le client réexécute tous les changements qu'il a apportés depuis l'erreur avec les valeurs corrigées, pour le rendre à un état compatible. avec le serveur.

Pour ce faire, le client doit se souvenir de certains états et mises à jour passés. Ce ne peut être que quelques valeurs simples telles que la position, la vitesse, l’orientation, ce genre de chose. Le serveur enverra périodiquement un accusé de réception indiquant que différentes mises à jour du client étaient légitimes, ce qui signifie qu'elles peuvent maintenant être oubliées du client. Toutefois, si le serveur signale qu'une mise à jour était invalide, l'état du client revient à ce point et les modifications futures sont appliquées à cet état modifié.

Il y a quelques liens supplémentaires au bas de l'article de Valve qui méritent d'être lus - c'est l'un d'entre eux: https://developer.valvesoftware.com/wiki/Prediction

Kylotan
la source
Alors, ai-je raison de penser que le client (at t=4) reçoit des informations à propos de t=2, de sorte qu'il réinitialise l'état pour réexécuter t=2ensuite les mises à jour pour amener les objets de t=2à t=4?
George Duckett
Je ne le sais toujours pas pour une raison quelconque. Le serveur n'est pas informé de la position du lecteur, mais uniquement des entrées. Le joueur se déplace donc de la dernière position à laquelle le serveur a indiqué se trouver. L'entrée est appliquée. Le serveur est informé. Le serveur confirme l'entrée à tout le monde. En supposant que toutes les commandes soient acceptées, le serveur sera toujours derrière le client A. Ainsi, lorsque le client A s’arrête, son caractère s’arrête immédiatement, puis il revient à l’emplacement du serveur lorsqu’il reçoit la confirmation de l’arrêt.
Chris Evans
@GeorgeDuckett: oui (bien que cela ne soit pas nécessairement t = 4, cela pourrait être le cas chaque fois qu'une anomalie serait détectée et qu'il pourrait y avoir un nombre quelconque de mises à jour
réappliquées
@ChrisEvans: l'état connu + les changements basés sur l'entrée sont quand même équivalents à l'état d'envoi. En ce qui concerne l'exemple d'arrêt, il s'agit en soi d'une entrée et le serveur continue de simuler le mouvement jusqu'à ce qu'il reçoive cette entrée. En supposant une latence constante, le serveur empêchera le lecteur de se déplacer exactement à la même position que celle que le client a vue lorsqu'il a arrêté de se déplacer, car le client était en avance sur le serveur. (Dans le monde réel, la latence varie, vous interpolez donc un peu pour le lisser.)
Kylotan