Préface
Si vous êtes trop
paresseux pour prendre votre temps, faire un niveau pour votre jeu, alors vous êtes au bon endroit.
Cet article vous expliquera en détail comment vous pouvez utiliser l'une des
nombreuses autres méthodes de génération en utilisant l'exemple des hautes terres et des grottes. Nous considérerons l'algorithme
Aldous-Broder et comment rendre la grotte générée plus belle.
À la fin de la lecture de l'article, vous devriez obtenir quelque chose comme ceci:
Théorie
Montagne
Pour être honnête, la grotte peut être générée à partir de zéro, mais sera-t-elle en quelque sorte laide? Dans le rôle de
"plate-forme" pour le placement des mines, j'ai choisi une chaîne de montagnes.
Cette montagne est générée tout simplement: ayons un
tableau à deux dimensions et une
hauteur variable , initialement égale à la moitié de la longueur du
tableau dans la deuxième dimension; nous allons simplement parcourir ses colonnes et remplir quelque chose avec toutes les lignes de la colonne à une valeur de
hauteur variable , en la changeant avec une chance aléatoire vers le haut ou vers le bas.
Cave
Pour générer les donjons eux-mêmes, j'ai choisi -
il me semble - un excellent algorithme. En termes simples, cela peut être expliqué comme suit: même si nous avons deux (peut-être dix) variables
X et
Y , et un tableau bidimensionnel de 50 par 50, nous donnons à ces variables des valeurs aléatoires dans notre tableau, par exemple,
X = 26 et
Y = 28 . Après cela, nous faisons les mêmes actions plusieurs fois: nous obtenons un nombre aléatoire de zéro à
, dans notre cas, jusqu'à
quatre ; puis, en fonction du nombre d'abandons, nous changeons
nos variables:
switch (Random.Range(0, 4)) { case 0: X += 1; break; case 1: X -= 1; break; case 2: Y += 1; break; case 3: Y -= 1; break; }
Ensuite, bien sûr, nous vérifions si une variable est tombée en dehors des limites de notre champ:
X = X < 0 ? 0 : (X >= 50 ? 49 : X); Y = Y < 0 ? 0 : (Y >= 50 ? 49 : Y);
Après toutes ces vérifications, nous faisons quelque chose dans les nouvelles valeurs
X et
Y pour notre tableau
(par exemple: en ajouter une à l'élément) .
array[X, Y] += 1;
La préparation
Pour simplifier la mise en œuvre et la visualisation de nos méthodes, allons-nous dessiner les objets résultants? Je suis tellement content que ça ne te dérange pas! Nous le ferons avec
Texture2D .
Pour fonctionner, nous n'avons besoin que de deux scripts:
ground_libray est ce que l'article tournera autour. Ici, nous générons, nettoyons et dessinons
ground_generator est ce que notre ground_libray utilisera
Que le premier soit
statique et n'hérite de rien:
public static class ground_libray
Et le second est normal, seulement nous n'aurons pas besoin de la méthode
Update .
Créons également un objet de jeu sur scène, avec le composant
SpriteRendererPartie pratique
En quoi cela consiste-t-il?
Pour travailler avec des données, nous utiliserons un tableau à deux dimensions. Vous pouvez prendre un tableau de différents types, d'
octet ou d'
int , à
Color , mais je crois que ce sera mieux fait:
Nouveau typeNous écrivons cette chose dans
ground_libray .
[System.Serializable] public class block { public float[] color = new float[3]; public block(Color col) { color = new float[3] { col.r, col.g, col.b }; } }
Je vais expliquer cela par le fait que cela nous permettra à la fois de
sauvegarder notre tableau et de le
modifier si nécessaire.
Massif
Désignons, avant de commencer à générer la montagne, désigner l'endroit où nous allons la
stocker .
Dans le script
ground_generator, j'ai écrit ceci:
public int ground_size = 128; ground_libray.block[,] ground; Texture2D myT;
ground_size - la taille de notre champ (c'est-à-dire que le tableau sera composé de 16384 éléments).
ground_libray.block [,] ground - c'est notre domaine de génération.
Texture2D myT est ce sur quoi nous allons nous
appuyer .
Comment ça va fonctionner?Le principe de travail avec nous sera le suivant - nous appellerons quelques méthodes ground_libray de ground_generator , donnant au premier notre champ sol .
Créons la première méthode dans le script ground_libray:
Faire de la montagne public static float mount_noise = 0.02f; public static void generate_mount(ref block[,] b) { int h_now = b.GetLength(1) / 2; for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < h_now; y++) { b[x, y] = new block(new Color(0.7f, 0.4f, 0)); h_now += Random.value > (1.0f - mount_noise) ? (Random.value > 0.5 ? 1 : -1) : 0; } }
Et tout de suite, nous allons essayer de comprendre ce qui se passe ici: comme je l'ai dit, nous passons simplement sur les colonnes de notre tableau
b , en changeant en
même temps la variable de hauteur
h_now , qui était à l'origine égale à la moitié
128 (64) . Mais il y a encore quelque chose de nouveau -
mount_noise . Cette variable est responsable de la possibilité de changer
h_now , car si vous changez la hauteur très souvent, la montagne ressemblera à un
peigne .
La couleurJ'ai immédiatement mis une couleur légèrement brunâtre , que ce soit au moins une partie - à l'avenir, nous n'en aurons plus besoin.
Passons maintenant à
ground_generator et écrivons ceci dans la méthode
Start :
ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground);
Nous initialisons le
terrain variable
une fois qu'il a dû être fait .
Après, sans explication, envoyez-le à
ground_libray .
Nous avons donc généré la montagne.
Pourquoi ne puis-je pas voir ma montagne?
Tirons maintenant ce que nous avons!
Pour le dessin, nous écrirons la méthode suivante dans notre
ground_libray :
Dessin public static void paint(block[,] b, ref Texture2D t) { t = new Texture2D(b.GetLength(0), b.GetLength(1)); t.filterMode = FilterMode.Point; for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < b.GetLength(1); y++) { if (b[x, y] == null) { t.SetPixel(x, y, new Color(0, 0, 0, 0)); continue; } t.SetPixel(x, y, new Color( b[x, y].color[0], b[x, y].color[1], b[x, y].color[2] ) ); } t.Apply(); }
Ici, nous ne donnerons plus à quelqu'un notre domaine, nous n'en donnerons qu'une copie
(bien que, en raison du mot classe, nous en ayons donné un peu plus qu'une simple copie) . Nous donnerons également notre
Texture2D à cette méthode.
Les deux premières lignes: nous créons notre texture à la
taille du champ et
supprimons le filtrage .
Après cela, nous parcourons tout notre champ de tableau et où nous n'avons rien créé
(la classe doit être initialisée) - nous dessinons un carré vide, sinon, s'il n'est pas vide, nous dessinons ce que nous avons enregistré dans l'élément.
Et, bien sûr, une fois terminé, nous allons à
ground_generator et ajoutons ceci:
ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground);
Mais peu importe combien nous dessinons sur notre texture, dans le jeu, nous ne pouvons la voir qu'en plaçant cette toile sur quelque chose:
SpriteRenderer n'accepte
Texture2D nulle part , mais rien ne nous empêche de créer un
sprite à partir de cette texture -
Sprite.Create (
texture ,
rectangle avec les coordonnées du coin inférieur gauche et supérieur droit , les
coordonnées de l'axe ).
Ces lignes seront appelées les plus récentes, nous ajouterons le reste au-dessus de la méthode de
peinture !
Le mien
Maintenant, nous devons remplir nos champs de grottes aléatoires. Pour de telles actions, nous allons également créer une méthode distincte dans
ground_libray . Je voudrais expliquer immédiatement les paramètres de la méthode:
ref block[,] b - . int thick - int size - Color outLine -
Cave public static void make_cave(ref block[,] b, int thick, int size, Color outLine) { int xNow = Random.Range(0, b.GetLength(0)); int yNow = Random.Range(0, b.GetLength(1) / 2); for (int i = 0; i < size; i++) { b[xNow, yNow] = null; make_thick(ref b, thick, new int[2] { xNow, yNow }, outLine); switch (Random.Range(0, 4)) { case 0: xNow += 1; break; case 1: xNow -= 1; break; case 2: yNow += 1; break; case 3: yNow -= 1; break; } xNow = xNow < 0 ? 0 : (xNow >= b.GetLength(0) ? b.GetLength(0) - 1 : xNow); yNow = yNow < 0 ? 0 : (yNow >= b.GetLength(1) ? b.GetLength(1) - 1 : yNow); } }
Pour commencer, nous avons déclaré nos variables
X et
Y , mais je les ai juste appelées respectivement
xNow et
yNow .
Le premier, à savoir
xNow , obtient une valeur aléatoire de zéro à la taille du champ dans la première dimension.
Et le second -
yNow - obtient également une valeur aléatoire: de zéro au milieu du champ dans la deuxième dimension.
Pourquoi? Nous générons notre montagne à partir du milieu, la chance qu'elle atteigne le "plafond" n'est
pas grande . Sur cette base, je ne considère pas pertinent de générer des grottes dans l'air.
Après cela, une boucle va immédiatement, dont le nombre de ticks dépend du paramètre de
taille . Chaque fois que nous mettons à jour le champ aux
positions xNow et
yNow , et seulement ensuite nous les mettons à jour nous-mêmes
(les mises à jour du champ peuvent être mises à la fin - vous ne sentirez pas la différence)Il existe également une méthode
make_thick , dans les paramètres dont nous passons notre
champ , la
largeur du trait de la grotte , la
position actuelle de mise à jour de la grotte et la
couleur du trait :
AVC static void make_thick (ref block[,] b, int t, int[] start, Color o) { for (int x = (start[0] - t); x < (start[0] + t); x++) { if (x < 0 || x >= b.GetLength(0)) continue; for (int y = (start[1] - t); y < (start[1] + t); y++) { if (y < 0 || y >= b.GetLength(1)) continue; if (b[x, y] == null) continue; b[x, y] = new block(o); } } }
La méthode prend la coordonnée de
départ qui lui est transmise, et autour d'elle à distance
t repeint tous les blocs en couleur
o - tout est très simple!
Ajoutons maintenant cette ligne à notre
générateur de terrain :
ground_libray.make_cave(ref ground, 2, 10000, new Color(0.3f, 0.3f, 0.3f));
Vous pouvez installer le script
ground_generator en tant que composant sur notre objet et vérifier son fonctionnement!

En savoir plus sur les grottes ...- Pour créer plus de grottes, vous pouvez appeler la méthode make_cave plusieurs fois (utilisez une boucle)
- La modification du paramètre de taille n'augmente pas toujours la taille de la grotte, mais elle devient souvent plus grande
- En modifiant le paramètre épais , vous augmentez considérablement le nombre d'opérations:
si le paramètre est 3, alors le nombre de carrés dans un rayon de 3 sera 36 , donc avec la taille du paramètre = 40 000 , le nombre d'opérations sera 36 * 40 000 = 1440000
Correction de la grotte

Avez-vous remarqué que de ce point de vue, la grotte n'a pas le meilleur aspect? Trop de détails supplémentaires
(peut-être pensez-vous différemment) .
Pour nous débarrasser des inclusions de certains
# 4d4d4d, nous allons écrire cette méthode dans
ground_libray :
Plus propre public static void clear_caves(ref block[,] b) { for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < b.GetLength(1); y++) { if (b[x, y] == null) continue; if (solo(b, 2, 13, new int[2] { x, y })) b[x, y] = null; } }
Mais il sera difficile de comprendre ce qui se passe ici si vous ne savez pas ce que fait la fonction
solo :
static bool solo (block[,] b, int rad, int min, int[] start) { int cnt = 0; for (int x = (start[0] - rad); x <= (start[0] + rad); x++) { if (x < 0 || x >= b.GetLength(0)) continue; for (int y = (start[1] - rad); y <= (start[1] + rad); y++) { if (y < 0 || y >= b.GetLength(1)) continue; if (b[x, y] == null) cnt += 1; else continue; if (cnt >= min) return true; } } return false; }
Dans les paramètres de cette fonction, notre
champ , le
rayon de la vérification du point , le
«seuil de destruction» et les
coordonnées du point à contrôler doivent être présents.
Voici une explication détaillée de ce que fait cette fonction:
int cnt est le compteur du "seuil" actuel
Viennent ensuite deux cycles qui vérifient tous les points autour de celui dont les coordonnées sont passées pour commencer . S'il y a un point vide , alors nous en ajoutons un à cnt , lorsque nous atteignons le «seuil de destruction», nous renvoyons la vérité - le point est superflu . Sinon, on ne la touche pas.
J'ai défini le seuil de destruction à 13 points vides et le rayon de vérification est 2 (c'est-à-dire qu'il vérifiera 24 points, sans le central)
ExempleCelui-ci restera indemne, car il ne reste que
9 points vides.

