Tutoriel N°2 – Partie 2 : Création des classes

Bonjour et bienvenue dans cette 2e partie du tutoriel sur la création d’un générateur de labyrinthe aléatoire sur Unity 3D.

Cette partie du tutoriel est la plus grande, nous allons programmer toute les classes qu’utilisera notre générateur pour pouvoir fonctionner.

Avant d’ouvrir le projet, je vous informe que cette partie sera faite avec la version 5.3.0 f4 d’Unity. Il ne devrait pas avoir de problème avec les versions antérieur.

Une fois le projet Unity ouvert, commencez par créer un script C# et appelez le ListExtensions.
Comme son nom l’indique, cette classe est une extension de la classe générique « List ».

using System.Collections.Generic;


public static class ListExtensions
{

	public static IList<T> Shuffle<T> (this IList<T> list)
    { 

		int n = list.Count;  

		while (n > 1) {  
			n--;  
			int k = UnityEngine.Random.Range(0, n + 1);  
			T value = list[k];  
			list[k] = list[n];  
			list[n] = value;  
		} 
		return list;
	}
}

Cette classe ne possède qu’une méthode permettant de mélanger une liste. Elle sera utile dans la classe MazeGenerator.

N’hésitez pas à créer plusieurs dossiers pour que le projet soit mieux organiser. Voilà l’arborescence de mes dossiers.

Créez maintenant un nouveau script et nommez le VisualCell.
Ce petit script servira à contenir les composants Transform des murs de notre préfab que l’on a créé la dernière fois.

using UnityEngine;

public class VisualCell : MonoBehaviour 
{
    public Transform _Est;
    public Transform _West;
    public Transform _North;
    public Transform _South;
}

Effectuez un Drag and Drop du script sur notre prefab puis glissez les murs du prefab dans le script.

Continuons avec les petits scripts. Créez encore un nouveau script et nommez le Cell.
Ce script représente une cellule de notre labyrinthe, avec ses portes, sa position ainsi que son « état » si elle a était visitée ou non et bien sûr sont constructeur.

public class Cell 
{
    public bool _West, _North, _Est, _South;    // Mur Ouest, Nord, Est et Sud.
    public bool _visited;                       // Permet de savoir si oui ou non la cellule à était visiter.

    public int xPos, zPos;                      // Position en X et en Z.


    // Constructeur.
    public Cell (bool west, bool north, bool est, bool south, bool visited)
    {
        this._West = west;
        this._North = north;
        this._Est = est;
        this._South = south;
        this._visited = visited;
    }
}

Pour finir créez un script et nommez le CellAndRelativePosition.
Ce script contient une cellule, une énumération de direction, qui servira à savoir dans quelle direction aller pour construire notre labyrinthe, et son constructeur.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class CellAndRelativePosition 
{
    public Cell cell;           // Cellule.
    public Direction direction; // Direction à prendre lors de la suppression de mur.

    public enum Direction
    {
        North,
        South,
        East,
        West
    }
	
    public CellAndRelativePosition (Cell cell, Direction direction)
    {
        this.cell = cell;
        this.direction = direction;
    }
}

Voilà tous les petits scripts étant écrit, nous pouvons passer au script le plus gros.

Créez un nouveau script et nommez le MazeGenerator et ajoutez la directive using « System.Collections.Generic » pour avoir accès aux listes.

