dimanche 11 novembre 2012

Tutorial: La mise en réseau sous Unity3D - Partie 3

Salut tout le monde !

Si vous êtes arrivés ici par hasard, vous aurez sans doute besoin de commencer par la partie 1, par-ici.

Troisième et avant-dernière partie du tutorial. Aujourd'hui on va apprendre à notre Cuby a tirer et on va transformer ce petit projet en vrai jeu jouable et presque amusant.

Au fait, dans la partie 2 je vous parlais pas mal d'un certain script Deplacement, je voulais en fait parler du script Cuby.js. J'ai corrigé le billet et j'espère que ça ne vous a pas trop embrouillé / dérangé, désolé.

Pew Pew



Ok, on va apprendre à Cuby, notre cube d'intervention spéciale, à tirer.
  • Commençons par créer un nouveau script "Weapon".
  • Ajoutez-lui la fonction Shoot(). 
On aurait pu créer cette fonction dans le script Cuby.js mais je préfère utiliser un script Weapon qu'on assignera à chaque arme différente pour les raisons suivantes:
    • Si on ajoute plusieurs types d'armes, ce seras beaucoup plus simple de configurer chaque arme avec un script comme ça. Par exemple si je crée une arbalète, je pourrai lui dire, dans l'inspector, d'utiliser des carreaux comme projectiles plutôt que des balles.
    • Ça évite d'encombrer le script Cuby.js qui dans le cas d'un gros jeu contiendrait déja pas mal de code lié à ses déplacements.
    • L'instanciation sera plus facilement configurable puisque ce script sera placé sur l'arme, et donc à la position d'où doit partir le projectile.
La première chose à faire va être d'instancier un projectile sur le réseau à la position de notre arme:
  • var objet:GameObject = Network.Instantiate(projectile, transform.position, transform.rotation, 0);
  • Déclarez la variable var projectile:GameObject; tout en haut du script.
Il ne reste plus qu'a donner une vitesse à notre projectile.
  • objet.rigidbody.velocity = transform.TransformDirection (Vector3.forward * 40);
Unity déconseille de toucher directement à la vélocité d'un objet et propose plutôt d'utiliser des AddForce mais puisque je suis un gros noob de la physique et que ce n'est pas un "vrai jeu" on va faire comme ça pour plus de facilité.

On va maintenant pouvoir configurer tout ça dans Unity. 
  • Sélectionnez le GameObject "Gun" sur votre Prefab "Player" et assignez-lui le script "Weapon".
  • Faites glisser le Prefab "Bullet" sur le champs Projectile dans l'inspector.


On va ensuite ajouter un RigidBody sur notre Bullet sans quoi elle ne subira pas la gravité et ne pourra pas être éjectée de l'arme.
  • Sélectionnez le Prefab "Bullet", cliquez sur Component/Physics/RigidBody et laissez tout par défaut.
  • Ajoutez-lui aussi une NetworkView. Et assignez le RigidBody plutôt que le Transform de l'objet à cette NetworkView Sinon les balles se déplaceront en laggant et ça fera tout foirer^^

En principe tout est prêt mais on doit encore appeler la fonction Shoot(). Retournez dans le script Weapon et ajoutez ceci dans la fonction Update():

