Unity - Tanks

Gestionnaires

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Voici le septième épisode de la formation donnée au cours du Unite 2015 à Boston, qui va vous apprendre à faire un jeu dans lequel deux tanks s'affrontent.
Vous pouvez retrouver les autres épisodes de cette série dans le sommaire dédié.

II. Vidéo


Unity - Tanks ! - Gestionnaires


III. Résumé

Dans cette vidéo vous allez apprendre à créer le chef d'orchestre du jeu (aussi appelé le gestionnaire du jeu). Grâce à lui, les joueurs pourront se battre dans des matchs et le résultat du match et de la partie sera affiché.

III-A. Emplacements d'apparition des tanks

À travers l'éditeur, vous devez rajouter des points de référence qui serviront comme repères pour faire apparaître les tanks.

Créez un GameObject vide et dupliquez-le. Nommez-les « SpawnPoint1 » et « SpawnPoint2 ». Leur position est (-3, 0, 30) et (13, 0, -5) et leur rotation est (0, 180, 0) et (0, 0, 0) respectivement.

Les objets vides sont très difficiles à voir dans la scène. Afin de mieux les repérer, passez en mode déplacement des objets et sélectionner l'objet en question. Ainsi, vous verrez les axes liés à l'objet. Toutefois, il y a mieux. Vous pouvez définir un affichage (uniquement pour l'éditeur) grâce au cube en haut à gauche du composant « Transform » dans l'inspecteur.

III-B. Affichage des résultats

Pour afficher le résultat des matchs, rajoutez un nouveau canevas appelé « MessageCanvas ». Par défaut, le canevas s'applique à l'intégralité de l'écran, tel un calque que l'on posera sur l'écran.

Pour pouvoir travailler sur ce nouveau canevas, vous pouvez passer l'éditeur en mode 2D.

Pour centrer la vue sur un objet, appuyez sur la touche 'F'.

Ajoutez un objet « Text » dans le canevas. Par défaut, celui-ci est centré. Toutefois, il doit s'appliquer sur l'ensemble de l'écran. Afin de corriger cela, changez les ancres à (0.1, 0.1) pour les dimensions minimums et (0.9, 0.9) pour les dimensions maximums. Ensuite, réinitialisez les positions et tailles. Maintenant, vous pouvez définir le texte et la police de caractères (avec celle fournie dans le projet) à travers les propriétés de l'objet. Définissez aussi l'alignement pour centrer le texte (sur les deux axes). Activez la propriété « Best Fit » et donnez-lui une valeur minimale à 10 et maximale à 60. La couleur du texture sera blanche.

Il est possible d'ajouter une ombre au texte. Cela se fait au travers d'un composant à part, de type « Shadow ». Définissez la couleur de l'ombre à (114, 71, 40) et la transparence à 128. La distance de l'effet doit être (-3, -3).

III-C. Gestionnaires

III-C-1. Gestionnaire du jeu

Le gestionnaire permet de faire les liens entre les fonctionnalités que vous avez intégrées précédemment. Son premier rôle sera de créer les tanks et de les enregistrer dans l'objet de la caméra afin que celle-ci puisse les suivre. De plus, il devra compter le nombre de matchs et afficher les résultats. Finalement, entre chaque phase (lors de l'affichage des scores), les contrôles des tanks seront bloqués pour ne pas permettre aux joueurs de les déplacer.

Le gestionnaire n'est qu'un objet vide appelé « GameManager » auquel vous attachez un script.

III-C-1-a. Script GameManager