Attaquons par les attributs de la classe.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MazeGenerator : MonoBehaviour 
{
    public int _width, _height; //Largeur et hauteur du labyrinthe.

    public VisualCell visualCellPrefab; // Prefab qui sert de modèle à l'instanciation.

    public Cell[,] cells; //Tableau de cellules a deux dimensions.

    private Vector2 _randomCellPos; //Position de la cellule aleatoire qui va commencer la generation.
    private VisualCell visualCellInst; // Contient la copie du prefab de l'instanciation.

    private List<CellAndRelativePosition> neighbors; //Liste des cellules voisines

Voilà les attributs de notre classe MazeGenerator. Nous avons besoin de :

  • Deux entiers pour définir la taille de notre labyrinthe.
  • De notre prefab créer dans le précédent tutoriel.
  • Un tableau à deux dimensions représentant notre labyrinthe.
  • D’un Vecteur pour stocker la position de départ de la génération.
  • D’un deuxième prefab, cette fois-ci privé, qui stocke la copie du premier prefab pour permettre de le modifier.
  • Une liste des cellules voisines qui servira pendant la génération du labyrinthe.

Continuons avec les deux premières méthodes de la classe.

    void Start ()
    {
        cells = new Cell[_width, _height]; //Initialisation du tableau de cellules
        Init(); //Lance la fonction Init
    }

    void Init ()
    {
        for(int i = 0; i < _width; i++)
        {

            for(int j = 0; j < _height; j++)
            {

                cells[i, j] = new Cell(false, false, false, false, false);
                cells[i, j].xPos = i;
                cells[i, j].zPos = j;
            }
        }
        RandomCell(); //Lance la fonction RandomCell

        InitVisualCell(); //Lance l'instantiation des cellules visuel
    }

Ici rien de bien compliqué. Le point d’entrée du script est la méthode Start. Elle initialise notre tableau de cellule à deux dimensions et lance la méthode Init.
La méthode Init, parcourt notre tableau grâce aux deux boucles imbriquées et créés une cellule et lui attribut sa position à chaque index.
La méthode lance ensuite deux autres méthodes, RandomCell et InitVisualCell.

Voici la méthode RandomCell.

    void RandomCell ()
    {
        //Recupere une position X et Y aleatoire
        _randomCellPos = new Vector2((int)UnityEngine.Random.Range(0, _width), (int)UnityEngine.Random.Range(0, _height));

        //Lance la fonction GenerateMaze avec la positions X et Y aleatoire.
        GenerateMaze((int)_randomCellPos.x, (int)_randomCellPos.y); 
    }

Cette méthode permet de choisir aléatoirement une position dans le labyrinthe et de lancer la méthode GenerateMaze avec la position choisis en paramètre.

Voici la méthode GenerateMaze.

    void GenerateMaze (int x, int y)
    {
        //	Debug.Log("Doing " + x + " " + y);
        Cell currentCell = cells[x, y]; //Definit la cellule courante
        neighbors = new List<CellAndRelativePosition>(); //Initialise la liste
        if(currentCell._visited == true) return;
        currentCell._visited = true;

        if(x + 1 < _width && cells[x + 1, y]._visited == false)
        { //Si on est pas a la largeur limite max du laby et que la cellule de droite n'est pas visiter alors on peut aller a droite
            neighbors.Add(new CellAndRelativePosition(cells[x + 1, y], CellAndRelativePosition.Direction.East)); //Ajoute la cellule voisine de droite dans la liste des voisins
        }

        if(y + 1 < _height && cells[x, y + 1]._visited == false)
        { //Si on est pas a la longueur limite du laby et que la cellule du bas n'est pas visiter alors on peut aller en bas
            neighbors.Add(new CellAndRelativePosition(cells[x, y + 1], CellAndRelativePosition.Direction.South)); //Ajoute la cellule voisine du bas dans liste des voisins
        }

        if(x - 1 >= 0 && cells[x - 1, y]._visited == false)
        { //Si on est pas a la largeur limite mini du laby et que la cellule de gauche n'est pas visiter alors on peut aller a gauche
            neighbors.Add(new CellAndRelativePosition(cells[x - 1, y], CellAndRelativePosition.Direction.West)); //Ajoute la cellule voisine de gauche dans la liste des voisins
        }

        if(y - 1 >= 0 && cells[x, y - 1]._visited == false)
        { //Si on est pas a la longueur limite mini du laby et que la cellule du haut n'est pas visiter alors on peut aller en haut
            neighbors.Add(new CellAndRelativePosition(cells[x, y - 1], CellAndRelativePosition.Direction.North)); //Ajoute la cellule voisine du haut dans la liste des voisins
        }

        if(neighbors.Count == 0) return;  // Si il y a 0 voisins dans la liste on sort de la méthode.

        neighbors.Shuffle(); // Melange la liste de voisins

        foreach(CellAndRelativePosition selectedcell in neighbors)
        {
            if(selectedcell.direction == CellAndRelativePosition.Direction.East)
            { // A droite
                if(selectedcell.cell._visited) continue;
                currentCell._Est = true; //Detruit le mur de droite de la cellule courante
                selectedcell.cell._West = true; //Detruit le mur de gauche de la cellule voisine choisie
                GenerateMaze(x + 1, y); //Relance la fonction avec la position de la cellule voisine
            }

            else if(selectedcell.direction == CellAndRelativePosition.Direction.South)
            { // En bas
                if(selectedcell.cell._visited) continue;
                currentCell._South = true; //Detruit le mur du bas de la cellule courante
                selectedcell.cell._North = true; //Detruit le mur du haut de la cellule voisine choisie
                GenerateMaze(x, y + 1); //Relance la fonction avec la position de la cellule voisine
            }
            else if(selectedcell.direction == CellAndRelativePosition.Direction.West)
            { // A gauche
                if(selectedcell.cell._visited) continue;
                currentCell._West = true; //Detruit le mur de gauche de la cellule courante
                selectedcell.cell._Est = true; //Detruit le mur de droite de la cellule voisine choisie
                GenerateMaze(x - 1, y); //Relance la fonction avec la position de la cellule voisine
            }
            else if(selectedcell.direction == CellAndRelativePosition.Direction.North)
            { // En haut
                if(selectedcell.cell._visited) continue;
                currentCell._North = true; //Detruit le mur du haut de la cellule courante
                selectedcell.cell._South = true; //Detruit le mur du bas de la cellule voisine choisie
                GenerateMaze(x, y - 1); //Relance la fonction avec la position de la cellule voisine
            }
        }
    }

Cette méthode est une méthode récursive, c’est-à-dire qu’elle s’appelle elle-même.
Elle permet de parcourir chaque cellule et de rechercher les cellules voisines non visitées. Pour chaque cellule non visitée, elle relance la méthode avec la position de la cellule voisine.
Cette méthode permet de générer le labyrinthe « virtuellement ».

Et enfin la méthode InitVisualCell.

    void InitVisualCell ()
    {
        // Initialise mes cellules visuel et detruit les murs en fonction des cellules virtuel
        foreach(Cell cell in cells)
        {

            visualCellInst = Instantiate(visualCellPrefab, new Vector3(cell.xPos * 3, 0, _height * 3f - cell.zPos * 3), Quaternion.identity) as VisualCell;
            visualCellInst.transform.parent = transform;
            visualCellInst._North.gameObject.SetActive(!cell._North);
            visualCellInst._South.gameObject.SetActive(!cell._South);
            visualCellInst._Est.gameObject.SetActive(!cell._Est);
            visualCellInst._West.gameObject.SetActive(!cell._West);

            visualCellInst.transform.name = cell.xPos.ToString() + "_" + cell.zPos.ToString();
        }
    }
}