if ( Input.GetKeyDown(KeyCode.Mouse0))
Shoot();
    N'oubliez pas la fonction Awake avec le code suivant pour éviter que votre adversaire ne tire aussi quand vous cliquez:

    if ( !networkView.isMine)
        Destroy(this);

    Votre code devrait maintenant ressembler à ça.


    Si vous lancez le jeu vous constaterez qu'il est désormais jouable ! En effet vous pouvez vous amusez à pousser l'adversaire en dehors de la map. C'est pas Crysis 3 mais c'est déjà plus amusant à jouer qu'un JRPG [/troll]

    Quelques idées d'amélioration:
    • Créer plusieurs arènes avec des obstacles, etc
    • Mettre un compteur de points qui augmente à chaque fois qu'un joueur tombe de la map
    • Faire respawn le joueur lorsqu'il tombe.
    • Ajouter une visée à la souris et des déplacements latéraux
    Bien, revenons-en à notre réseau, il nous reste encore quelques trucs à voir.

     Accéder aux objets en réseau



    Je ne sais pas vous, mais souvent, lorsque je code et que je désire accéder au joueur, je tape un truc du genre "GameObject.Find("Player")".

    Oubliez cette fonction ! En réseau vous ne l'utiliserez quasiment plus jamais. Pourquoi?

    Vous avez plusieurs joueurs sur votre map et ils ont en principe tous le même nom (l'objet Player en tout cas). Cette fonction va vous-en renvoyer un au hasard ou en tout cas de manière difficilement contrôlable.

    Je vais vous montrer une alternative et on en profitera pour régler un petit bug de notre jeu.

    Vous l'avez peut-être remarqué mais Cuby est vraisemblablement équipé d'un Jetpack puisqu'il peut voler lorsqu'on appuie sur la touche de saut alors que Cuby n'est pas au sol. C'est classe mais ça ne m'arrange pas.

    On ne le fera pas dans ce tuto mais disons que je veux ajouter un objet Jetpack ramassable sur la map. Je vais devoir empêcher Cuby de voler lorsqu'il n'a pas ce Jetpack. Pour ça on va devoir détecter si Cuby est au sol ou non.

    On pourrait simplement détecter si l'objet "Player" est en collision avec le sol mais pour ce tutorial et parce que bien souvent votre joueur ne tiendra pas en un seul bloc, on va plutôt utiliser un sous-objet "GroundAnalyst" qui se chargera de regarder si le joueur est au sol ou non.

    Je vous conseille d'ailleurs de toujours créer un objet vide "Player" qui aura ensuite un objet Modele3D attaché comme sous-objet, sinon tout changement de modèle sera difficilement faisable sans devoir reconfigurer tout le personnage.
    • Dans Unity, créez un nouvel Empty GameObject que vous nommerez "GroundAnalyst". 
    • Placez Cuby dans la scène et assignez-lui ce nouvel objet. 
    • Ajoutez une CollisionBox au GroundAnalyst et cochez la case "Is Trigger" afin qu'il passe à travers les objets.
    • Redimensionnez la box (shift + clic sur les carrés des faces) pour qu'elle soit un peu en-dessous de Cuby. De cette façon:


    • Créez un nouveau script "GroundAnalyst" que vous assignez au GroundAnalyst.
    • N'oubliez pas d'appliquer vos modifications au Prefab en appuyant sur le bouton "Apply" en haut a droite de l'inspector (en rouge sur l'image) avant de le supprimer de la scène.

    J'en ai profité pour rendre Cuby rebondissant (en bleu sur l'image), ce qui ajoutera un peu de challenge au jeu. Cette étape n'est pas du tout obligatoire et n'a rien à voir avec le réseau mais si vous voulez faire pareil voici les spécifications de mon Physic Material:


    Attaquons nous maintenant à notre GroundAnalyst. On va faire un petit changement dans le script Cuby.js pour commencer:
    • Déclarez la variable "var canJump : boolean;"
    • Vous pouvez aussi supprimer la variable "life" si vous l'avez puisque le jeu ne consiste finalement plus à tuer l'ennemi en tirant dessus (je trouve que le pousser en dehors du terrain est bien plus fun et original).
    • Changez ensuite le saut dans la fonction "Deplacement()":

    if ( Input.GetKeyDown(KeyCode.Space) && canJump)
    {
    rigidbody.AddForce(Vector3.up * 350);
    canJump = false;
    }

    au lieu de:

    if ( Input.GetKeyDown(KeyCode.Space))
    rigidbody.AddForce(Vector3.up * 350);



    Passons maintenant à notre script "GroundAnalyst":

    Il va simplement regarder s'il est en contact avec le sol, ce qui signifierais que le joueur peut sauter:

    function OnTriggerEnter(col:Collider)
    {
    if(col.gameObject.name == "Terrain") //si l'objet qui traverse le GroundAnalyst s'appelle "Terrain"
    {
    scriptCuby.canJump = true;
    }
    }
    • Déclarez la variable "private var scriptCuby:Cuby;" au-début du script.
    Il faut maintenant assigner un script à cette variable. Un réflexe de débutant pourrait-être de taper "scriptCuby = GameObject.Find("Player").GetComponent("Cuby");" dans la fonction Start().

    Mais rappelez-vous ce que j'ai dit plus haut, en faisant ça Unity va vous renvoyer le script Cuby d'un joueur. Peut-être le vôtre mais peut-être celui d'un autre... Cette méthode est donc à totalement bannir quand on code en réseau et qu'on tente d’accéder à un objet instancié.

    On va plutôt utiliser une ligne toute simple que vous connaissez peut-être déjà. Personnellement je l'ai apprise en cherchant une solution à mes problèmes réseaux lorsque je débutais:

    La solution est donc de taper dans la fonction OnTriggerEnter():
    • scriptCuby = transform.root.GetComponent("Cuby");
    Votre fonction doit ressembler à ça:

    function OnTriggerEnter(col:Collider)
    {
    if(col.gameObject.name == "Terrain")
    {
    scriptCuby = transform.root.GetComponent("Cuby");
    scriptCuby.canJump = true;
    }
    }

    Si vous testez le jeu ça devrais normalement fonctionner. Si vous avez rendu votre Cuby rebondissant plus haut, vous devriez pouvoir vous amuser à contrôler la hauteur de votre saut en appuyant sur espace au bon moment.

    Je reviens donc sur ce petit mot magique: "transform.root". En fait il vous renvoie tout simplement le Transform le plus haut de la hiérarchie de l'objet. Ici, notre objet "Player" donc puisqu'il est le parent le plus haut du GroundAnalyst. On aurait aussi pu utiliser "transform.parent" qui renvoie le parent direct de l'objet.

    Vous utiliserez souvent cette manière de procéder. Je pense d'ailleurs qu'elle est moins gourmande en ressources que GameObject.Find donc pensez-y aussi pour vos jeux solo.

    Vous remarquerez peut-être que notre jeu bug un peu parfois ce qui fait qu'on peut se retrouver au sol sans pouvoir sauter. Voici mon code qui contient un petit "workaround" pour contourner ce problème (un peu plus gourmand en mémoire).



    Il ne nous reste qu'une seule chose à voir mais ce sera pour la prochaine partie du tutorial.


    Conclusion



    On possède maintenant un vrai jeu jouable et qui fonctionne en réseau. Il vous manque encore un petit concept réseau à voir pour vraiment pouvoir vous lancer seuls mais cette lacune sera réglée après la prochaine et dernière partie du tutorial. N'hésitez pas à apportez des modifications au jeu pour le rendre plus intéressant.

    Si votre code ne fonctionne pas je vous invite à refaire cette partie en prenant éventuellement mon projet Unity que j'ai donné à la fin de la seconde partie comme base de départ. Si vous avez des questions ou des suggestions, les commentaires sont fait pour ça donc n'hésitez pas.

    Voila le projet Unity du jeu tel qu'il doit être à la fin de cette partie.

    A télécharger ici

    Avant de passer à la partie 4 du tuto, je vous conseille de faire un tour sur cet annexe où on réglera un problème de lags.