Crée ton premier jeu d’arcade sur Unity: Le space shooter !
Ce guide a été créé avec la dernière version stable de Unity au moment de la rédaction (2021.3.3f)
Sur cette page tu trouveras :
Un space shooter est un jeu en vue du dessus où le joueur incarne un vaisseau et doit tirer sur des ennemis tout en évitant des obstacles! C’est un grand classique de l’histoire du jeu vidéo!
Objectif de ce Projet : Réaliser un top down space shooter en 2D, autrement dit un jeu de tir spatial en vue de dessus ! Jeu exemple : ici
Ce que tu vas découvrir lors de ce projet:
Comment faire apparaitre aléatoirement des obstacles et des ennemis
Si tu n’as pas installé Unity, suis les étapes d’installation d’Unity et de Visual Studio Code de la fiche ressource Installation et Prise en Main de Unity
Si tu as Unity d’installé, suis les étapes de créer un Projet 2D de la fiche ressource Installation et Prise en Main de Unity
Pour cette activité, nous allons nous servir d’assets déjà existants. Magic Makers a préparé ce kit du shoot them up avec tous les assets nécessaires aux activités de ce module! Tu peux notamment y trouver des sprites pour construire assembler ton joueur.
Étapes pour télécharger le kit:
Confirmer l’importation de tous les éléments du package.
Tu peux maintenant assembler ton joueur à l’aide des sprites présents dans le kit!
On va se servir d’une base, et ajouter par dessus des parties indépendantes pour personnaliser le joueur! Par exemple, tu peux prendre le sprite de base d’un vaisseau et le décorer d’ailes, de canons, de moteurs, d’un cockpit, d’un parapluie…etc. Personnalise ton propre joueur!
Commence d’abord par créer un sprite sur ta scène en t’aidant de la Fiche Ressources Programmation en C#
Tu peux ensuite ajouter par dessus d’autres sprites en glissant dans la hiéarchie, un élément dans un autre, il deviendra son enfant et bougera en même temps que l’élément parent dont il dépend.
Trouve dans tes assets une image de fond qui te servira de décor! Ça peut être une image de l’espace, de l’océan, le sol d’une planète… tout ce que tu veux qui soit en vue du dessus!
Astuce: Si tu as trouvé une image sur internet ou depuis ton ordinateur, tu peux glisser le fichier image vers l’onglet Assets de ta fenêtre Unity pour importer l’image dans ton projet!
Il suffit ensuite de glisser l’image sur la scène! Tu peux ensuite utiliser ces outils pour placer correctement ton joueur et ton décor:
Attention: Il se peut que l’un de tes sprites disparaisse parce qu’il est recouvert par un autre! C’est un problème qu’on peut régler en définissant l’ordre des calques. Tu peux apprendre à gérer l’ordre des calques de sprites en lisant la Fiche Ressources Objets et Composant
Maintenant que ton vaisseau est placé sur la scène, il est temps de le faire bouger! Il n’y a pas de composant dans Unity nous permettant de faire bouger le joueur en fonction de la manette…Pour cela, il va falloir créer son propre composant avec un script!
Ajoute donc un script sur l’objet que tu veux déplacer (ici le joueur)!
Tu ne sais plus comment créer un script? Appuie-toi sur la Fiche Ressources Programmation C#
Nous appellerons ce script MouvementHorizontal ou MouvementJoueur, ou PlayerMovement. N’importe quel nom fonctionne tant qu’il nous permet de comprendre sans mal à quoi sert le script!
Le défi est maintenant de déplacer l’objet horizontalement, à gauche ou à droite, en fonction des touches pressées par le joueur!
Remarque:
Puisque nous souhaitons que ce script tourne pendant toute la durée du jeu, tout sera codé dans le Update pour ce script.
private void Update() { |
Tu peux en apprendre plus, dans la Fiche Ressources Programmation C# sur les trois notions utilisées pour ce script :
Défi!
Penses-tu pouvoir changer la vitesse de déplacement? Essaie donc de multiplier ta translation par un nombre flottant (float) pour voir! Si tu y arrives, rends cette variable publique pour la manipuler depuis l’inspecteur!
Maintenant que tu peux contrôler le joueur avec les manettes, tu risques de remarquer un problème… Rien ne l’empêche de sortir de l’écran! Cela serait dommage d’avoir un joueur qui quitte l’arène avant même que le jeu ne commence! Heureusement, tu peux poser des murs invisibles pour empêcher le joueur de traverser les bords de l’écran. Pour cela, tu vas utiliser des colliders!
Un collider est une forme qui représente l’objet pour détecter les collisions avec les autres objets. Tu peux en apprendre plus sur la Fiche Ressources Objets et Composants
Crée d’abord tes murs!
Pense bien à renommer ton objet en Mur Invisible!
C’est la forme qui va représenter les frontières physiques de ton mur invisible. Ici, ça sera un rectangle!
Un contour vert apparaît sur la scène! !
Il s’agit de ton collider! Cette forme représente les collisions de ton mur invisible
Tu peux maintenant déplacer l’objet, l’agrandir et le rétrécir en utilisant les propriétés du Transform, afin de le placer sur le bord de l’écran!
(les murs sont visibles en vert ici, mais ils seront invisibles dans le jeu. Il suffit de les sélectionner pour les rendre visibles sur la scène)
Collision du joueur
Les murs n’arrêteront pas le joueur si celui-ci n’a pas de collider pour détecter les collisions!
Le rigidbody est le composant qui va s’occuper d’ajouter de la physique à un objet. C’est le composant qui permet la gestion des forces comme la gravité ou l’accélération et de détecter les collisions!
IsTrigger doit être désactivé pour que le joueur puisse être correctement repoussé par les murs.
Avec ceci, le joueur devrait pouvoir être bloqué par les deux murs au bord de l’écran même quand on essaie d’aller au-delà!
Explore les sprites que tu as importé dans tes assets et choisis un sprite d’obstacle qui te convient! Cela peut être un astéroïde, un ballon, un serpent, une voiture… tout ce que tu veux!
Une fois que tu as choisi le sprite que tu souhaites prendre en tant qu’obstacle, dépose-le sur la scène depuis les assets!
Tu as perdu les assets du projet? Voici un lien vers le Package d’assets 2D fourni par Magic Makers!
Si tu ne sais plus comment manipuler les sprites, appuie toi sur la Fiche Ressources Sprites Objets et Composants
Le mouvement de nos obstacles va être super simple: Aller tout droit! Pour cela, il va nous falloir un script.
Astuce:
Le rôle de ce script sera uniquement de déplacer un objet en avant! Pour tout ce qui est détection de collision et destruction, nous utiliserons d’autres scripts! En séparant chaque fonctionnalité dans son propre composant, on peut les combiner sur d’autres objets pour obtenir le comportement qu’on souhaite.
A quelle autre occasion nous pouvons utiliser ce script. Autrement dit, quel autre objet pourrait aller tout droit? Prend le temps de réfléchir à une réponse avant de lire la suite…
L’exemple le plus frappant serait: un projectile! L’obstacle et le projectile sont deux objets pouvant utiliser le même script pour aller tout droit! Ce script sera donc réutilisé plus tard 😉 Le secret du développeur de jeu, c’est de savoir comment réutiliser ce qu’il a déjà fait pour ne pas avoir à le refaire!
Voici ce que nous allons mettre dans le script:
void Update() |
transform.Translate() permet toujours de déplacer l’objet dans une direction. La direction est donnée entre les parenthèses:
Lorsque tu lances le jeu, l’obstacle devrait pouvoir partir dans une direction! Si cette direction ne te plait pas, tu peux toujours orienter ton objet en utilisant l’outil rotation!
Défi !
Penses-tu pouvoir changer la vitesse de déplacement? Essaie donc de multiplier ta translation par un nombre flottant (float) pour voir! Si tu réussis, rends la variable publique pour la manipuler depuis l’inspecteur!
Premièrement, il faut s’assurer que le joueur possède bien un Collider 2D! C’est la forme qui représente le joueur pour calculer les collisions. Il a déjà été ajouté lorsqu’il fallait empêcher le joueur de sortir de l’écran, mais c’est toujours bien de vérifier!
Dans la même logique, ajoutons un collider à notre obstacle! Il se peut que le sprite de l’obstacle ait déjà un box collider 2D qui lui soit attaché! Si ce n’est pas le cas, il faut l’ajouter soi-même:
Add Component > Physics 2D > Box Collider 2D
Rappel:
Il faut bien penser à utiliser les composants du Physics 2D puisque nous travaillons sur un projet 2D! Un collider ou un rigidbody classique ne fonctionnerait pas! Il faut un collider 2D!
Fiche Ressources Objets et Composants
Puisque nous voulons détecter quand notre objet rentre dans un autre, il faut penser à cocher la case IsTrigger. Cela transforme l’objet en trigger.
Fais bien attention à cocher cette case seulement pour l’obstacle et non pour le joueur! Si le joueur devient un trigger, il ne sera plus bloqué par les murs aux bords de l’écran!
Quelle est la différence entre un objet normal et un trigger?
Un trigger ne rentre pas en collision avec les autres objets. Il prévient juste quand il touche un autre objet. Les triggers sont généralement utilisés pour déclencher des événements comme l’apparition d’un ennemi, la destruction d’un obstacle, l’ouverture d’une porte…
Autrement dit, un trigger définit une zone non solide qui déclenche quelque chose quand un autre objet entre dedans! Tu peux en apprendre plus sur l’utilisation du trigger dans la Fiche Ressources Objets et Composants
Nous allons maintenant créer un nouveau script pour détecter la collision! Nous allons attacher ce script sur l’obstacle!
Tu connais la méthode Start() pour déclencher quelque chose en début de scène, et la méthode Update() qui s’exécute à chaque image, mais aucune des deux méthodes ne se déclenche au moment d’une collision! C’est donc le moment d’introduire une nouvelle méthode unity: OnTriggerEnter()
OnTriggerEnter() est une méthode qui se déclenche à chaque fois que l’objet rentre en collision avec un autre objet.
Puisque nous travaillons avec des objets 2D, il faudra utiliser OnTriggerEnter2D()
Voici le script de base:
public class DestroyTargetOnCollision : MonoBehaviour |
La console devrait afficher “Message pour tester la collision” à chaque fois que l’obstacle touche un autre objet.
Remarque:
N’oublie pas de cocher IsTrigger du Box Collider 2D de l’obstacle!
Le Debug.Log ne servait qu’à tester la collision! Maintenant il faut coder la destruction de l’objet qu’on a touché!
Pour détruire un objet, on utilise la méthode Destroy() en mettant l’objet à détruire entre parenthèses.
Fiche Ressources Programmation C#
Comment désigner celui que notre objet a touché? Avec la variable du Collider2D!
Ici, nous l’appelons other. Elle représente le collider de l’autre objet. On peut donc détruire l’autre objet avec:
Destroy(other.gameObject); |
L’utilisation du tag
Ce script détruit bel et bien tous les objets qui touchent notre obstacle, mais nous souhaitons seulement détruire le joueur. Il faut donc vérifier que l’objet qui touche l’obstacle soit bien le joueur avant de le détruire. La manière la plus répandue de le faire est de vérifier son “tag” autrement dit son étiquette. Si celui-ci correspond à celui du joueur, on peut donc le détruire!
Ajoutons d’abord ce fameux tag au joueur:
On verra plus tard comment ajouter ses propres étiquettes personnalisées.
Voici une nouvelle version du script avec la condition:
public class DestroyTargetOnCollision : MonoBehaviour |
Le tag est sensible à la casse! Il faut bien écrire Player avec la majuscule comme pour le tag!
Vérifie avec tes makers que le code fonctionne et que l’obstacle détruit bien le joueur quand il le touche!
Nous pouvons laisser le script tel quel vu qu’il fonctionne comme nous le souhaitons. Cependant, nous aurons besoin de détruire bien d’autres objets par la suite, qui ne possèdent pas forcément le tag Player. (Par exemple, au moment où le joueur pourra tirer sur les ennemis pour les détruire)
Cela serait pratique de pouvoir utiliser le même script à chaque fois, en ne changeant que le tag en fonction de ce qu’on veut. Et bien nous allons justement modifier le script pour qu’il puisse prendre n’importe quel tag!
public class DestroyTargetOnCollision : MonoBehaviour |
target est une variable publique. Elle apparaît donc dans l’inspecteur. Ici, précisons que le tag que nous voulons détruire est “Player” (avec une majuscule!) et le script devrait fonctionner à nouveau!
Super! Nous allons pouvoir utiliser ce script à chaque fois qu’on souhaite détruire un objet qui touche un autre objet!
A ce stade, nous avons donc un obstacle qui avance tout droit et qui détruit le joueur lorsqu’il entre en collision avec celui-ci. Mais un seul obstacle ne suffit pas pour faire un jeu! On va pouvoir cloner notre obtacle qui servira de modèle grâce au prefabs!
Fiches Ressources Objets et Composants
Dans les Assets créer un dossier “Prefab” où ranger tes prefabs et glisser ton Obstacle dans ce dossier ! Et voilà ! Tu en as fait un modèle.
Tu n’as plus qu’à glisser-déposer le préfab de ton obstacle sur la scène pour générer un Clone !
Générer automatiquement des obstacles
Nous avons vu comment placer des prefabs d’obstacles à la main. Cela permet de choisir où se trouve chaque obstacle au moment de lancer le jeu. Cependant, cela peut poser quelques problèmes:
Pour répondre à ces problèmes, nous allons laisser le code créer les obstacles pour nous!
On va donc créer un générateur d’obstacles, qui instancie lui-même un obstacle au bout d’un certain temps!
Maintenant place au script! Voici “Instantiate()” qui permet de générer un objet sur la scène à partir du prefab qu’on lui donne.
Instantiate(prefab); |
Pour récupérer le prefab, utilise une variable publique GameObject. Le GameObject peut aussi bien désigner un objet déjà présent sur la scène qu’un prefab situé dans les assets. Ici, on s’en sert plutôt dans le second cas, pour générer un prefab!
public class Generator : MonoBehaviour |
Glisser-déposer le prefab de l’obstacle dans l’inspecteur du générateur, sans quoi le script ne saura pas quel objet générer.
Le jeu devrait maintenant générer un obstacle à chaque image! Cependant, il le fait systématiquement au centre de la scène plutôt qu’à l’endroit où se trouve notre objet générateur.
Modifiez Instantiate(prefab) pour qu’il prenne en compte la position et la rotation de notre objet grâce à transform.position et transform.rotation.
public class Generator : MonoBehaviour |
Il reste une seule chose à régler: mettre un délai entre chaque instantiation!
Tel quel, un obstacle est créé toutes les secondes. Il faut modifier le script pour vérifier qu’un certain temps s’est écoulé avant d’instancier, et pour cela on utilise Time.time!
Time.time est la variable qui exprime le nombre de secondes écoulées depuis le début du jeu.
Voici le script en entier
public class Generator : MonoBehaviour |
A force de multiplier les obstacles, le jeu prend de plus en plus de mémoire pour tourner! Pour ne pas surcharger la mémoire du jeu, on va utiliser un collider qui va s’occuper de supprimer les obstacles qui quittent l’écran.
En assignant le tag “obstacle” au prefab de notre obstacle et à la cible du script qu’on vient d’ajouter, le collider va s’occuper de supprimer tous les obstacles qui le touche. Place le en dessous de l’écran de manière à ce qu’il attrape tous les obstacles que le joueur a pu évité!
(remarque: si la collision ne fonctionne pas, assure toi à ce que l’obstacle possède un Rigidbody 2D, avec une Gravité à Zéro)
Pour l’instant, le générateur d’obstacle pose les obstacles les uns à la suite des autres, ce qui ne doit pas être très amusant à éviter!
Pimentons un peu en les faisant apparaître avec un décalage aléatoire! Comment faire pour décaler le générateur d’astéroïde entre chaque obstacle? Tu l’as sûrement deviner, tu vas pouvoir le faire avec un script!
public class DecalageAleatoire : MonoBehaviour |
Ce script déplace le générateur d’obstacle par un décalage aléatoire (entre -magnitude et +magnitude) autour de sa position de base
Tu peux ensuite définir la magnitude (le maximum d’éloignement de la position de base) dans l’inspecteur!