Mise a jours du jeu de frag de zombies, en multijoueur, en ligne.
Il y a 2 jours, j’ai posté un petit jeu, fait en une matinée dans lequel il fallait tuer des zombies. Suite à un malentendu, on m’a conseillé d’ajouter un mode en ligne. Donc, je ne connais pas vraiment ça (j’ai un peu touché au networking avec un plug-in (Tnet), mais ici, ça ne collerait pas, vu que ça nécessite un serveur. J’ai donc fait ça avec les fonctions de réseau de base Unity. Étant un gros débutant là dedans, je suis parti d’un petit tuto, et j’ai brodé tout ça autour de mon jeu.
Le jeu :
Le jeu se joue à 2 joueurs.
Chaque joueur a une couleur (Bleu, ou Rouge), chaque zombie aussi. Les zombies bleus attaquent le joueur rouge, et les zombies rouges attaquent le joueur bleu. Le but est de survivre le plus longtemps possible, mais pour chaque zombie tué, 2 nouveaux apparaissent, l’idée est donc de tuer les zombies de l’autre joueur, sans blesser les siens. Dès qu’un joueur a subi 10 coups, il perd, et le jeu s’arrête. Les joueurs ne peuvent pas se tirer dessus l’un l’autre
Contrôles :
Le jeu est prévu pour être joué au Pad 360.
Joystick gauche pour déplacer le personnage, Joystick droit pour déplacer la caméra (quand on ne lock pas).
À pour tirer, Y pour sauter. B pour locker ou délocker un ennemi
LB/RB pour passer d’une cible a l’autre
Pour ceux qui n’ont pas de manette, j’ai bricolé un contrôle a la souris, mais le jeu n’est pas prévu pour ça a l’origine, et certaines choses ne passent pas trés bien.
ZQSD pour déplacer le personnage, bouger la souris pour déplacer la cameracaméra (quand on ne lock pas).
Clic gauche pour tirer, Espace pour sauter. Clic droit pour locker ou délocker un ennemi
E pour passer d’une cible a l’autre
Pour essayer, un petit clic sur l’image ci-dessous. Pad 360 et webplayer unity conseillés
Ce que j’ai compris du réseau :
Vu que je partais presque de zéro, en terme de réseau, j’ai beaucoup chipoté, une première base est simple : un joueur héberge, son serveur est disponible dans la liste, un autre peu cliquer pour le rejoindre, ils sont connectés ! Sauf qu’en fait, si on n’explique pas a chaque objets/actions, d’agir en réseau, c’est juste 2 joueurs, chacun de leurs cotés, qui ne se voient même pas l’un l’autre, avec aucun objets ou éléments en commun.
Pour mettre les choses en réseau, je dois changer plusieurs choses. Par exemple, lorsque j’instancie un prefab, le faire via une fonction instanciation de la Class de réseau, l’”objet” apparaîtra donc chez les deux joueurs. mais une fois en jeu, il tournera localement sur chaque machine, si je ne lui donne pas d’info pour se synchroniser
Pour ça, j’ai en gros accès à 2 méthodes. Pour une, je dois ajouter un composant (network view) a un prefab, qui va s’assurer a une fréquence donnée, de mettre a jours certains infos, si elles ont été modifiées. Par exemple, si j’y assigne la position du joueur. Tant que le joueur ne bouge pas, rien n’est envoyé, mais si il se déplace (de lui même, ou parce qu’on le pousse, ou peu importe le moyen), il enverra sa nouvelle position au réseau, et le personnage sera ajusté dans le jeu des autres joueurs. Et ce, plusieurs fois par seconde.
Une autre méthode consiste a appeler, “moi-même”, des fonctions en [RPC], qui seront exécutées , sur le réseau, par le personnage, chez tous les joueurs. (ou juste chez certaines, si je définis lesquels)
Vu que les héros ont le même script de contrôle, chaque joueur contrôlait les 2 héros simultanément, j’ai donc du m’assurer, dans la boucle de contrôle, de ne le contrôler que si le personnage appartient au joueur.(networkView.isMine).
Pour la caméra, chaque joueur dois voir son propre personnage, mais je n’ai pas besoin d’avoir 2 caméras dans la scène pour autant, j’ai donc fais en sorte que chaque joueur, au lancement, prenne le contrôle de la caméra, et comme je ne mets jamais a jours la caméra sur le réseau, la caméra suivra toujours son héros, pour chaque joueur.
Les mouvements des joueurs
Les position et rotation des joueurs sont, a ce moment, mis a jours par le réseau (avec le network view, comme dis plus haut), mais seulement plusieurs fois par secondes, donc très saccadées. Pour interpoler ça, j’ai utilisé un script NetworkRigidbody.cs, des tutoriaux unity officiels, qui ne va pas se limiter a changer la place du perso 10 fois par seconde, mais qui va interpoler et extrapoler les positions et rotations.
Donc là, on a les persos qui se déplacent sur le réseau, seulement voilà, ils n’ont aucune animation. En effet, je modifie les animations a jouer au moment ou je contrôle le personnage, en définissant à chaque frame, des valeurs aux paramètres du gestionnaire d’animation (component Animator). Mais vu que le joueur ne contrôle plus que son personnage, toute cette étape est ignorée, Il faut donc, trouver un moyen de transmettre les animations sur le réseau. , j’ai envisagé plusieurs moyens.
Première idée : utiliser des fonctions de RPC, et dés que je donne une valeur a mon animator, je préviens sur le réseau. Mais ça ne me semble pas viable, vu que je mets à jour plusieurs informations à chaque frame, et que je ne veux pas saturer la lignes avec des informations non primordiales.
Deuxième idée : vu que j’ai une interpolation des positions, je peux récupérer la vitesse de mouvement actuel du personnage, et à partir de ça, calculer ses informations d’animations de marche, sauts… Et a coté de ça, utiliser la première idée pour ce qui est des animations moins fréquentes, comme le tir, je ne mettrai a joueur les infos de tir que lorsque je lance vraiment un projectile.
Troisième idée : Ajouter un script au personnage, à part de ceux de contrôles et de mouvements. ce script va en permanence regarder l’état de presque tout les paramètres de l’animato, et s’il voit qu’il y a eu un changement de valeur dans un des paramètres, alors il va l’enregistrer, et envoyer sur le réseau la nouvelle valeur. C’est la méthode que j’ai décidé d’employer, et a priori, ça fonctionne. La seconde méthode doit, par contre, être plus légère en terme de bande passante, mais je préférais avoir un contrôle sur toutes les animes, avec la même logique
Les mouvements des zombies
Pour les zombies, a priori, ça sera plus simple, vu qu’il n’est pas contrôlé par les joueurs, et a une IA simple, j’ai juste a les faire spawn au même endroit, et a le laisser se débrouiller, il va chasser les joueurs, dont les positions sont synchronisées. Sauf qu’en fait, ça ne marche pas.
En effet, si la position du joueur était exactement la même synchro sur le réseau, ça irait, mais là, on extrapole. Donc il suffit d’un petit décalage, pour que le zombi soit décalé très légèrement par un obstacle, pour qu’il prenne un chemin différent. Et qu’au final, après 10 secondes, soit à des endroits très différents pour chaque joueur. (Ce qui n’est pas vraiment grave en fait, je pense, mais pour ce script, je voulais quelque chose de plus juste)
A la place, j’ai donc plutôt utilisé quelque chose de plus proche que ce que j’ai fait pour les persos. Le script de NetworkRigidbody, pour la position dans le monde. Mais par contre, il fallait a ce moment là, que je m’assure que le zombie ne soit pas calculé chez les deux joueurs (auquel cas il se déplacerait 2 fois plus vite, et aurait des soucis de synchro. Donc j’ai fait en sorte de vérifier qui est le “propriétaire” du zombie. (en gros, celui qui l’a fait spawn), et c’est donc chez un seul des deux joueurs que le zombie fera ses vrais calculs, complets. Chez l’autre, j’ai gardé une partie des calculs locaux, mais sans toucher à certaines choses, comme les déplacements. Par contre, je lui laisse calculer en local toute la partie animation, qui n’aura donc pas à transiter sur le réseau.
À y réfléchir maintenant, j’aurais peut-être dû laisser les déplacements complets en local de chaque côté, mais vérifier, occasionnellement, la position, et la synchroniser, si elle est différente (ce qui n’est pas le cas la plupart du temps, a moins de lagguer)
Les tirs
Pour les tirs des joueurs, il était évident que je ne pouvais pas les synchroniser trop souvent, vu qu’on tir tout le temps et qu’il y a donc beaucoup de balles à la fois.
Quand un joueur tir, je crée une balle sur le réseau. Au départ, j’ai laissé tel quel, en me disant que si ça touche, ça touchera le même ennemi chez les deux joueurs. Mais comme pour les calculs de mouvements des zombies, ça m’a vite semblé trop approximatif (et je ne veux pas avoir un zombie blessé chez un joueur, mais en pleine forme chez l’autre..) .
Ce qui m’a paru le plus simple, pour finir, a été de modifier juste le script de la balle, pour qu’au moment du contacte, elle ne fasse les calcules de dégâts, et autre, uniquement chez le propriétaire. Et la balle est détruite ensuite non pas sur le réseau, mais en local. Comme ça il est possible d’avoir une imprécision pour l’autre joueur, mais uniquement visuelle.
Pour ce qui est des dégâts, l’idée générale est donc d’appeler une fonction de réseau quand le zombie subit des dégâts, pour prévenir l’autre joueur. Et ensuite, seul le propriétaire va vérifier l’état de santé. et si il a moins de 0 PV, alors il appelle la fonction pour faire spawn 2 zombies, et détruit le zombie mourant sur le réseau, puis incrémente le compter de fraggs.
J’ai eu beaucoup de petits soucis pour ajouter le réseau dans ce script. Certains problèmes que j’ai compris, et corrigés. Et d’autres que j’ai juste contournés sans les comprendre. Par exemple a l’origine, j’envoyais un point pour compter les frags justes avant d’annoncer la mort du zombie sur le réseau. Mais l’autre joueur semblait ne pas pouvoir lancer la fonction, car il avait l’air de recevoir l’ordre de destruction avant celui de comptage de points. Pour contourner ça, j’ai fait ajouter un point lors de l’appel de la fonction “Ondestroy” interne au moteur, qui est appelé lors de la destruction de l’objet.
Aussi, j’ai l’impression que ce qu’on fait par le réseau est toujours très vague. On n’est jamais certaines des données que les autres vont recevoir toutes les infos, de ce que le lagg peut faire au jeu. Je pense que faudra que je m’achète et lise attentivement un bouquin sur la théorie de développement de jeux en réseau, avant de pouvoir faire quelque chose de bien.
Temps : 10 Heures pour la mise en reseau, mais en repartant du script fait la dernière fois (donc environ 30h en tout)
PS : Ce post est un peu chaotique et confus, mais mon esprit l’est aussi, aujourd’hui, donc c’est normal
http://thibautgiordano.com/