Le réseau dans les jeux vidéo

Ce que tout programmeur doit savoir sur le réseau dans les jeux vidéo

Glenn Fiedler

Traducteur

Bousk

Vous êtes programmeur. Vous êtes-vous déjà demandé comment les jeux multijoueurs fonctionnent ?

Commentez

- Titre : Le réseau dans les jeux vidéo

- Auteur : Glenn Fiedler

- Traducteur : Bousk

- Parution : 6 janvier 2016

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Glenn Fiedler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Peer-to-peer par pas

- III. Client/Serveur

- IV. La prédiction chez le client

- V. Remerciements

I - Introduction

Vous êtes programmeur. Vous êtes-vous déjà demandé comment les jeux multijoueurs fonctionnent ?

De l'extérieur, ça a l'air magique : deux joueurs ou plus partagent une expérience consistante à travers le réseau comme s'ils existaient dans le même monde virtuel. Mais en tant que programmeurs, nous savons que ce qui se passe réellement est très différent de ce que l'on voit. Que tout n'est qu'illusion. Un énorme tour de passe-passe. Ce que vous percevez comme une réalité partagée n'est qu'une approximation propre à votre propre point de vue et espace-temps.

II - Peer-to-peer par pas

Au départ, les jeux en réseau utilisaient du peer-to-peer (P2P), où chaque machine échangeait avec chaque autre dans une topologie entièrement connectée. De nos jours, vous pouvez toujours constater ce modèle dans les jeux de types STR (RTS), ce qui est intéressant pour certaines raisons, peut-être parce qu'il s'agissait de la première façon de faire - beaucoup de personnes croient que les jeux réseau fonctionnent encore ainsi.

L'idée de base est de penser le jeu comme un jeu tour par tour où à chaque tour vous devez appliquer un ensemble de commandes. Leur traitement au début de chaque tour définit l'évolution de l'état du jeu. Par exemple, déplacer une unité, en attaquer une autre, construire un bâtiment. Seul cet ensemble de commandes nécessite d'être transféré sur le réseau et être appliqué à l'identique sur les machines des joueurs. Tous les joueurs démarrent à un état initial connu et commun.

Bien sûr, ceci est très simplifié et occulte de nombreuses subtilités, mais ça suffit à faire comprendre l'idée de base du fonctionnement du réseau d'un jeu type STR. Vous pouvez en savoir plus sur ce modèle ici : 1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond.

Ça semble si simple et élégant, mais malheureusement il y a de nombreuses limitations.

D'abord, il est exceptionnellement difficile de s'assurer qu'un jeu est complètement déterministe ; que chaque tour se joue de manière identique sur chaque machine. Par exemple, une unité pourrait prendre un chemin très légèrement différent sur deux machines, arriver un peu plus tôt dans une bataille et permettre sa victoire sur une machine, alors qu'elle arrive un peu plus tard sur l'autre et résulte en la défaite. Tout comme un battement d'ailes de papillon qui cause une tornade de l'autre côté de la planète, une petite différence peut engendrer une complète désynchronisation après quelque temps.

Ensuite, pour s'assurer que le jeu se déroule identiquement sur chaque machine, il est nécessaire d'attendre que chaque joueur ait reçu les commandes dudit tour avant de le simuler. Ce qui signifie que chaque joueur a une latence égale à la plus élevée parmi eux. Les STR la camouflent généralement en fournissant un retour audio immédiat et/ou en proposant une animation cosmétique, mais finalement chaque action qui affecte réellement le jeu ne peut survenir qu'après cette latence.

Enfin à cause de la façon dont le jeu se synchronise en envoyant juste les messages de commandes qui modifient son état. Pour que ça fonctionne, il est nécessaire que chaque joueur parte du même état initial. Typiquement, chaque joueur devra rejoindre un lobby avant de commencer à jouer, bien qu'il soit techniquement possible de supporter l'arrivée dans une partie en cours, ceci n'est pas commun dû à la difficulté de capturer et transmettre un point de départ totalement déterministe au milieu d'une partie en cours.

Malgré toutes ces limitations, ce modèle convient naturellement aux STR et est toujours utilisé aujourd'hui dans des jeux comme « Command and Conquer », « Age of Empires » et « Starcraft ». La raison est que dans un STR l'état du jeu consiste en des centaines d'unités et serait simplement trop conséquent à échanger entre les joueurs. Ces jeux n'ont pas d'autre solution que d'échanger les commandes qui font évoluer l'état de la partie.

Mais pour les autres genres de jeux, les choses ont évolué. Voilà ce qu'il en est pour le modèle peer-to-peer par pas. Maintenant, regardons l'évolution des jeux d'action, à commencer par Doom, Quake et Unreal.

III - Client/Serveur

À l'époque des jeux d'action, les limites du peer-to-peer par pas sont apparues sur Doom qui, bien que totalement jouable en LAN, était totalement injouable sur Internet pour les joueurs typiques :

