Créer un jeu vidéo peut sembler complexe et intimidant.
Sans les bonnes bases, vous risquez de perdre du temps et de vous décourager rapidement.
Découvrez comment développer un jeu 3D en utilisant Unity et C#, avec des étapes claires pour chaque phase du projet.
Créez votre premier jeu 3D en C# avec Unity, étape par étape.
Table des matières
Créer un jeu 3D Unity avec C#
Projet Unity: Space Labyrinth FPS
Après de longues parties théoriques, nous allons enfin entrer de plein pied dans la création de notre jeu Space Labyrinth . A titre de rappel, nous allons créer un jeu de Labyrinthe 3D à la première personne ( FPS ). Le principe est simple. Le joueur doit parcourir le labyrinthe en évitant les dangers et trouver l’issue.
Les éléments clés de notre jeu s’articulent comme suit :
- Le labyrinthe :nous construirons un décor 3D futuriste où évoluera le personnage.
- Les ennemis (Slime, SpikeBall) :ils nous permettront de rajouter une petite dose de
stress au joueur puisseque les toucher revient à perdre la partie.
- La clé (Card) :elle encourage l’exploration et permet d’ouvrir la porte de sortie.
- La porte de sortie (Exit Door) :elle donne accès à de nouveaux niveaux de jeu que vous
serez libre de personnaliser afin de les rendre plus exigeant.
Bien sûr, nous n’oublions pas de mentionner qu’on intègrera des effets sonores ainsi que la boucle standard d’un jeu de ce type savoir : play , game over , continue .
Construire un labyrinthe en 3D
Dans cette section, nous allons créer notre labyrinthe. Nous n’aurons besoin pour cela que de manipuler un ensemble de formes primitives (plane, cube), des textures spécifiques et faire appel à notre créativité.
Commencez par importer dans votre projet le package « SpaceLabyrinthRessources ». Il contient toutes les ressources importantes liées au projet : modèle 3D, audio, textures et autres… En vous référençant aux textures adéquates, créez les matériaux suivants : FloorMat , WallMat , RoofMat .
Le labyrinthe est un assemblage de plusieurs composants à disposer selon votre propre inspiration :
- Floor(plancher) :utilisez une forme primitive « plane » à redimensionner à votre
gise. Ensuite, associez-lui la texture FloorMat.
- Wall(mûr) :il s’agit de petits cubes que vous allez ajuster pour forcer le joueur à
emprunter un itinéraire prédéfini. Rajoutez la texture appropriée.
- Longwall :Créez des mûrs d’une grande longueur pour refermer sur les quatre côtés le labyrinthe. Veuillez à ce que ces murs soient idéalement des associations plusieurs petits cubes. Cela sera pratique pour créer ultérieurement des ouvertures.
- Roof(le plafond) :utilisez une forme primitive de type « plane » comme toit pour le labyrinthe. Rajoutez-y la texture prévue à cet effet. La texture ne s’appliquera que sur la face visible de la surface plane.
Notre scène est prête à accueillir un joueur. Sans plus attendre, c’est exactement ce à quoi nous allons nous atteler dans l’article à suivre.
Développer le FPS Player avec Unity
Nous aurons dans cet article, l’occasion d’appliquer nos connaissances en programmation pour effectuer le déplacement du joueur. Le mouvement de ce dernier devra se synchroniser aux entrés de l’utilisateur.
Commençons par créer une capsule qui représente le joueur sur la scène et à y associer comme élément enfant une caméra. La caméra pour ainsi suivre continuellement le déplacement du joueur. Après avoir défini le point d’entrée du joueur sur la scène assurez-vous que le joueur ainsi que sa caméra soient alignés sur l’axe Z en rapport avec le sens du déplacement. Pour cela, ajustez la valeur de la rotation en veillant à ce que le mode Local soit activé sur la scène.
Renommez la capsule « PlayerFPS » et rajoutez-y un composant Rigidbody pouvant activer la physique. Prenez le soin d’utiliser l’option « freeze » pour désactiver la rotation sur les axes X, Y et Z. Vous éviterez ainsi quelques désagréments. Nous allons à présent y associer un script « PlayerController ». Nous allons y écrire une logique qui vous est déjà familière :
usingUnityEngine;
publicclassPlayerController : MonoBehaviour {
[SerializeField] privatefloatspeedWalk = 8f; // Vitesse de marche
[SerializeField] privatefloatspeedRun = 16f; // Vitesse de course
[SerializeField] privateintspeedRotation = 100; // Vitesse de rotation
// Variable privée pour stocker la vitesse courante du joueur
privatefloatcurSpeed;
voidUpdate() {
// Si la touche LeftControl est enfoncée, la vitesse courante (curSpeed)
// sera la vitesse de course (speedRun) Sinon, elle sera égale à la vitesse
// de marche (speedWalk)
curSpeed = Input.GetKey(KeyCode.LeftControl) ? speedRun : speedWalk;
// Récupère les inputs de l'axe horizontal et vertical (mouvement
// gauche/droite et avant/arrière)
floath = Input.GetAxis("Horizontal");
floatv = Input.GetAxis("Vertical");
// Déplace l'objet en avant ou en arrière selon l'input vertical (v)
transform.Translate(Vector3.forward * v * curSpeed * Time.deltaTime);
// Fait tourner l'objet autour de l'axe Y (vertical) selon l'input
// horizontal (h)
transform.Rotate(Vector3.up * h * speedRotation * Time.deltaTime);
}
}
Lancez le jeu dans Unity. Vous serez à présent en mesure de vous déplacer aisément dans le labyrinthe et d’effectuer des rotations.
😅 Un instant ! On dirait qu’il y a un problème avec la caméra.
Vous avez probablement observé un effet indésirable lorsque vous vous rapprocher des murs. Cela est lié à la physique. Rappelez-vous que nous avons ajouté au joueur le composant « Rigidbody ». Nous allons ajuster notre script en conséquence :
void FixedUpdate() {
curSpeed = Input.GetKey(KeyCode.LeftControl) ? speedRun : speedWalk;
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
transform.Translate(Vector3.forward * v * curSpeed * Time.fixedDeltaTime);
transform.Rotate(Vector3.up * h * speedRotation * Time.fixedDeltaTime);
}
Nous avons remplacé la méthode Update par FixedUpdate ainsi que deltaTime par fixedDeltaTime pour que le déplacement prenne en compte la physique .
Relancez à présent le jeu, vous remarquerez que le mouvement est beaucoup plus fluide quelle que soit la position du joueur.
Si le protagoniste principal est déjà en place, il ne nous reste plus qu’à inviter les antagonistes. Dans les articles à suivre, nous créerons des ennemis.
Créer l'ennemi Slime dans Unity
Nous allons dans cet article créer notre ennemi de type Slime et lui rajouter pour le moment une fonctionnalité très basique. Il sera en mesure de suivre du regard le joueur quelque soit la position de ce dernier.
Placez sur la scène du jeu le modèle 3D du Slime et éditez à votre convenance ses proportions. Ajoutez-y les composants Rigidbody et sphère collider.
Comme précédemment, désactivez la rotation sur les axes X Y et Z pour le rigidbody . Prenez soin d’ajuster le collider à la forme du modèle. Nous allons à présent créer un tag « Enemy » puis l’ajouter au Slime.
Si cela n’est pas encore fait, veuillez rajouter le tag « Player » à l’objet « PlayerFPS ». Notre prochain script se nommera « LookAt ». En voici le contenu :
usingUnityEngine;
publicclassLookAt : MonoBehaviour {
privateTransform target;
privatevoidAwake() {
// Recherche un objet avec le tag "Player"
// et récupère son composant Transform pour accéder à sa position.
target =
GameObject.FindGameObjectWithTag("Player").GetComponent();
}
voidUpdate() {
// Oriente l'objet courant vers la position du joueur
transform.LookAt(target);
}
}
Vérifiez bien si le script est associé au prefab du Slime. Ensuite, lancez le jeu et déplacez-vous près du Slime.
Le comportement de l’ennemi est à présent conforme à nos attentes. Mais il reste bien évidemment quelques améliorations à apporter. Rendez-vous à l’article prochain pour plus de détails.
Déplacement ennemi Slime Unity
Dans cet article, nous allons rendre notre ennemi Slime plus dangereux. Pour cela, nous lui donnerons la possibilité de se déplacer d’un point à un autre.
Tout d’abord, nous aurons besoin d’ajouter à l’objet slime, un « empty object », que nous renommerons « DestinationPoint ». Veillez bien à ce que DestinationPoint soit d’un point de vue hiérarchique un enfant de l’objet slime. Il définira la destination que le Slime devra atteindre pour ensuite revenir à sa position initiale. Cette logique sera gérée par un script que nous nommerons « ObjectDeplacement ». Le script en question devra être en mesure de :
- Accéder et stocker la position initiale et finale (destination point) du slime,
- Indiquer lorsque le slime atteint l’une ou l’autre des dites positions,
- Gérer le déplacement d’un point à un autre.
😎 Avec vos récents acquis vous pouvez déjà relever ce challenge.
Prenez le temps d’essayer d’écrire la logique par vous-même. Ensuite, vous pouvez consulter la réponse à suivre :
usingUnityEngine;
publicclassObjectDeplacement : MonoBehaviour {
// [Range] permet de définir une plage de valeurs ajustables dans l'inspecteur
// Unity. Ici, le speed varie entre 0 et 10.
[Range(0f, 10f)][SerializeField] privatefloatspeed =
2f; // La vitesse de déplacement de l'objet.
// Deux variables pour stocker les positions de départ et de destination.
privateVector3 destination, initialPosition;
// Un booléen pour gérer l'état de retour de l'objet vers sa position
// initiale.
privateboolreturnPosition = false;
voidStart() {
// On stocke la position initiale de l'objet
// (celle qu'il a au moment où le jeu démarre).
initialPosition = this.transform.position;
// On cherche un point de destination nommé "DestinationPoint"
// comme enfant de l'objet actuel et on récupère sa position.
destination =
transform.Find("DestinationPoint").GetComponent().position;
}
voidUpdate() {
// Si la position actuelle de l'objet est égale à la destination
if (transform.position == destination)
returnPosition = true;
// Si la position actuelle de l'objet est égale à sa position initiale
if (transform.position == initialPosition)
returnPosition = false;
// l'objet (slime) se déplace vers la position initiale ou
// vers la destination en fonction de "returnPosition".
transform.position =
returnPosition
? Vector3.MoveTowards(transform.position, initialPosition,
speed * Time.deltaTime)
: Vector3.MoveTowards(transform.position, destination,
speed * Time.deltaTime);
}
}
Dans notre approche, nous utilisons la méthode Vector3.MoveTowards pour déplacer un objet entre sa position initiale et une position de destination . L’objet oscille entre ces deux positions en fonction de sa position actuelle.
Vous pouvez avoir quelques problèmes après avoir associé le script à l’objet puis lancé le jeu. Cela sera probablement lié au rigidbody qui, du fait de la physique, fait osciller la position de l’objet sur l’axe y. Prenez soin d’établir une contrainte sur l’axe y comme suit :
Voilà une bonne chose de faite. Il est à présent temps pour nous d’étendre notre inventaire d’ennemi en rajoutant un nouveau.
Créer ennemi SpikeBall Unity
Le répertoire des ennemis va s’agrandir avec le SpikeBall . L’implémentation sera assez facile compte tenu du travail que nous avions déjà fait en amont avec le précédent ennemi.
Dans les ressources, vous trouverez le modèle 3D de l’ennemi SpikeBall. Ajoutez-le à la scène et comme avec le précédent modèle :
- Ajoutez et ajustez le rigidbody avec les configurations ad-hoc
- Ajoutez un nouvel objet « DestinationPoint » de telle sorte qu’il soit hiérarchiquement
parlant un enfant de l’objet SpikeBall
- Associez le script « ObjectDeplacement »
Une particularité liée à cet ennemi est que nous voulons qu’il se déplace légèrement en apesanteur. Pour cela, nous augmentons légèrement la valeur de sa position sur l’axe Y. Ensuite nous désactivons la physique en attribuant à la propriété « IsKinematic » une valeur « false » .
Lancez Unity et appréciez le résultat.
Nous pouvons cependant donner à cet ennemi un cachet un peu plus spécial. Par exemple, le faire osciller selon un angle de rotation définie. Considérons le script suivant :
usingUnityEngine;
publicclassObjectRotation : MonoBehaviour {
// Vector3.one signifie que la rotation initiale
// est 1 degré par frame autour des axes X, Y et Z.
[SerializeField] privateVector3 rotation = Vector3.one;
voidUpdate() {
// La méthode Rotate applique une rotation selon le vecteur "rotation".
// Space.World spécifie que la rotation se fait dans l'espace globale,
// et non par rapport à l'orientation locale de l'objet.
transform.Rotate(rotation, Space.World);
}
}
Vous pouvez à présent Lancer Unity pour apprécier le résultat. Dans la suite de notre apprentissage nous allons nous intéresser à d’autre aspect de notre projet.
Créer la porte de sortie Unity
Pour permettre au joueur de s’échapper nous allons créer une porte de sortie. Elle fera passer le joueur au niveau suivant.
Tout d’abord, récupérons dans les ressources le modèle « Small Gate ». Placez-le sur la scène à l’endroit que vous avez identifié comme sortie. Place maintenant à l’édition. Une fois que vous aurez le bon emplacement et les bonnes proportions il faudra penser à rajouter un jeu de lumière.
Un point light placer derrière la porte et quelques réglages devraient faire l’affaire. Rendez-vous à l’article suivant pour rajouter quelques subtilités au niveau de la porte de sortie.
Texture de fond de porte Unity
Il vous arrivera très souvent au cours du développement de votre projet de jeu de faire face à des problèmes que vous devriez résoudre pour avancer. Dans notre cas, ce sera l’occasion de manipuler via script une texture pour simuler l’arrière-plan de notre porte.
Dans la logique de notre jeu, le joueur devra accéder à une carte pour déverrouiller la porte. Une fois que la porte est ouverte (translation de l’objet Door ), la seule chose qu’on aperçoit c’est le mur du labyrinthe. Bien sûr, une approche serait d’ajuster les proportions des murs environnants de telle sorte à créer de l’espace pour la sortie.
Le problème avec cette approche est que non seulement cela pourrait altérer la texture des murs mais, en réalité, le joueur n’a pas besoin de sortir en dehors du labyrinthe. La transition sera prise en charge dynamiquement.
Nous allons plutôt créer puis redimensionner un cube pour l’ajuster avec l’embrasure de la porte. Renommez le cube « Background ». Ensuite, rajoutez au cube la texture « space » disponible dans les ressources. Cela devrait déjà créer l’illusion d’une ouverture. Enfin, il ne nous reste qu’à accéder à cette texture via un script et la faire bouger dynamiquement pour que l’illusion soit complète.
Prenez au préalable le temps de vous challenger à intégrer ce comportement. La documentation sur les composants Render ou MeshRender vous sera – entre autres – assez utile.
Considérons le script qui suit :
usingUnityEngine;
publicclassScrollTexture : MonoBehaviour {
// Permet d'ajuster la vitesse de défilement de la texture directement dans
// Unity.
[SerializeField][Range(0, 0.2f)] privatefloatscrollSpeed = 0f;
// "rend" est une variable de type Renderer,
// utilisée pour accéder au matériau (texture) de l'objet.
privateRenderer rend;
// "offset" est la valeur de décalage de la texture sur l'axe Y.
privatefloatoffset = 0f;
voidAwake() {
// On obtient ici le Renderer de l'objet, qui permet de manipuler le
// matériau (texture).
rend = GetComponent();
}
voidUpdate() {
// "offset" est incrémenté par le produit de Time.deltaTime et scrollSpeed.
offset += Time.deltaTime * scrollSpeed;
// On applique le nouvel offset à la texture.
// Ici, "_MainTex" fait référence à la texture principale de l'objet.
// Le décalage se fait sur l'axe Y (vertical)
rend.material.SetTextureOffset("_MainTex", newVector2(0, offset));
}
Nous avons choisi d’effectuer le décalage sur l’axe Y mais vous pouvez changer d’axe si cela ne vous convient pas.
Associez le script « ScrollTexture » à l’objet « background », définissez la vitesse qui vous convient puis lancez le jeu.
Puisque nous en sommes actuellement à tout ce qui se rapporte à la porte de sortie, prenons le temps de modéliser la carte d’ouverture. Rendez-vous à l’article suivant.
Modéliser carte d'ouverture Unity
Dans cet article nous allons utiliser des formes primitives pour modéliser la carte d’ouverture de la porte puis nous y rajouterons quelques effets. Pour accomplir cette tâche nous n’aurons pas besoin d’un logiciel de graphisme 3D . On restera dans Unity
Commençons par créer un prefab que nous nommerons « UnLocker ». Il sera essentiellement composé d’un ensemble de cubes redimensionnés, imbriquées assortis d’un texte.
En utilisant des formes primitives, vous avez juste à suivre les étapes suivantes :
- Créez un cadre qui servira de support principal pour la carte. Affectez-lui un matériau
de couleur noir .
- Incrustez un nouveau cadre de couleur verte qui occupera les 3/4 du précédent carte.
Vous lui associerez le texte suivant : « UNLOCK DOOR ». Ajustez la mise en forme (taille, couleur, position) pour qu’il corresponde le plus possible à la référence ci-dessus.
- À titre décoratif, placez sur la surface inférieure quatre petits cubes vert .
- Créez ensuite une source lumineuse ( point light ), de préférence de couleur verte.
Continuez les ajustements jusqu’à avoir un rendu qui vous convienne.
- Regroupez le modèle obtenu dans un objet que vous renommerez :Face A. Par duplication, créez une Face B identique à la précédente et permettant de percevoir la carte sous tous les angles de vue.
Pensez à rajouter au prefab un boxCollider suffisamment large. Cela servira ultérieurement pour détecter le joueur et activer un comportement spécifique. À ce niveau, il ne nous reste plus qu’à rajouter au prefab le script « ObjectRotation ». Via l’inspecteur, vous pouvez par exemple initialiser la rotation sur l’axe Y à la valeur 3 . Placez la carte dans un recoin du labyrinthe et lancez le jeu.
Les différents composants de notre jeu prennent peu à peu vie. Dans le prochain article nous nous attarderons sur l’animation de la porte d’ouverture.
Animer l'ouverture de porte Unity
Collecte carte et ouverture porte
Nous nous sommes jusque-là habitués à réaliser diverses actions et fonctionnalités en manipulant les Api d’Unity via un script. Pour cette partie, nous allons faire plus simple : créer une animation à partir de l’interface d’unity.
Pour commencer, nous allons sélectionner l’objet « Door » qui se trouve dans le prefab « ExitDoor ». Ouvrons ensuite la fenêtre Animation : Window > Animation et appuyez sur le bouton Create . Cela vous donnera la possibilité de renommer l’animation à créer. Dans notre cas nous l’appellerons « OpenDoor ».
La partie de l’objet « Door » que nous voulons animer, c’est la propriété « position » du composant « Transform » qui lui est associé. Pour cela, la commande est simple : Add Property > Transform > Position (bouton + ).
À partir de cet instant, activez le bouton d’enregistrement près de l’option Preview et placez le curseur à la frame 60. Vous pouvez également renseigner la section ad-hoc avec la valeur 60 . Il ne vous reste plus qu’à déplacement l’objet « Door » pour indiquer sa position finale.
Désactivez l’enregistrement et lancez le jeu. Vous remarquerez que notre nouvelle animation fonctionne bien. Le seul problème est qu’il tourne en boucle.
Pour régler cela, il nous suffit de sélectionner l’animation « OpenDoor » précédemment créée puis désactiver l’option « Loop Time » via l’Inspector.
Pour bien visualiser le résultat, nous allons ajouter une Caméra dans la hiérarchie de « ExitDoor » tout en initialisant sa propriété Depth à 1 pour lui accorder une prioriété sur la caméra principale. Prenez le temps de relancer le jeu. Et voilà, tout se passe comme prévu.
Bien sûr nous ne voulons pas en réalité que dès le lancement du jeu cette animation se déclenche. Pour cela, intéresserons-nous au composant Animator de l’objet « Door ». Il s’est automatiquement ajouté lors de nos précédentes manipulations car il assure la gestion des animations.
Ainsi donc, si nous le désactivons, de même que la caméra tout fonctionne comme auparavant. Lorsque le joueur trouvera une carte nous pourrions les réactiver via un script. C’est justement ce à quoi nous allons nous attellerons dans l’article suivant.
Gérer collisions avec Unity C#
C’est à nouveau le moment d’écrire quelques lignes de code. Nous allons créer un script « Door » qui assurera l’activation de comportements spécifiques liées à la cinématique et coordonner le tout à partir du script « PlayerController »
Notre script « Door » sera associé au prefab « ExitDoor » et assurera, pour le moment, les fonctions suivantes :
- Activer l’animation de la porte
- Activer la caméra
- Activer le script « ScrollTexture » qui, pour des raisons d’optimisation devrait être
désactivé par défaut.
Essayer par vous-même d’implémenter ces différentes fonctionnalités. Vous pouvez en consulter en guise de référence la solution suivante :
publicclassDoor : MonoBehaviour {
// définir un délai d'attente (en secondes) modifiable dans l'inspecteur
// Unity.
[SerializeField] privatefloatsec = 2f;
// Déclaration de variables pouvant contenir les composants :
// Animator, un ScrollTexture, et caméra.
privateAnimator anim;
privateScrollTexture scrollTexture;
privateCamera cam;
voidAwake() {
// On récupère ici le composant Animator se trouvant
// parmi les enfants du GameObject auquel ce script est attaché.
anim = GetComponentInChildren();
// Pareil pour le script ScrollTexture
scrollTexture = GetComponentInChildren();
// Pareil pour le composant caméra
cam = GetComponentInChildren();
}
}
Pour le moment, nous accédons juste aux différents composants qui nous permettraient d’activer les trois fonctionnalités citées plus haut. Il s’agit de l’animation de la porte , le défilement de sa texture et enfin l’activation de la caméra .
L’étape suivante consiste à vraiment activer ces fonctionnalités en utilisant une fonction publique « OpenDoor » :
public
void OpenDoor() {
// Active l'animation de la porte en réactivant l'Animator.
anim.enabled = true;
// Active l'effet de défilement de texture (scroll texture).
scrollTexture.enabled = true;
// Démarre la coroutine PlayDoorCam() qui jouera des actions sur la caméra.
StartCoroutine(PlayDoorCam());
}
IEnumerator PlayDoorCam() {
// Active la caméra pour afficher la scène depuis un angle spécifique.
cam.enabled = true;
// Attend un certain nombre de secondes avant de poursuivre
// Ce délai est défini par la variable sec.
yield return new WaitForSeconds(sec);
// Désactive la caméra après l'attente.
cam.enabled = false;
}
Nous faisons également appel au Coroutine « PlayDoorCam » en raison de la nature différée de l’action.
Pour l’étape finale, nous aurons besoin de trouver une façon simple d’identifier les objets «Door » et « Unlocker ». Dans notre cas, nous allons simplement créer de nouveaux tags : « Door », « Unlocker » et les associés aux objets du même nom. Le reste se fera au niveau du script « PlayerController » :
publicclassPlayerController : MonoBehaviour {
[SerializeField] privatefloatspeedWalk = 8f;
[SerializeField] privatefloatspeedRun = 16f;
[SerializeField] privateintspeedRotation = 100;
privatefloatcurSpeed;
// Référence au script Door qui contrôle l'ouverture de la porte.
privateDoor door;
privatevoidAwake() {
// Recherche l'objet avec le tag "Door" et obtient le script Door de cet
// objet.
door = GameObject.FindGameObjectWithTag("Door").GetComponent();
}
/***/
// Méthode appelée automatiquement lorsqu'une collision avec un autre objet
// est détectée.
privatevoidOnCollisionEnter(Collision collision) {
// Vérifie si l'objet avec lequel il y a collision a le tag "Unlocker".
if (collision.gameObject.tag == "Unlocker") {
// Détruit l'objet avec lequel le joueur est entré en collision (l'objet
// Unlocker).
Destroy(collision.gameObject);
// Appelle la méthode OpenDoor du script Door pour ouvrir la porte.
door.OpenDoor();
}
}
}
Nous faisons appel à « OnCollisionEnter » pour détecter la collision avec la carte d’ouverture. Plus tard, cette même méthode nous sera de nouveau utile.
Vous pouvez maintenant lancer votre jeu. En vous approchant de la carte d’ouverture la cimématique ad-hoc se déclenchera. Dans le cas contraire, veuillez revérifier si vous n’avez manqué aucune des étapes précédentes. Vous pouvez également consulter le corrigé type disponible dans les ressources du cours. Place maintenant à la gestion des collisions avec le personnage.
Passer au niveau suivant Unity
Le propre de tout jeu en général est d’avoir des conditions de réussites et des conditions d’échecs. Dans notre cas, toucher un ennemi reviens à perdre la partie. Nous allons dans ce chapitre implémenter cette logique.
La première étape consiste à créer une interface utilisateur pour annoncer le Game Over. Nous avons besoin pour cela d’une image : ( Hierachy Window ) clic droit > UI > Image . L’image viendra par défaut avec un Canvas. Vous pouvez le renommer « CanvasGameOver » et initialiser la propriété UI Scale Mode ( Canvas Scaler ) à Scale With Screen Size . C’est utile pour maintenir le ratio de votre UI indépendamment de la taille de l’écran.
Revenons à présent à notre image pour y associer via la propriété Source Image le sprite GameOver accessible dans le dossier Texture. Il ne vous reste plus qu’à éditer les proportions pour un rendu convenable. Bien sûr, nous ne voulons pas que l’UI Game Over s’affiche dès le début du jeu. Nous allons dès lors le désactiver pour le moment.
Pour les étapes à suivre, vous devez vous assurer que tous les ennemis de la scène ont le tag « Enemy ». Il ne nous restera plus qu’à faire appel à OnCollisionEnter comme suit :
publicclassPlayerController : MonoBehaviour {
/* *** */
// Référence à l'objet "CanvasGameOver" dans la scène
[SerializeField] privateGameObject CanvasGameOver;
/* *** */
// Cette méthode est déclenchée lorsque le joueur entre en collision avec un
// autre objet.
privatevoidOnCollisionEnter(Collision collision) {
/* *** */
// Vérifie si l'objet avec lequel le joueur entre en collision a le tag
// "Enemy".
if (collision.gameObject.tag == "Enemy") {
// Si c'est le cas, active l'interface de Game Over (affiche le
// CanvasGameOver).
CanvasGameOver.SetActive(true);
}
/* *** */
}
}
Il n’y a rien dans ce script qui ne vous soit pas déjà familier. Nous n’avons plus qu’à renseigner le canvas via l’inspecteur et testez notre logique.
À ce stade, nous voudrions bien renvoyer le joueur vers une autre scène (le menu principal par exemple) quelques secondes après le Game Over. Pour cela, créons et ajoutons un script « LoadScence » à l’objet « CanvasGameOver ». Son rôle se décrit comme suit :
usingSystem.Collections;
usingUnityEngine;
usingUnityEngine.SceneManagement;
publicclassLoadScene : MonoBehaviour {
// Nom de la scène à charger, par défaut "Menu".
// Ce champ peut être modifié dans l'inspecteur Unity.
[SerializeField] stringsceneToLoad = "Menu";
// Temps d'attente avant de charger la scène
[Range(0f, 5f)][SerializeField] floatdelay = 0f;
// une coroutine qui démarre au lancement du script
// quand le GameObject auquel ce script est attaché est activé
IEnumeratorStart() {
// Attend pendant "delay" secondes avant de poursuivre l'exécution du code.
yieldreturnnewWaitForSeconds(delay);
// Charge la scène spécifiée dans "sceneToLoad" en utilisant SceneManager.
SceneManager.LoadScene(sceneToLoad);
}
}
En prenant le temps de tester vous noterez que notre logique fonctionne bien. Cependant une erreur est envisageable parce que la scène de destination n’a pas encore été créée. Nous nous pencherons sur la question dans les articles à venir.
Créer menu de jeu Unity
Dans cet article nous allons implémenter la transition vers un niveau supérieur en cas de réussite. Nous n’aurons besoin pour cela que de réutiliser ou étendre des fonctionnalités déjà existantes.
Rappelez-vous de l’objet « Background » possédant une texture dont nous contrôlons le défilement via le script « ScrollTexture ». Nous allons à présent éditer le boxCollider de ce cube de sorte à l’utiliser pour détecter l’arrivée du joueur. Pensons à rajouter le script « LoadScene » au gameObject « ExitDoor ». Ce script nous donne la possibilité de renseigner via l’inspecteur le niveau à charger.
Cependant, nous devons prendre soin à désactiver le box collider du gameObject Background et ne l’activer qu’après récupération de la carte d’ouverture . Au cas contraire, le joueur pourra passer au niveau suivant sans avoir besoin de la carte d’ouverture. De même, le script « LoadScene » devra être par défaut désactivé au niveau de « ExitDoor ».
Pour en revenir au changement de niveau, le script « Door » semble tout indiqué pour nous aider :
publicclassDoor : MonoBehaviour {
/* *** */
// Déclaration d'une variable pour stocker le BoxCollider
// qui sera activé lorsque la porte s'ouvre.
privateBoxCollider bcExit;
voidAwake() {
/* *** */
// Récupération du BoxCollider dans les enfants de l'objet
// pour permettre des interactions après l'ouverture de la porte.
bcExit = GetComponentInChildren();
/* *** */
}
publicvoidOpenDoor() {
/* *** */
// Activation du BoxCollider
bcExit.enabled = true;
StartCoroutine(PlayDoorCam());
}
IEnumeratorPlayDoorCam() { /* *** */ }
// Méthode publique pour charger la prochaine scène une fois la porte ouverte.
publicvoidLoadNextScene() {
// Récupération du script LoadScene attaché à ce même GameObject
// pour charger la prochaine scène.
LoadScene loadSene = GetComponent();
// Activation du script LoadScene pour démarrer le processus de chargement.
loadSene.enabled = true;
}
}
Nous avons à présent besoin d’un tag spécifique pour identifier le gameObject Background . Créons pour cela un tag « Exit » à lui affecter. Il ne nous reste plus qu’à étendre la méthode OnCollisionEnter du script « PlayerController » pour implémenter notre fonctionnalité :
private
void OnCollisionEnter(Collision collision) {
/* *** */
// Vérification si l'objet avec lequel le joueur
// entre en collision possède le tag "Exit".
if (collision.gameObject.tag == "Exit") {
// Si la collision se produit avec un objet marqué comme "Exit",
// la porte (door) charge la scène suivante.
door.LoadNextScene();
}
}
Vous pouvez tester le résultat en lançant le jeu. Nous rajouterons bientôt des scènes additionnelles pour conclure l’implémentation.
Implémentation système continu Unity
Nous allons enfin crée une interface d’accueil pour notre jeu. L’approche sera assez simple : réutiliser la scène existante. Autrement dit, la scène du Level1 nous servira de base.
Pour dupliquer la scène actuelle suivez l’instruction : File > Save As
Choisissez le dossier de destination (de préférence le dossier Scenes ) et renommez la scène « Menu ». Nous allons épurer la scène en supprimant les éléments suivants : PlayerFps , Unlocker , ExitDoor , CanvasGameOver . Ils ne nous seront plus utile.
Commençons par créer une nouvelle caméra. Positionnez-la au centre du labyrinthe et déplacer les ennemis de telle sorte qu’ils se retrouvent dans le champ de vision de la caméra lorsque cette dernière effectuera une rotation autour de l’axe Y. Pour la rotation, il nous suffit de rajouter le script « ObjectRotation » en initialisant la valeur de l’axe Y à 0.05 par exemple.
😎 Lancez le jeu et appréciez le rendu !
Créons en suite un nouveau canvas pour l’écran d’accueil. Bien sûr, nous veillerons comme la dernière fois à le mettre en mode Scale With Screen Size . Rajoutez un texte comportant le titre du jeu (SPACE LABYRINTH) et les trois boutons qui suivent : PLAY , CONTINUE , EXIT .
Il nous faut à présent un script « Menu » pour gérer le tout :
usingUnityEngine;
usingUnityEngine.SceneManagement;
publicclassMenu : MonoBehaviour {
// Cette méthode est appelée lorsqu'on clique sur Play
publicvoidPlayGame() {
// Utilisation de SceneManager pour charger la scène "Level1".
SceneManager.LoadScene("Level1");
}
// Cette méthode est utilisée pour quitter le jeu.
publicvoidQuitGame() {
// Directive conditionnelle pour vérifier si on est dans l'éditeur Unity
#if UNITY_EDITOR
// Si le jeu est exécuté dans l'éditeur Unity, on arrête simplement le mode
// jeu
UnityEditor.EditorApplication.isPlaying = false;
#else
// Si on est dans une version de build (jeu compilé), on quitte
// l'application
Application.Quit();
#endif
}
}
La subtilité dans ce code ci est au niveau de la méthode QuitGame . Elle s’adapte selon si on est dans l’éditeur ou dans une build finale du jeu.
Nous pouvons maintenant associer ce script au gameObject Canvas . Nos méthodes PlayGame et QuitGame étant publiques nous pouvons respectivement les associer à l’évènement OnClick des boutons Play et Quit .
Si vous avez suivi la formation précédente cette approche vous est largement familière. Il nous faut à présent ajouter la logique pour le bouton « Continue ». Rendez-vous à l’article suivant.
Effets sonores pour jeu Unity
C’est le moment d’implémenter la fonctionnalité du bouton continue. Nous pourrons ainsi sauvegarder la progression du joueur limitant ainsi d’éventuelle frustration.
Pour commencer nous allons prendre quelques dispositions au préalable comme créer (à titre figuratif) une nouvelle scène Level2 . Vous pourriez bien sûr y rajouter du contenu pour avoir un niveau de jeu supplémentaire.
Pour stocker la progression du joueur nous allons utiliser la classe PlayerPrefs .
En un premier temps, nous allons nous assurer qu’à chaque transition vers un niveau supérieur, le nom du niveau soit sauvegardé. Nous allons pour cela étendre la logique de la fonction LoadNextScene dans le script Door . Cela se présente comme suit :
publicvoidLoadNextScene() {
LoadScene loadSene = GetComponent();
loadSene.enabled = true;
PlayerPrefs.SetString("Continue", loadSene.sceneToLoad);
}
Le tout tient en une ligne de code (ligne 5). Il est important de souligner que nous avons dû modifier l’accessibilité de la variable sceneToLoad dans le script LoadScene afin d’y accéder à partir du script Door .
publicstringsceneToLoad = "Menu";
En faisant ultérieurement appel à la clé « Continue » nous pourrons récupérer la sauvegarde. C’est d’ailleurs ce que nous allons faire en rajoutant une nouvelle méthode nommée Continue dans le script Menu .
public
void Continue() {
// Récupère la valeur associée à la clé "Continue" dans les PlayerPrefs (si
// elle existe). Si aucune valeur n'a été sauvegardée avec cette clé, elle
// retourne une chaîne vide ("").
string playPrefValue = PlayerPrefs.GetString("Continue");
// Utilise l'opérateur ternaire pour déterminer quelle scène charger.
// Si 'playPrefValue' n'est pas vide, on utilise sa valeur pour charger la
// scène correspondante. Sinon, on charge la scène "Level1" par défaut.
string str = playPrefValue != "" ? playPrefValue : "Level1";
// Charge la scène définie dans la variable 'str'.
SceneManager.LoadScene(str);
}
C’était la pièce manque du puzzle. Notre méthode étant publique nous allons y accéder en rajoutant le script Menu dans l’évènement OnClick du bouton Continue .
Place maintenant au test. Passez du niveau 1 au 2 puis revenez appuyer le bouton Continue après avoir relancé le jeu. Si les transitions sont bien référencées vous accéderez directement au Level2. N’hésitez pas en cas d’erreur persistance à consulter le corrigé type disponible dans les ressources pour avoir des pistes d’erreurs potentielles.
S’il y a bien une chose qui manque à présent à notre jeu, c’est une ambiance sonore. Nous allons régler le problème dans l’article à suivre.
Gérer curseur souris dans Unity
C’est le moment d’aborder l’ambiance sonore du jeu. C’est de petits détails comme ceux à suivre qui aident à rendre un jeu plus agréable et immersif. Une partie se fera manuellement et pour l’autre nous aurons à toucher du code.
Toutes les audios dont nous aurons besoin se retrouvent dans le dossier Sounds . Commençons par la musique de fond :
- Ajoutez une audio source au PlayerFPS en renseignant l’audio music-loop
- La musique devra être audible et constante sur toute l’étendue de la scène. Veuillez
donc à initialiser la propriété Spatial Blend à 0. On s’assure ainsi que le son sera en 2D .
- N’oubliez pas d’activer les propriétés Play On Awake et Loop .
Suivez à nouveau chacune des étapes précédentes pour ajouter les son game-over au CanvasGameOver . Pour les deux autres ennemis la configuration est identique :
- Ajoutez une audio source avec les musiques appropriées,
- Activez les propriétés Play On Awake et Loop .
- La hauteur du son des ennemis devra être proportionnelle à leur distance. Veuillez
donc à initialiser la propriété Spatial Blend à 1 . On s’assure ainsi que le son sera en 3D .
Vous pouvez rajouter quelques configurations supplémentaires à la portée du sound conformément aux configurations suivantes :
Lorsque la porte s’ouvre ou que le joueur gagne la partie ; nous voudrions également des musiques spéciales pour marquer ces moments. Commençons par rajouter un composant audio source à l’objet ExitDoor . La suite va se gérer au niveau du script Door :
publicclassDoor : MonoBehaviour {
/* *** */
// Déclaration d'une variable pour l'AudioSource qui jouera les sons.
privateAudioSource audioSource;
// Déclaration des clips audio à jouer.
[SerializeField] AudioClip sfxOpenDoor, sfxWin;
voidAwake() {
/* *** */
// Récupération du composant AudioSource attaché à l'objet "Door".
// Cela permettra de jouer des sons sur cet objet.
audioSource = GetComponent();
}
publicvoidOpenDoor() { /* *** */ }
IEnumeratorPlayDoorCam() {
// Joue le son "sfxOpenDoor" une seule fois, lorsqu'on appelle cette
// méthode.
audioSource.PlayOneShot(sfxOpenDoor);
/* *** */
}
publicvoidLoadNextScene() {
// Joue le son "sfxWin" une seule fois lorsqu'on appelle cette méthode,
audioSource.PlayOneShot(sfxWin);
/* *** */
}
}
Il ne nous reste plus qu’à renseigner via l’inspecteur les audio appropriés et tester. Notre jeu est maintenant beaucoup plus intéressant. Nous pouvons cependant prendre le temps d’améliorer encore un détail : le curseur de la souris.
Gérer le curseur de la souris
Les mécaniques les plus importantes de notre jeu sont déjà implémentées. Il y a cependant un détail qui embête un peu. Lorsque vous lancez le jeu, le curseur de la souris reste inutilement visible, ce qui brise un peu l’immersion. Nous allons corriger cela.
Nous allons créer une classe Pointer à associer au PlayerFPS . Il y a deux aspects que nous devrions prendre en compte au sujet de cette mécanique. Tout d’abord, rendre la souris invisible et verrouiller sa position au cours de la partie de jeu. Ensuite, désactiver le verrouillage de la souris et activer sa visibilité en dehors des niveaux de jeu. Par exemple, au menu principal nous voudrions bien avoir de nouveau accès à la souris.
Prenez le temps de réfléchir à une solution en consultant la documentation de Unity. A titre de référence, voici une solution :
publicclassPointer : MonoBehaviour {
voidStart() {
// Rendre le curseur de la souris invisible à l'écran.
Cursor.visible = false;
// Verrouiller le curseur de la souris au centre de l'écran, empêchant tout
// mouvement du curseur.
Cursor.lockState = CursorLockMode.Locked;
}
// Cette méthode est appelée automatiquement lorsqu'on désactive ce
// GameObject.
privatevoidOnDisable() {
// Rendre le curseur de la souris visible à nouveau quand ce GameObject est
// désactivé.
Cursor.visible = true;
// Libérer le curseur, permettant à l'utilisateur de déplacer la souris
// librement. `CursorLockMode.None` désactive le verrouillage, autorisant le
// mouvement normal du curseur.
Cursor.lockState = CursorLockMode.None;
}
}
Une partie intéressante à souligner dans ce script se rapporte à l’usage de OnDisable . En cas d’échec de la partie en cours, le script est désactivé avant la transition vers le menu principal ; réactivant au passage l’usage de la souris.
A présent, prenez le temps de tester toutes les mécaniques implémentées jusqu’ici. Vous avez la possibilité de les étendre et de les enrichir sur la base des compétences acquises tout au long de cette formation.
Formez-vous gratuitement avec Alphorm !
Maîtrisez les compétences clés en IT grâce à nos formations gratuites et accélérez votre carrière dès aujourd'hui.
FAQ
Comment créer un labyrinthe 3D dans Unity ?
Comment programmer le mouvement du joueur dans un jeu Unity ?
Comment ajouter des ennemis dans un jeu Unity ?
Comment créer des animations d'ouverture de porte dans Unity ?
Comment intégrer des effets sonores dans un jeu Unity ?
Conclusion
En explorant ces techniques de développement de jeu avec Unity, quels autres aspects du développement de jeux aimeriez-vous approfondir ?