Le script donc, permet de gérer le nombre de matchs entre les joueurs, le délai d'attente avant et après chaque match, la création des tanks et l'affichage des résultats.

 
Sélectionnez
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    public int m_NumRoundsToWin = 5;            // Le nombre de matchs qu'un joueur doit gagner pour remporter la partie.
    public float m_StartDelay = 3f;             // Le temps entre le début des phases RoundStarting et RoundPlaying.
    public float m_EndDelay = 3f;               // Le temps entre la fin des phases RoundPlaying et RoundEnding.
    public CameraControl m_CameraControl;       // Référence le script CameraControl pour contrôler les différentes phases du jeu.
    public Text m_MessageText;                  // Référence le texte des résultats.
    public GameObject m_TankPrefab;             // Référence le préfabriqué du tank.
    public TankManager[] m_Tanks;               // Une liste de gestionnaire de tanks pour activer ou désactiver plusieurs paramètres des tanks.

    
    private int m_RoundNumber;                  // Numéro du match en cours.
    private WaitForSeconds m_StartWait;         // Utilisé pour avoir un délai au démarrage d'un match.
    private WaitForSeconds m_EndWait;           // Utilisé pour avoir un délai à la fin d'un match.
    private TankManager m_RoundWinner;          // Référence le gagnant du match. Utilisé pour annoncer qui a gagné.
    private TankManager m_GameWinner;           // Référence le gagnant de la partie. Utilisé pour annoncer qui a gagné.


    private void Start()
    {
        // Crée les délais d'attente afin que cela ne soit réalisé qu'une seule fois.
        m_StartWait = new WaitForSeconds (m_StartDelay);
        m_EndWait = new WaitForSeconds (m_EndDelay);

        SpawnAllTanks();
        SetCameraTargets();

        // Une fois que les tanks ont été créés et que la caméra les utilise comme cible, démarre le jeu.
        StartCoroutine (GameLoop ());
    }


    private void SpawnAllTanks()
    {
        // Pour tous les tanks...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            // ... les crée, défini le numéro du joueur et les référence pour les manipuler.
            m_Tanks[i].m_Instance =
                Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;
            m_Tanks[i].m_PlayerNumber = i + 1;
            m_Tanks[i].Setup();
        }
    }


    private void SetCameraTargets()
    {
        // Créer une liste de transformation de la même taille que le nombre de tanks.
        Transform[] targets = new Transform[m_Tanks.Length];

        // Pour chacune de ces transformations...
        for (int i = 0; i < targets.Length; i++)
        {
            // ... la définit à la transformation du tank.
            targets[i] = m_Tanks[i].m_Instance.transform;
        }

        // Ce sont les cibles que la caméra doit suivre.
        m_CameraControl.m_Targets = targets;
    }


    // La fonction est appelée au début et exécutera chaque phase du jeu, l'une après l'autre.
    private IEnumerator GameLoop ()
    {
        // Démarre avec la coroutine 'RoundStarting' mais ne continuera que lorsqu'elle sera finie.
        yield return StartCoroutine (RoundStarting ());

        // Une fois que la coroutine 'RoundStarting' est terminée, exécute  la coroutine 'RoundPlaying' mais ne continuera que lorsqu'elle sera finie.
        yield return StartCoroutine (RoundPlaying());

        // Une fois que l'exécution arrive ici, exécute la coroutine 'RoundEnding', mais ne continuera que lorsqu'elle sera finie.
        yield return StartCoroutine (RoundEnding());

        // Ce code ne s'exécutera que lorsque 'RoundEnding' se termine. À ce moment, vérifie si un joueur gagnant a été trouvé.
        if (m_GameWinner != null)
        {
            // S'il y a un joueur gagnant, redémarre le niveau.
            Application.LoadLevel (Application.loadedLevel);
        }
        else
        {
            // S'il n'y a pas encore de joueur gagnant, la boucle continue.
            // Notez que la coroutine ne fait pas de cession. Cela signifie que l'exécution actuelle de la fonction GameLoop va s'arrêter.
            StartCoroutine (GameLoop ());
        }
    }


    private IEnumerator RoundStarting ()
    {
        // Dès que le match démarre, réinitialise les tanks et s'assure qu'ils ne peuvent se déplacer.
        ResetAllTanks ();
        DisableTankControl ();

        // Force le zoom et la position de la caméra à quelque chose d'approprié pour les tanks réinitialisés.
        m_CameraControl.SetStartPositionAndSize ();

        // Incrémente le nombre de matchs et affiche un texte au joueur pour indiquer quel match est en cours.
        m_RoundNumber++;
        m_MessageText.text = "ROUND " + m_RoundNumber;

        // Attend pour le temps spécifié avant de rendre l'exécution à la boucle de jeu.
        yield return m_StartWait;
    }


    private IEnumerator RoundPlaying ()
    {
        // Dès que le match démarre sa phase de jeu, donne aux joueurs le contrôle des tanks.
        EnableTankControl ();

        // Nettoie le texte affiché à l'écran.
        m_MessageText.text = string.Empty;

        // Tant qu'il reste plusieurs tanks...
        while (!OneTankLeft())
        {
            // ... passe à la prochaine frame.
            yield return null;
        }
    }


    private IEnumerator RoundEnding ()
    {
        // Stoppe le déplacement des tanks.
        DisableTankControl ();

        // Enlève le gagnant du précédent match.
        m_RoundWinner = null;

        // Regarde s'il y a un gagnant maintenant que le match est fini.
        m_RoundWinner = GetRoundWinner ();

        // S'il y a un gagnant, incrémente son score.
        if (m_RoundWinner != null)
            m_RoundWinner.m_Wins++;

        // Maintenant que le score du gagnant a été incrémenté, regarde si quelqu'un à gagné la partie.
        m_GameWinner = GetGameWinner ();

        // Récupère un message dépendant des scores et de l'existence d'un gagnant pour la partie et l'affiche.
        string message = EndMessage ();
        m_MessageText.text = message;

        // Attend pour le temps indiqué avant de rendre l'exécution à la boucle de jeu.
        yield return m_EndWait;
    }


    // Utilisé pour vérifier s'il reste au plus un tank dans le match.
    private bool OneTankLeft()
    {
        // Démarre un compteur des tanks restants.
        int numTanksLeft = 0;

        // Parcours tous les tanks...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            // ... et s'ils sont actifs, incrémente le compteur.
            if (m_Tanks[i].m_Instance.activeSelf)
                numTanksLeft++;
        }

        // S'il y a au plus un tank, retourne true, sinon false.
        return numTanksLeft <= 1;
    }
    
    
    // Cette fonction permet de trouver s'il y a un gagnant dans le match.
    // Cette fonction est appelée avec l'hypothèse qu'il reste tout au plus un tank encore actif.
    private TankManager GetRoundWinner()
    {
        // Parcours tous les tanks...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            // ... et si l'un d'entre eux est actif, c'est le gagnant.
            if (m_Tanks[i].m_Instance.activeSelf)
                return m_Tanks[i];
        }

        // Sinon, aucun tank n'est actif, c'est un match nul.
        return null;
    }


    // Cette fonction permet de trouver s'il y a un gagnant pour la partie.
    private TankManager GetGameWinner()
    {
        // Parcours tous les tanks...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            // ... et si l'un d'entre eux a gagné assez de matchs, le retourne.
            if (m_Tanks[i].m_Wins == m_NumRoundsToWin)
                return m_Tanks[i];
        }

        // Sinon, retourne null.
        return null;
    }


    // Retourne une chaîne de caractères pour afficher à la fin de chaque match.
    private string EndMessage()
    {
        // Par défaut, lorsqu'un match se termine, il n'y a pas de gagnant, donc le message par défaut est pour un match nul.
        string message = "DRAW!";

        // S'il y a un gagnant, on change le texte pour l'indiquer.
        if (m_RoundWinner != null)
            message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";

        // Ajoute quelques sauts de ligne après le message de base.
        message += "\n\n\n\n";

        // Parcours tous les tanks et ajoute leur score au message.
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";
        }

        // S'il y a un gagnant de partie, change complètement le message.
        if (m_GameWinner != null)
            message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!";

        return message;
    }


    // Cette fonction est utilisée pour réinitialiser tous les tanks..
    private void ResetAllTanks()
    {
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            m_Tanks[i].Reset();
        }
    }


    private void EnableTankControl()
    {
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            m_Tanks[i].EnableControl();
        }
    }


    private void DisableTankControl()
    {
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            m_Tanks[i].DisableControl();
        }
    }
}

