Ressources Magic Makers

Space Invaders - Bases

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 :

Exemple et Inspirations

Space Invaders

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 créer un projet 2D sur Unity
  • Comment placer sur la scène des images appelés sprites
  • Comment créer un joueur et le contrôler avec ton clavier ou ta manette
  • Comment créer tes propres scripts avec le langage C#

Comment faire apparaitre aléatoirement des obstacles et des ennemis

Jouer à la version : VERTCAL SCROLLING

Description du jeu

  • Un joueur qui se déplace de gauche à droite à l’aide du clavier
  • Des obstacles aléatoires défilant de bas en haut, à détruire ou à éviter
  • Des objets à ramasser

Création du projet sur Unity

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

Importation des assets

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: 

  • Télécharger le kit du shoot them up
  • Dézippe le dossier
  • Glisser-déposer le unitypackage téléchargé dans la partie Assets de l’éditeur Unity

Confirmer l’importation de tous les éléments du package.

Création de la scène

Assembler le joueur

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.

 

Ajouter une image de fond

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

Faire bouger le joueur

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! 

Ajouter un script au joueur

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! 

Déplacer le joueur horizontalement

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() {
  float horizontal = Input.GetAxis(« Horizontal »);
  transform.Translate(Vector2.right * horizontal * Time.deltaTime);

 

Tu peux en apprendre plus, dans la Fiche Ressources Programmation C# sur les trois notions utilisées pour ce script :

  • Input.GetAxis() 
  • transform.Translate() 
  • Time.deltaTime 

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!

Empêcher le joueur de sortir de l’écran

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éation des murs

Crée d’abord tes murs!

  1. Ajoute un objet vide sur la scène (clic droit sur la hiérarchie > create empty)

Pense bien à renommer ton objet en Mur Invisible!

  1. Ajoute un Box Collider 2D (add component > physics 2D > box collider 2D)

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!

  1. Une fois que le mur invisible est bien placé, tu peux enfin le dupliquer (sélectionne-le puis CTRL+D) pour placer un mur sur le bord opposé

(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!

 

  1. Sélectionne le joueur et ajoute-lui un Rigidbody2D  (add component > physics 2D > Rigidbody 2D)

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!

 

  1. Vérifie les paramètres du RigidBody 2D

     

  • Assure toi que le Rigidbody du joueur soit dynamique! Contrairement au statique, cela veut dire que le joueur peut se déplacer et être déplacé par d’autres objets qui exercent une force sur lui!

     

  • Pense aussi à mettre la gravité à 0! Autrement, le joueur tomberait constamment en bas de l’écran
  1. Ajoute aussi au joueur un Box Collider 2D

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à! 

Création des obstacles

Ajouter un obstacle dans ta scène

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

Déplacement de l’obstacle

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(Vector2.up * Time.deltaTime);
}

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!

Collision entre l’obstacle et le joueur

Les colliders

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

Trigger

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

 

Script

  • Le joueur possède un collider 2D non-trigger
  • L’obstacle possède un collider 2D trigger

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
{
    private void OnTriggerEnter2D(Collider2D other)
    {
      //Code qui se déclenche lors de la collision
      Debug.Log(« Message pour tester la collision »);
    }
}

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é! 

 

 

Destruction de l’objet

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
{
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag==« Player »)
        {
            Destroy(other.gameObject);
        }
    }
}

 

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
{
    public string target;

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag==target)
        {
            Destroy(other.gameObject);
        }
    }
}

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!

Multiplication de l’Obstacle

Prefab

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:

  • La position des obstacles est toujours la même à chaque fois qu’on recommence le jeu
  • Il faut placer soi-même les obstacles, ce qui peut vite être pénible
  • Il est impossible de créer un niveau sans fin

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!

 

  1. Créer un objet vide sur la scène (clic droit sur la hiérarchie > create Empty)
  2. Ajouter son propre script sur cet objet (Add Component > New Script). Ce script servira à générer des objets donc on peut le baptiser Instantiateur, Generateur ou Spawner…

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
{
    public GameObject prefab;

    private void Update()
    {
        Instantiate(prefab);
    }
}

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
{
    public GameObject prefab;

    private void Update()
    {
        Instantiate(prefab, transform.position, transform.rotation);
    }
}

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
{
    public GameObject prefab;
    public float delai;

    private float prochainTemps;


    private void Update()
    {
        if (Time.time >= prochainTemps)
        {
            Instantiate(prefab, transform.position, transform.rotation);
            prochainTemps = Time.time + delai;
        }
    }
}

  • tempsPrécédent est la variable où on stocke le moment de la dernière instanciation. 
  • delai est le temps qu’on définit entre chaque instantiation. On l’ajoute au temps précédent pour savoir quand sera la prochaine instantiation.

 

Nettoyage des obstacles

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.

  • crées un gameObject vide
  • ajoutes un Box Collider 2D avec IsTrigger activé
  • ajoutes sur cet objet le script du style DetruireCibleQuandCollision que tu as fait plus tôt

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)

 

Apparition aléatoire des obstacles

 

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! 

  • sélectionne ton générateur d’obstacle et ajoute un nouveau script (que tu peux appeler DecalageAleatoire)

public class DecalageAleatoire : MonoBehaviour
{
    public float magnitude = 1f;

    private Vector2 positionDeBase;

    private void Start()
    {
        positionDeBase = transform.position;
    }

    void Update()
    {
        float decalage = Random.Range(-magnitude,magnitude);
        transform.position = positionDeBase + Vector2.right * decalage;
    }
}

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!