Mais celui-ci n'a pas eu de chance - jusqu'à
14 points vides

Une brève description de l'algorithme:
nous parcourons tout le champ et vérifions tous les points pour voir s'ils sont nécessaires. Ensuite, nous ajoutons simplement la ligne suivante à notre
ground_generator :
ground_libray.clear_caves(ref ground);
Comme nous pouvons le voir, la plupart des particules inutiles ont tout simplement disparu.Ajoutez de la couleur
Notre montagne a l'air très monotone, je la trouve ennuyeuse.
Ajoutons de la couleur. Ajoutez la méthode
level_paint à
ground_libray :
Peinture sur les montagnes public static void level_paint(ref block[,] b, Color[] all_c) { for (int x = 0; x < b.GetLength(0); x++) { int lvl_div = -1; int counter = 0; int lvl_now = 0; for (int y = b.GetLength(1) - 1; y > 0; y--) { if (b[x, y] != null && lvl_div == -1) lvl_div = y / all_c.Length; else if (b[x, y] == null) continue; b[x, y] = new block(all_c[lvl_now]); lvl_now += counter >= lvl_div ? 1 : 0; lvl_now = (lvl_now >= all_c.Length) ? (all_c.Length - 1) : lvl_now; counter = counter >= lvl_div ? 0 : (counter += 1); } } } </ <cut />source> . , , . , . <b>Y </b> , . </spoiler> <b>ground_generator </b> : <source lang="cs"> ground_libray.level_paint(ref ground, new Color[3] { new Color(0.2f, 0.8f, 0), new Color(0.6f, 0.2f, 0.05f), new Color(0.2f, 0.2f, 0.2f), });
J'ai choisi seulement 3 couleurs:
vert ,
rouge foncé et
gris foncé .
Bien sûr, vous pouvez modifier à la fois le nombre de couleurs et les valeurs de chacune. Il s'est avéré comme ceci:
Mais ça a l'air trop strict pour ajouter un peu d'aléatoire aux couleurs, on va écrire cette propriété dans
ground_libray :
Couleurs aléatoires public static float color_randomize = 0.1f; static float crnd { get { return Random.Range(1.0f - color_randomize, 1.0f + color_randomize); } }
Et maintenant dans les
méthodes level_paint et
make_thick , dans les lignes où nous
assignons des couleurs, par exemple dans
make_thick :
b[x, y] = new block(o);
Nous écrirons ceci:
b[x, y] = new block(o * crnd);
Et dans
level_paint b[x, y] = new block(all_c[lvl_now] * crnd);
En fin de compte, tout devrait ressembler à ceci:
Inconvénients
Supposons que nous ayons un champ de 1024 par 1024, nous devons générer 24 grottes, dont l'épaisseur des bords sera de 4 et la taille est de 80 000.
1024 * 1024 + 24 * 64 * 80 000 = 5
368 832 000 000 opérations.
Cette méthode ne convient que pour générer de petits modules pour le monde du jeu, il est
impossible de générer quelque chose de très grand
à la fois .