Unity: Comment créer un Tilemap 2D par programmation
Pour le tutoriel vidéo, cliquez ici
Si vous devez créer différents Tilemaps de différentes tailles, consultez la deuxième partie de ce tutoriel
Le composant Tilemap a été introduit dans Unity 2017.2 et a considérablement facilité le processus de développement de jeux 2D. Avec la version 2018.3, le Tilemap isométrique a été introduit, offrant un excellent support pour les jeux 2.5D. Récemment, j’ai eu l’occasion de travailler étroitement avec ce composant et j’ai été mis au défi de créer des tuiles par programmation. Dans mon dernier jeu, j’ai beaucoup de niveaux et tous ont le même plateau basé sur le Tilemap. Mais la carte elle-même a une configuration unique au niveau. Évidemment, étant un développeur professionnel, je ne voulais pas créer 60 scènes de jeu et peindre tous les niveaux à la main, mais plutôt avoir un mécanisme pour remplir le plateau avec les éléments appropriés en fonction de l’entrée donnée. Si vous êtes curieux de savoir à quoi ressemble le résultat final, voici un lien vers le jeu.
Le code source est disponible sur GitHub, veuillez trouver le lien à la fin de ce tutoriel.
J’utilise la dernière Unity 2019.2.0.1f disponible. Pour travailler avec ce tutoriel, vous devriez avoir au moins la version 2018.3. Nous utiliserons la grille isométrique mais la technique décrite est applicable à tout type.
Avant de commencer, je suggère fortement de lire cet environnement 2D isométrique brillant avec l’entrée de blog Tilemap pour obtenir une compréhension de base du Tilemap isométrique.
Comme nous travaillons dans un environnement 2D, vous devez configurer un nouveau projet 2D (reportez-vous à 2DAnd3DModeSettings)
Il y a 3 composants principaux nécessaires pour atteindre notre objectif: un plateau de jeu où le jeu aura lieu, un endroit où garder une description de niveau et un code qui se connecte l’un à l’autre.
Partie 1. Création du tableau
Commençons par importer les ressources d’image nécessaires. Je vais utiliser les mêmes images que j’ai utilisées pour mon jeu:
Nous garderons les images dans le dossier des tuiles, il suffit de les faire glisser et de les déposer là.
Remarque! Vous devriez avoir une taille correcte de « Pixel par unité » pour chaque image de tuile (pour plus de détails, voir Environnements 2D isométriques avec Tilemap). Pour ces images, la valeur est 1096.
C’est une bonne pratique de séparer les différentes couches d’une scène en objets de jeu dédiés avec des noms descriptifs. Imaginez l’écran de jeu typique qui pourrait contenir une zone de jeu, une interface utilisateur, des placements publicitaires, etc.
Suivant cette approche, créons un nouvel objet de jeu vide nommé GameZone
où le plateau et tous les éléments du jeu pourraient être placés.
Maintenant, il est temps de créer des tuiles réelles à partir des images que nous avons importées précédemment
Appuyez sur Window->2D ->Palette de tuiles
Une fois que vous avez terminé la peinture, vous devez compresser manuellement les limites du tilemap. Pour cela, vous devez sélectionner Tilemap dans la vue Hiérarchie, appuyer sur un bouton paramètres (engrenage) du composant Tilemap et sélectionner « Compresser les limites de Tilemap »
Bien joué, le plateau de jeu est prêt à être utilisé! Nous y reviendrons dans la partie 3.
Partie 2. Données de niveau
Comme nous voulons réutiliser la même scène et le même plateau de jeu, les données de niveau doivent être conservées quelque part. Une solution simple pour cela est un simple fichier json qui décrira comment chaque niveau doit être construit.
Il est important de comprendre ce que nous essayons de réaliser, c’est pourquoi je dois dire quelques mots sur la mécanique. Dans le jeu, il y a des objets qui se déplacent le long du chemin du début à la fin (à peu près comme dans Zuma) et le but du joueur est de tous les détruire. Dans ce tutoriel, nous allons créer ce chemin qui sera unique pour chaque niveau.
D’accord, revenons au projet.
Il existe plusieurs façons d’accéder à une donnée externe dans un runtime à partir d’un script. Ici, nous utiliserons des dossiers de ressources
Créons un nouveau dossier — Fichiers — et un sous-dossier Ressources. C’est l’endroit où nous voulons conserver les données, alors créez un nouveau fichier — Niveaux.json et placez-le là.
Pour les besoins du tutoriel, nous n’aurons que deux champs pour décrire chaque niveau:
- number – un int pour identifier un chemin de niveau
- — le chemin basé sur des tuiles que nous voulons créer par programme. Tableau de valeurs où la première valeur est le point de départ et la dernière valeur est le point final.
C’est le fichier que je vais utiliser. Ne vous inquiétez pas de ces valeurs dans path, nous y reviendrons plus tard.
{
"levels":
},
{
"number": 2,
"path":
},
{
"number": 3,
"path":
}
]
}
Créons un autre dossier — Scripts — et maintenant le codage commence enfin.
Nous voulons accéder aux données du fichier dans le code, nous avons donc besoin d’une classe de modèle pour cela. Il est temps de créer notre tout premier script – LevelsData
. Il n’est pas destiné à être instancié par Unity, donc les méthodes MonoBehaviour
et Start
Update
doivent être supprimées. Dans le fichier ci-dessus, nous pouvons voir que l’élément racine est un array
de niveaux où chaque niveau doit avoir un int
numéro de champ et un int array
chemin de champ. N’oubliez pas non plus de mettre l’annotation .
public class LevelsData
{
public LevelData levels;
public class LevelData
{
public int number;
public int path;
}
}
Bien, maintenant nous avons le fichier et le modèle. L’étape suivante consiste à se transformer l’un en l’autre. Créons un autre script – GameZone
— et attachons-le à l’objet GameZone
sur la scène. Ce script sera utilisé plus tard pour configurer l’ensemble du plateau de jeu.
Suivez le principe de responsabilité unique créons encore un autre script – LevelsDataLoader
– qui fera toute la transformation. Attachez-le également à l’objet GameZone
.
public class LevelsDataLoader : MonoBehaviour
{
private const string LevelsPath = "Levels";
public Dictionary<int, LevelsData.LevelData> ReadLevelsData()
{
var jsonFile = Resources.Load(LevelsPath, typeof(TextAsset)) as TextAsset;
if (jsonFile == null)
{
throw new ApplicationException("Levels file is not accessible");
}
var loadedData = JsonUtility.FromJson<LevelsData>(jsonFile.text);
return loadedData.levels.ToDictionary(level => level.number, level => level);
}
}
Cette classe chargera les données et les renverra sous forme de dictionnaire où la clé est le numéro de niveau et les données sont les données de niveau elles-mêmes.
Maintenant, nous devrions pouvoir accéder aux données dans le script GameZone
.
public class GameZone : MonoBehaviour
{
private Dictionary<int, LevelsData.LevelData> _levelsData;
private LevelsDataLoader _dataLoader;private void Awake()
{
_dataLoader = GetComponent<LevelsDataLoader>();
}private void Start()
{
_levelsData = _dataLoader.ReadLevelsData();Debug.Log(_levelsData.Count + " levels have been stored in the dictionary!");
}
}
Revenez à Unity, appuyez sur le bouton de lecture et vérifiez la console — vous devriez voir le message « 3 niveaux ont été stockés dans le dictionnaire! »
Partie 3. Connexion de la Carte et des données
Félicitations, vous avez atteint la dernière et la plus intéressante partie de ce tutoriel. Comment allons-nous réellement connecter la carte et les données? Continuez à lire pour le découvrir!
Tout d’abord, plaçons les tuiles horizontal
et start_stop
créées dans la première partie du tutoriel dans le dossier Ressources sous le dossier Tuiles. Ajoutez ensuite un nouveau script — TilesResourcesLoader
— – une classe d’assistance statique pour charger des tuiles à partir de ce dossier dans un runtime.
public static class TilesResourcesLoader
{
private const string PathHorizontal = "horizontal";
private const string StartStop = "start_stop"; public static Tile GetPathHorizontalTile()
{
return GetTileByName(PathHorizontal);
} public static Tile GetStartStopTile()
{
return GetTileByName(StartStop);
} private static Tile GetTileByName(string name)
{
return (Tile) Resources.Load(name, typeof(Tile));
}
}
Comme dernière étape, nous devrions placer ces tuiles sur le plateau au démarrage de la scène. Revenons au script GameZone
. Tout d’abord, nous devons simuler la sélection du niveau, dans le jeu réel, cela se produit généralement chaque fois qu’un utilisateur appuie sur un bouton de niveau. Par souci de simplicité, ajoutons un niveau de champ public à GameZone
et changeons sa valeur en 1 pour démarrer. Je vais d’abord vous montrer le script final:
public class GameZone : MonoBehaviour
{
public int Level; private const int FieldLineSize = 11;
private const int FieldTotalTiles = FieldLineSize * FieldLineSize;
private Dictionary<int, LevelsData.LevelData> _levelsData; private void Start()
{
_levelsData = GetComponent<LevelsDataLoader>().ReadLevelsData();
SetupTiles();
} private void SetupTiles()
{
var baseLevel = GetComponentsInChildren<Tilemap>(); var localTilesPositions = new List<Vector3Int>(FieldTotalTiles);
foreach (var pos in baseLevel.cellBounds.allPositionsWithin)
{
Vector3Int localPlace = new Vector3Int(pos.x, pos.y, pos.z);
localTilesPositions.Add(localPlace);
} SetupPath(localTilesPositions, baseLevel);
} private void SetupPath(List<Vector3Int> localTilesPositions, Tilemap baseLevel)
{
var path = _levelsData.path;
var pathHorizontalTile = TilesResourcesLoader.GetPathHorizontalTile();
var first = path.First();
var last = path.Last();
foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
} var startStopTile = TilesResourcesLoader.GetStartStopTile();
baseLevel.SetTile(localTilesPositions, startStopTile);
baseLevel.SetTile(localTilesPositions, startStopTile);
}
}
Wow, c’est beaucoup d’action! Laisse-moi te guider.
Dans la méthode SetupTiles
, nous devons d’abord obtenir le tilemap lui-même car nous avons besoin de connaître les positions des tuiles pour le changer. Pour ce faire, nous utilisons la méthode tilemap.cellBounds.allPositionsWithin
qui renvoie toutes les positions des tuiles à partir de la toute première — dans la configuration donnée, il s’agit d’une tuile la plus basse.
Reportez-vous à l’image suivante où chaque nombre représente l’index dans la liste localTilesPositions
.
Vous souvenez-vous des valeurs que nous utilisons dans le chemin dans Levels.json
? Comme vous l’avez peut-être déjà deviné, ces valeurs sont les index des tuiles du tableau. Tout ce que vous devez faire maintenant est d’avoir une image de feuille de triche pour vous aider à construire des niveaux. C’est celui que j’ai utilisé pendant le développement:
Dans cet exemple, nous définissons une ligne horizontale dans le chemin, veuillez vous référer à la méthode SetupPath
. La partie clé est la boucle suivante:
foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
}
Ici, nous parcourons localTilesPositions
pour trouver ceux pour définir la tuile souhaitée — horizontale dans ce cas.
Remarque! la méthode GetRange
a deux paramètres – index et count.
Afin de marquer les positions de début et de fin du chemin, la tuile start_stop
est utilisée.
Voici le résultat de notre travail acharné:
Essayez maintenant de changer le champ de numéro de niveau du script GameZone
de 1 à 2 ou 3 et vous verrez que le chemin est chargé correctement pour le niveau donné.