Bien qu'il soit possible de connecter deux machines jouant à DOOM par Internet avec une connexion modem, le jeu sera lent, injouable (avec une connexion 14.4 Kbps PPP) à tout juste jouable (avec une connexion 28.8 Kbps et un modem utilisant SLIP - Serial Line Internet Protocol). Puisque ce type de connexion est très peu fréquent, ce document se concentrera sur les connexions directes par Internet. (faqs.org)

Le problème bien sûr était que Doom était fait pour du réseau en LAN uniquement, et utilisait le modèle peer-to-peer par pas décrit précédemment pour les jeux STR. À chaque tour, les entrées du joueur (bouton pressé, etc.) étaient échangées avec les autres machines, et avant que chaque joueur puisse simuler une frame il fallait recevoir les entrées de chacun des autres joueurs.

En d'autres termes, avant que vous puissiez tourner, vous déplacer ou tirer, vous devez attendre les entrées du joueur ayant la latence la plus élevée. Essayez d'imaginer les grincements de dents et pleurs d'un joueur qui écrit plus haut « ce type de connexion est très peu fréquent » .

Pour que le jeu soit jouable autrement qu'en LAN, dans les universités d'élite ayant un réseau correct et les grandes entreprises, il était nécessaire de changer le modèle. Et en 1996, c'est exactement ce que John Carmack fit quand il sortit Quake en utilisant un modèle client/serveur au lieu du peer-to-peer.

Maintenant, au lieu que chaque joueur joue la même simulation et échange directement avec chaque autre, chaque joueur est désormais un « client » et ils communiquent tous avec un seul autre ordinateur appelé « serveur ». Il n'y a plus besoin que le jeu soit déterministe sur les différentes machines, parce que le jeu n'existe réellement que sur le serveur. Chaque client est un stupide terminal affichant une approximation du jeu telle que le serveur le joue.

Dans un modèle pur client/serveur, vous ne simulez aucun code du jeu localement, vous envoyez vos entrées comme les boutons appuyés, mouvement de souris et clics au serveur. En réponse, le serveur met à jour l'état de chaque personnage dans le monde et répond avec un paquet contenant l'état de votre personnage et des autres joueurs alentour. Chaque client doit faire sa propre interpolation entre ces mises à jour pour fournir l'illusion d'un mouvement lisse et BAM, vous voilà avec un jeu client/serveur.

C'était un grand pas en avant. La qualité du jeu dépendant maintenant de l'état de la connexion au serveur au lieu du joueur ayant la latence la plus élevée. Il était aussi devenu possible pour les joueurs d'arriver et partir en milieu de partie, et le nombre de joueurs s'améliorait à mesure que la bande passante moyenne nécessaire par joueur diminuait.

Mais il restait des problèmes avec le modèle purement client/serveur :

Alors que je me souviens et peux justifier chacune de mes décisions sur le réseau de DOOM à Quake, le fait est que je travaillais avec les mauvaises suppositions initiales pour faire un bon jeu via Internet. Mon design original visait des connexions avec des latences < 200 ms. Les gens qui avaient une bonne connexion Internet chez de bons fournisseurs avaient une plutôt bonne expérience de jeu. Malheureusement, 99 % des personnes dans le monde ont une connexion via un modem ppp ou slip, souvent chez un fournisseur surchargé. Ceci résulte en une latence de 300 ms, minimum. Client. Modem de l'utilisateur. Modem du fournisseur. Serveur. Modem du fournisseur. Modem de l'utilisateur. Client. Mon Dieu, que c'est mauvais.
Ok, j'ai fait une erreur. J'ai une connexion T1 chez moi, donc je n'étais juste pas familier avec les possesseurs de PPP. Je m'en occupe maintenant.

Le problème était bien sûr la latence.

Ce que John a fait ensuite quand il sortit QuakeWorld révolutionna l'industrie.

IV - La prédiction chez le client

Dans la version originale de Quake, vous ressentiez les latences entre les ordinateurs et le serveur. Appuyez pour avancer et vous attendrez aussi longtemps qu'il faut aux paquets pour voyager jusqu'au serveur puis revenir chez vous avant de commencer à vous déplacer. Appuyez sur la touche de tir et vous attendez autant avant de tirer.

Si vous avez joué à un FPS moderne comme Call of Duty : Modern Warfare, vous savez que ce n'est plus le cas. Alors comment les FPS modernes ont pu donner l'impression de supprimer la latence de vos actions en multijoueur ?

Ce problème a été historiquement résolu en deux temps. D'abord par la prédiction des mouvements côté client développée par John Carmack pour QuakeWorld, puis incorporée plus tard dans le modèle réseau d'Unreal par Tim Sweeney. Puis par la compensation de la latence développée par Yvan Bernier chez Valve pour Counterstrike. Dans ce paragraphe, nous nous concentrerons sur cette partie tout d'abord - cacher la latence sur le mouvement du joueur.

