Concevoir un jeu multijoueur est loin d'être une tache aisée. Même si Unity3D simplifie grandement le développement, ce moteur reste trés général et ne simplifie pas tout. Un probléme récurent qui est souvent présenté sur les forums d'unity concerne le mouvement autoritatif d'un object ou d'un joueur à travers le réseau.
Lors qu'on développe naivement un jeu en ligne, on a tendance à penser que chaque client doit pouvoir gérer son joueur. Malheureusement cette approche laisse l'entrée grande ouverte à tous les tricheurs qui peuvent alors simplement modifier leur jeu local pour gagner.
La meilleure façon d'empecher un client modifié d'effectuer des mouvements interdits par le jeu est de rendre le controle au serveur. Pour tricher il faut donc modifier le serveur, ce qui est tout de suite moins aisé. Pour centraliser, il faut donc que notre client envoie au serveur les touches préssées par le joueur. Le serveur peux alors simuler le déplacement de l'avatar à partir des commandes et envoyer la nouvelle position au client pour qu'il l'affiche.
Cette partie du code est décrite dans un premier article.
Le probléme avec cette approche, c'est que du coup lorsqu'un joueur presse une touche, son personnage met du temps a bouger (le temps que l'information fasse l'aller puis le retour jusqu'au serveur). Ce délai rend le jeu désagréable voir impossible a jouer.
Pour avoir un jeu réactif, il faut donc que le client execute les commandes du joueur en temps réél. Cette partie s'apelle la prédiction de mouvement. En effet, le client et le serveur travaillent avec le même jeu de données (même map par exemple). Donc, si le client et le serveur executent le même code avec la même entrée, dans 99% des cas le résultat sera le même.
En se basant sur ce constat donc, le client peux prédire le mouvement qui s'effectuera sur le serveur en executant de son coté le mouvement. Comme tout est en local, on affiche donc le mouvement en instantanné pour le joueur.
Cette partie du code est décrite dans un second article (à venir).
Nous avons donc un client qui avance en instantanné, mais le serveur lui est en retard sur le client (le temps que la commande arrive). Donc, lorsque le serveur confirme un mouvement, le client qui reçois la confirmation à probablement déjà executé plusieurs autres commandes entre temps.
Il faut donc réconciler l'état actuel (dans le présent) avec l'état (dans le passé) reçu du serveur (en retard de 2 * votre ping).
La solution consiste à réconcilier le client avec le serveur. C'est a dire integrer l'état validé par le serveur dans notre état présent.
Pour résoudre cela, nous allons donc repartir de la version confirmée par le serveur, et rejouer par dessus toutes les commandes que l'on a enregistré mais que le serveur n'a pas encore confirmé. De cette façon, on avance notre état valide (mais passé) jusqu'a arriver a notre état présent (prédit).
De cette façon si le serveur a effectué la même simulation que notre client, les deux versions concordent et tout va bien. Sinon, la nouvelle position remplace l'ancienne et l'erreur de prédiction est corrigée.
Cette partie du code est décrite dans mon troisiéme article (à venir).
Comme une image vaux bien une centaine de mots, voici un graphique qui montre comment marche l'architecture décrite précédemment.
Graphique montrant comment s'enchaine les étapes de prédictions et reconciliation
Dans cet article, j'ai montré comment rendre responçable le serveur du mouvement des joueurs. Nous avons donc le serveur qui contient la simulation vraie et chaque joueur qui contient une simulation prédite de l'état de son entité.
La synchronisation de cette simulation vraie vers les autres joueurs fera l'object d'un autre article, qui montrera comment on peux, a partir du systéme précédent, synchroniser des entités sans lag visible.