III-C-2. Coroutines

Les coroutines doivent être déclarées pour retourner une valeur de type IEnumerator. Le mot- clé « yield » permet de mettre en pause la coroutine afin de laisser le reste du programme s'exécuter. Le reste de la coroutine pourra être exécuté après un certain temps, ou lorsqu'une condition est vérifiée.

Dans le code du gestionnaire de jeu, le yield est placé dans une boucle while() et permet de rester dans une certaine phase du jeu tant qu'il reste plus d'un tank. Toutefois, grâce aux coroutines, l'application n'est pas bloquée dans le while().

III-C-3. Gestionnaire de tank

Le gestionnaire de tank va gérer les tirs des tanks, leur mouvement et l'interface utilisateur liée au tank.

III-C-3-a. Script TankManager

 
Sélectionnez
using System;
using UnityEngine;

[Serializable]
public class TankManager
{
    // Cette classe permet la gestion de plusieurs paramètres d'un tank.
    // Elle fonctionne en collaboration avec le gestionnaire de jeu pour contrôler le comportement des tanks
    // et si les joueurs ont ou n'ont pas le contrôle de leur tank
    // au cours des différentes phases du jeu.

    public Color m_PlayerColor;                             // La couleur que prendra le tank.
    public Transform m_SpawnPoint;                          // La position et la direction du tank lors de son apparition.
    [HideInInspector] public int m_PlayerNumber;            // Spécifie pour à quel joueur appartient le tank.
    [HideInInspector] public string m_ColoredPlayerText;    // Une chaîne de caractères pour représenter le joueur avec le nombre coloré correspondant au tank.
    [HideInInspector] public GameObject m_Instance;         // Une référence à l'instance du tank lorsqu'il est créé.
    [HideInInspector] public int m_Wins;                    // Le nombre de victoires pour ce joueur.
    

