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.
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▲
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 où 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 où 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.