Mouvement autoritatif en réseau partie 2: L'envoi des commandes

Dans un jeu multijoueur, si on veux garder l'autorité au niveau du serveur, il faut que tous les calculs importants (mouvement, tir) soient effectués sur celui-ci. Dans cette premiére partie nous allons donc traiter l'envoi des commandes du client vers le serveur.

Cet article sera plutot court car c'est une partie simple qui permet de faire une entrée en matiére doucement.

Le principe général

Pour rappeller notre principe général, le client écoute les commandes du joueur (J'appuye sur le boutonavancer pendant 3 secondes) et les envoie au serveur. Le serveur effectue alors la commande (par exemple avancer un personnage de 5 unités par seconde) et renvoie le résultat final au client (mon joueur a avancé de 15 unités).

Coté client: L'envoi des commandes

Pour atteindre notre ojectif, on doit transmettre les commandes du client vers le serveur. Suivant le type du jeu, on doit aussi faire attention à ne pas ajouter trop de délai.

Dans mon cas, pour un FPS rapide (a la quake), j'ai décidé de capter une image de l'état des touches à intervalle régulier quoi qu'il arrive. En effet, on a besoin d'un flux constant de commandes car il est fort probable que celles-ci changent rapidement entre les états capturés.

Cette partie est a adapter en fonction du type du jeu: Pour un jeu de poker, on enverra juste lorsque le joueur choisi une carte par exemple. Pour un RTS, on envoie la commande directement, avec une information sur l'heure ou elle à été effectuée.

Coté réseau: Le probléme du timing

En réseau synchrone, tout est une histoire de timing. En envoyant uniquement l'état des touches, on doit informer le serveur du temps qui s'écoule entre deux changement d'êtat pour qu'il puisse simuler le mouvement correctement.

De plus, notre intervalle d'envoi doit être totalement indépendant du nombre de frames par seconde pour garantir une certaine constance entre des machines différentes.

Dans mon cas, j'ai utilisé la méthode FixedUpdate d'unity avec un intervalle de 0.2. Cela me garanti une execution indépendante du nombre de frames par seconde. De plus, comme le client et le serveur utilisent le même intervalle, on n'a pas a envoyer cet intervalle dans les données (reduction des données à transférer).

De plus, pour ne pas surcharger la connection, j'ajoute les états a un buffer interne (une file) et j'envoie les packets du buffer toutes les 0.33 secondes sur une channel unreliable.

Coté serveur: On joue les commandes et on envoie le résultat.

Dés que le serveur reçoit un paquet, il execute toutes les commandes dans l'ordre. Le joueur avance alors dans le temps au fur et a mesure qu'on avance dans les states reçus.

Quand la simulation est terminée, le serveur envoie au client le numero du dernier état executé ainsi que la nouvelle position du joueur.

Le client, de son coté, peut alors supprimer du buffer les états validés par le serveur et déplacer le joueur affiché à sa nouvelle position.

Conclusion

A travers cet article, nous avons mis en place un mouvement autoritatif entre un client et un serveur.

Voici le résultat final de notre implementation:

mouvementautoritatifclientsansprediction

Graphique montrant comment s'enchaine les étapes dans un client sans prediction

Malheureusement, comme notre client attend que le serveur confirme un mouvement avant de l'afficher, on se retrouve avec un délai entre la pression d'un bouton et l'action associée qui rend le jeu totalement injouable. Le délai est représenté sur le graphique et vaux 2 fois le temps de latence entre le client et le serveur.

Pour compenser cela on utilise une technique de prédiction coté client qui sera décrite dans l'article suivant.

Qu'avez vous pensé de cet article?