    private TankMovement m_Movement;                        // Référence du script de mouvement du tank, utilisé pour activer et désactiver le contrôle.
    private TankShooting m_Shooting;                        // Référence le script de tir du tank, utilisé pour activer et désactiver le contrôle.
    private GameObject m_CanvasGameObject;                  // Utilisé pour désactiver l'interface utilisateur en espace monde lors des phases de début et de fin de chaque match.


    public void Setup ()
    {
        // Récupère la référence des composants.
        m_Movement = m_Instance.GetComponent<TankMovement> ();
        m_Shooting = m_Instance.GetComponent<TankShooting> ();
        m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas> ().gameObject;

        // Définit le numéro du joueur pour chaque script.
        m_Movement.m_PlayerNumber = m_PlayerNumber;
        m_Shooting.m_PlayerNumber = m_PlayerNumber;

        // Crée une chaîne de caractères en utilisant la couleur adéquate et qui indique 'PLAYER 1', etc. suivant la couleur du tank et le numéro du joueur.
        m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>";

        // Récupère tous les composants de rendu du tank.
        MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer> ();

        // Parcours les composants...
        for (int i = 0; i < renderers.Length; i++)
        {
            // ... défini leur couleur à la couleur de ce tank.
            renderers[i].material.color = m_PlayerColor;
        }
    }


    // Utilisé durant les phases du jeu  le joueur ne doit pas être capable de contrôler son tank.
    public void DisableControl ()
    {
        m_Movement.enabled = false;
        m_Shooting.enabled = false;

        m_CanvasGameObject.SetActive (false);
    }


    // Utilisé durant les phases du jeu  le joueur doit être capable de contrôler son tank.
    public void EnableControl ()
    {
        m_Movement.enabled = true;
        m_Shooting.enabled = true;

        m_CanvasGameObject.SetActive (true);
    }


    // Utilisé au début de chaque match pour remettre le tank à son état d'origine.
    public void Reset ()
    {
        m_Instance.transform.position = m_SpawnPoint.position;
        m_Instance.transform.rotation = m_SpawnPoint.rotation;

        m_Instance.SetActive (false);
        m_Instance.SetActive (true);
    }
}

III-C-4. Déclaration de la classe

Contrairement à toutes les autres classes, TankManager n'hérite pas de la classe MonoBehavior. En effet, cela n'est pas utile, car il n'y a pas besoin des fonctions Awake(), Start(), Update()… Par contre, les variables publiques ne seront pas affichées dans l'éditeur. Toutefois, afin que l'éditeur affiche ces variables, vous devez ajouter l'attribut [Serializable] avant la déclaration de la classe.

III-D. Finalisation

Pour finir, définissez les variables exposées dans l'inspecteur.

IV. Ressources

Vous pouvez télécharger le diaporama de la présentation.

Vous pouvez télécharger les ressources pour ce projet sur l'Asset Store de Unity.

V. Commenter

Vous pouvez commenter et donner vos avis dans la discussion associée sur le forum.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2016 Unity Technologies. 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.