À propos de ses plans pour la sortie imminente de QuakeWorld, John Carmack disait :

J'autorise maintenant le client à deviner le résultat du mouvement des joueurs jusqu'à ce qu'une réponse autoritaire du serveur arrive. Ceci est un graaand changement d'architecture. Le client a maintenant besoin de connaître la solidité des objets, la friction, la gravité, etc. Je suis triste de voir l'élégante solution du client comme un terminal disparaître, mais je suis pratique avant d'être idéaliste.

Donc maintenant pour supprimer la latence, le client exécute plus de code que précédemment. Ce n'est plus un terminal stupide qui envoie des entrées au serveur et interpole entre les états retournés par le serveur. Il est capable de prédire le mouvement de votre personnage localement et immédiatement selon vos entrées, en exécutant une partie du code de jeu pour votre personnage sur la machine cliente.

Maintenant dès que vous pressez une touche de déplacement, il n'y a plus d'attente d'un aller-retour entre le client et le serveur - votre personnage commence à se déplacer immédiatement.

La difficulté de cette approche n'est pas dans la prédiction : la prédiction n'utilise que le code du jeu classique - faire évoluer l'état du personnage dans la direction choisie par le joueur à une vitesse correcte. La difficulté est d'appliquer la correction quand la réponse du serveur est reçue pour résoudre les cas où le client et le serveur sont en désaccord sur ce que le personnage devrait faire et ce qu'il est en train de faire.

À ce moment, vous pourriez vous poser la question. Hé, si on exécute le code chez le client - pourquoi ne pas juste rendre le client autoritaire sur son personnage ? Le client pourrait exécuter la simulation pour son propre personnage et simplement indiquer au serveur où il se trouve à chaque paquet envoyé. Le problème est que si chaque joueur était capable de simplement dire au serveur « voici ma position actuelle », il serait trivial de modifier le client pour qu'un tricheur puisse instantanément esquiver la roquette sur le point de le toucher, ou se téléporter instantanément derrière vous pour vous tirer dans le dos.

Donc dans les FPS, il est absolument nécessaire que le serveur soit autoritaire sur l'état de chaque personnage, bien que chaque joueur prédise localement leurs déplacements pour cacher la latence. Comme Tim Sweeney écrit dans l'architecture réseau d'Unreal : « The Server Is The Man », le serveur fait la loi.

C'est ici que ça devient intéressant. Si le client et le serveur sont en désaccord, le client doit accepter la mise à jour de la position du serveur, mais à cause de la latence entre le client et le serveur, cette correction est forcément dans le passé. Par exemple, s'il faut 100 ms pour envoyer un paquet du client au serveur, et 100 ms du serveur au client, alors chaque correction du serveur pour la position du personnage sera 200 ms dans le passé, relativement au temps maximal pour lequel le client a prédit son propre mouvement.

Si le client appliquait simplement la correction du serveur telle quelle, il provoquerait un à-coup et renverrait le client dans le passé, au début du déplacement en annulant complètement sa prédiction. Alors, comment résoudre ce problème tout en autorisant le client à faire des prédictions ?

La solution est de garder un tampon circulaire de l'état passé du personnage et des entrées pour le joueur local sur le client, pour que quand le client reçoit une correction du serveur, il commence par rejeter tous les états plus anciens que l'état corrigé du serveur, et rejoue la simulation avec pour état initial l'état corrigé reçu du serveur jusqu'au moment « prédit » présent en utilisant les entrées du joueur stockées dans le tampon circulaire. Dans les faits, le client « rembobine et rejoue » de manière invisible les n dernières frames du mouvement du personnage du joueur local tandis que le reste du monde est fixe.

Ainsi le joueur semble contrôler son propre personnage sans latence, et puisque la simulation chez le client et le serveur sont déterministes - avec les mêmes entrées, on obtient les exacts mêmes résultats sur le client et le serveur - il est rarement corrigé. C'est ce que Tim Sweeney décrit :

… le meilleur de chaque monde : dans tous les cas, le serveur reste totalement autoritaire. Presque tout le temps, la simulation des mouvements du client est exactement identique au mouvement effectué par le serveur, donc la position du client est rarement corrigée. Seulement dans de rares cas, comme un joueur touché par une roquette, ou qui saute sur un ennemi, la position du client a besoin d'être corrigée.

En d'autres mots, seulement quand le personnage du joueur est affecté par quelque chose d'externe aux entrées du joueur, ce qui ne peut pas être prédit par le client, la position du joueur doit être corrigée. Ceci bien sûr, si le joueur essaye de tricher .

V - Remerciements

Cet article est une traduction autorisée de l'article de Glenn Fiedler.

Merci aussi à f-leb pour sa relecture orthographique.