C’est la méthode qui permet de de créer le visuel du labyrinthe.
Elle instancie notre prefab et désactive les murs en fonction des booléens.
Par exemple : quand le booléen _North est sur true (dans la méthode GenerateMaze), cette méthode indique au prefab que son mur nord est désactivé.

Pour finir créez un GameObject vide dans votre scène et glissez le script MazeGenerator. Ce GameObject permet de contenir tous les prefabs instancié.
Indiquez la taille de votre choix, glissez le prefab de la cellule dans la case approprié.
Attention à ne pas choisir une très grande taille, vous risquerez de faire planter Unity. Au-delà de 70 x 70 vous verrez une baisse des FPS.

La 2e partie de ce tutoriel touche à sa fin, n’hésitez pas à commenter si ce tutoriel vous a plu ou si vous avez des problèmes de code.

Vous pouvez télécharger le projet compressé au format 7zip en suivant ce lien (Unity 5.3) : https://www.dropbox.com/s/p3r9jtlhvpjhv71/TutoLabyrinthe.7z?dl=0

Lien pour la version 2017.2 : https://www.dropbox.com/s/d27b7mrqmdydl23/TutoLabyrinthe.rar?dl=0

Licence Creative Commons
Le tutoriel est mis à disposition selon les termes de la Licence Creative Commons Attribution – Pas d’Utilisation Commerciale – Pas de Modification 4.0 International.

Ré-upload de l’ancien blog.
Date du poste original : 22 décembre 2015 
Mise à jour le : 19 mars 2019

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *