Vorwort
Wenn Sie zu 
faul sind, um sich um Ihre Zeit 
zu kümmern und ein Level für Ihr Spiel zu erstellen, sind Sie bei uns genau richtig.
In diesem Artikel erfahren Sie ausführlich, wie Sie am Beispiel von Hochland und Höhlen eine von 
vielen anderen Generierungsmethoden anwenden können. Wir werden den 
Aldous-Broder- Algorithmus betrachten und wie man die erzeugte Höhle schöner macht.
Am Ende des Lesens des Artikels sollten Sie ungefähr Folgendes erhalten:
Theorie
Berg
Um ehrlich zu sein, kann die Höhle von Grund auf neu erstellt werden, aber wird sie irgendwie hässlich sein? In der Rolle der 
"Plattform" für die Platzierung von Minen habe ich eine Bergkette gewählt.
Dieser Berg wird ganz einfach erzeugt: Lassen Sie uns ein 
zweidimensionales Array und eine 
variable Höhe haben , die anfänglich der halben Länge des 
Arrays in der zweiten Dimension entspricht; Wir gehen einfach die Spalten durch und füllen etwas mit allen Zeilen in der Spalte auf einen 
variablen Höhenwert aus, wobei wir ihn mit einer zufälligen Chance nach oben oder unten ändern.
Höhle
Um die Dungeons selbst zu generieren, habe ich - 
wie mir schien - einen hervorragenden Algorithmus gewählt. In einfachen Worten kann dies wie folgt erklärt werden: Selbst wenn wir zwei (vielleicht zehn) Variablen 
X und 
Y und ein zweidimensionales Array von 50 mal 50 haben, geben wir diesen Variablen Zufallswerte innerhalb unseres Arrays, zum Beispiel 
X = 26 und 
Y = 28 . Danach führen wir die gleichen Aktionen mehrmals aus: Wir erhalten eine Zufallszahl von Null bis
AnzahlderVariablen∗2
in unserem Fall bis zu 
vier ; und dann ändern wir uns abhängig von der Anzahl der abgebrochenen Personen
unsere Variablen:
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; } 
Dann prüfen wir natürlich, ob eine Variable außerhalb der Grenzen unseres Feldes liegt:
  X = X < 0 ? 0 : (X >= 50 ? 49 : X); Y = Y < 0 ? 0 : (Y >= 50 ? 49 : Y); 
Nach all diesen Überprüfungen tun wir etwas in den neuen 
X- und 
Y- Werten für unser Array 
(zum Beispiel: Fügen Sie dem Element einen hinzu) .
 array[X, Y] += 1; 
Vorbereitung
Zeichnen wir zur Vereinfachung der Implementierung und Visualisierung unserer Methoden die resultierenden Objekte? Ich bin so froh, dass es dir nichts ausmacht! Wir werden dies mit 
Texture2D tun.
Zum Arbeiten benötigen wir nur zwei Skripte:
Bei ground_libray dreht sich der Artikel. Hier erzeugen und reinigen und zeichnen wir
ground_generator ist das, was unser ground_libray verwenden wird
Lassen Sie den ersten 
statisch sein und erben Sie nichts:
 public static class ground_libray 
Und die zweite ist normal, nur brauchen wir die 
Update- Methode nicht.
Lassen Sie uns außerdem mit der 
SpriteRenderer- Komponente ein Spielobjekt auf der Bühne 
erstellenPraktischer Teil
Woraus besteht es?
Um mit Daten zu arbeiten, verwenden wir ein zweidimensionales Array. Sie können eine Reihe verschiedener Typen verwenden, von 
Byte oder 
Int bis hin zu 
Farbe , aber ich glaube, dass dies am besten möglich ist:
Neuer TypWir schreiben dieses Ding in 
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 }; } } 
 Ich werde dies durch die Tatsache erklären, dass wir damit sowohl unser Array 
speichern als auch es bei Bedarf 
ändern können.
Massiv
Bevor wir mit der Erzeugung des Berges beginnen, bestimmen wir den Ort, an dem wir ihn 
lagern werden .
Im Skript 
ground_generator habe ich 
Folgendes geschrieben:
  public int ground_size = 128; ground_libray.block[,] ground; Texture2D myT; 
ground_size - die Größe unseres Feldes ( 
dh das Array besteht aus 16384 Elementen).
ground_libray.block [,] ground - das ist unser Feld für die Erzeugung.
Texture2D myT ist das, worauf wir 
zurückgreifen werden.
Wie wird es funktionieren?Das Prinzip der Arbeit mit uns wird wie folgt sein - wir werden einige ground_libray- Methoden von ground_generator aufrufen und dem ersten unser Bodenfeld geben.
 Erstellen wir die erste Methode im Skript ground_libray:
Bergbau  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; } } 
Und sofort werden wir versuchen zu verstehen, was hier passiert: Wie gesagt, wir gehen einfach die Spalten unseres Arrays 
b durch und ändern gleichzeitig die Höhenvariable 
h_now , die ursprünglich gleich der Hälfte 
128 (64) war . Aber es gibt noch etwas Neues - 
mount_noise . Diese Variable ist für die Möglichkeit 
verantwortlich , 
h_now zu ändern, denn wenn Sie die Höhe sehr oft ändern, 
sieht der Berg wie ein 
Kamm aus .
 FarbeIch habe sofort eine leicht bräunliche Farbe eingestellt, lass es zumindest einige sein - in Zukunft werden wir sie nicht brauchen.
 Gehen 
wir nun zu 
ground_generator und schreiben dies in die 
Start- Methode:
  ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground); 
Wir initialisieren den variablen 
Boden, sobald dies erforderlich ist .
Senden Sie es 
anschließend ohne Erklärung an 
ground_libray .
Also haben wir den Berg erzeugt.
Warum kann ich meinen Berg nicht sehen?
Zeichnen wir jetzt, was wir haben!
Zum Zeichnen schreiben wir die folgende Methode in unser 
ground_libray :
Zeichnen  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(); } 
Hier geben wir niemandem mehr unser Feld, wir geben nur eine Kopie davon 
(obwohl wir aufgrund der Wortklasse etwas mehr als nur eine Kopie gegeben haben) . Wir werden dieser Methode auch unsere 
Texture2D geben.
Die ersten beiden Zeilen: Wir erstellen unsere Textur in der 
Größe des Feldes und 
entfernen die Filterung .
Danach gehen wir unser gesamtes Array-Feld durch und haben nichts erstellt 
(die Klasse muss initialisiert werden) - wir zeichnen ein leeres Feld, andernfalls, wenn es nicht leer ist - zeichnen wir das, was wir gespeichert haben, in das Element.
 Und wenn wir fertig sind, gehen wir 
natürlich zu 
ground_generator und fügen 
Folgendes hinzu:
  ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground);  
Aber egal wie viel wir auf unsere Textur zeichnen, im Spiel können wir es nur sehen, indem wir diese Leinwand auf etwas legen:
SpriteRenderer akzeptiert 
Texture2D nirgendwo , aber nichts hindert uns daran, aus dieser Textur ein 
Sprite zu erstellen - 
Sprite.Create ( 
Textur , 
Rechteck mit den Koordinaten der unteren linken Ecke und der oberen rechten Ecke , der 
Koordinate der Achse ).
Diese Linien werden als die neuesten bezeichnet, wir werden den Rest über der 
Malmethode hinzufügen!
Meins
Jetzt müssen wir unsere Felder mit zufälligen Höhlen füllen. Für solche Aktionen erstellen wir auch eine separate Methode in 
ground_libray . Ich möchte sofort die Parameter der Methode erklären:
 ref block[,] b -     . int thick -    int size -         Color outLine -   
Höhle  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); } } 
Zunächst haben wir unsere Variablen 
X und 
Y deklariert, aber ich habe sie nur 
xNow bzw. 
yNow genannt .
Das erste, nämlich 
xNow , erhält einen zufälligen Wert von Null bis zur Größe des Feldes in der ersten Dimension.
Und das zweite - 
yNow - erhält ebenfalls einen zufälligen Wert: von Null bis zur Mitte des Feldes in der zweiten Dimension. 
Warum? Wir erzeugen unseren Berg aus der Mitte, die Chance, dass er bis zur "Decke" wächst, ist 
nicht groß . Aus diesem Grund halte ich es nicht für relevant, Höhlen in der Luft zu erzeugen.
Danach geht sofort eine Schleife, deren Anzahl von Ticks vom Größenparameter abhängt. Bei jedem Tick aktualisieren wir das Feld an den 
Positionen xNow und 
yNow und erst dann aktualisieren wir sie selbst 
( Feldaktualisierungen können am Ende vorgenommen werden - Sie werden den Unterschied nicht spüren).Es gibt auch eine 
make_thick- Methode, in deren Parametern wir unser 
Feld übergeben , die 
Breite des Höhlenstrichs , die 
aktuelle Aktualisierungsposition der Höhle und die 
Farbe des Strichs :
Schlaganfall  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); } } } 
Die Methode verwendet die an sie übergebene Startkoordinate und malt in einem Abstand 
t alle Blöcke in der Farbe 
o neu - alles ist sehr einfach!
  Fügen wir 
nun diese Zeile zu unserem 
ground_generator hinzu :
 ground_libray.make_cave(ref ground, 2, 10000, new Color(0.3f, 0.3f, 0.3f)); 
Sie können das Skript 
ground_generator als Komponente auf unserem Objekt installieren und überprüfen, wie es funktioniert!

Mehr über die Höhlen ...- Um mehr Höhlen zu erstellen , können Sie die Methode make_cave mehrmals aufrufen (verwenden Sie eine Schleife).
- Durch Ändern des Größenparameters wird die Höhle nicht immer vergrößert, sie wird jedoch häufig größer
- Durch Ändern des Thick- Parameters erhöhen Sie die Anzahl der Operationen erheblich:
 Wenn der Parameter 3 ist, beträgt die Anzahl der Quadrate in einem Radius von 3 36 , sodass bei einer Parametergröße von 40.000 die Anzahl der Operationen 36 * 40.000 = 1440000 beträgt
 Höhlenkorrektur

Haben Sie bemerkt, dass die Höhle in dieser Ansicht nicht besonders gut aussieht? Zu viele zusätzliche Details 
(vielleicht denken Sie anders) .
Um Einschlüsse von 
# 4d4d4d loszuwerden, schreiben wir diese Methode in 
ground_libray :
Reiniger  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; } } 
Es wird jedoch schwierig sein zu verstehen, was hier vor sich geht, wenn Sie nicht wissen, was die 
Solofunktion bewirkt:
  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; } 
In den Parametern dieser Funktion müssen unser 
Feld , der 
Radius der Punktüberprüfung , die 
„Zerstörungsschwelle“ und die 
Koordinaten des zu überprüfenden Punktes vorhanden sein.
Hier ist eine detaillierte Erklärung, was diese Funktion tut:
int cnt ist der Zähler der aktuellen "Schwelle"
Als nächstes folgen zwei Zyklen, die alle Punkte um den einen herum überprüfen, dessen Koordinaten zum Starten übergeben werden . Wenn es einen leeren Punkt gibt , fügen wir einen zu cnt hinzu. Wenn wir die "Schwelle der Zerstörung" erreichen, geben wir die Wahrheit zurück - der Punkt ist überflüssig . Ansonsten berühren wir sie nicht.
Ich habe die Zerstörungsschwelle auf 13 leere Punkte festgelegt, und der Überprüfungsradius beträgt 2 (dh es werden 24 Punkte überprüft, ohne den zentralen Punkt).
BeispielDieser bleibt unversehrt, da es nur 
9 leere Punkte gibt.

Aber dieser hatte kein Glück - ungefähr 
14 leere Punkte

 Eine kurze Beschreibung des Algorithmus: 
Wir gehen das gesamte Feld durch und überprüfen alle Punkte, um festzustellen, ob sie benötigt werden. Als nächstes fügen wir einfach die folgende Zeile zu unserem 
ground_generator hinzu :
 ground_libray.clear_caves(ref ground); 
Wie wir sehen können, gingen die meisten unnötigen Partikel einfach weg.Fügen Sie etwas Farbe hinzu
Unser Berg sieht sehr eintönig aus, ich finde es langweilig.
Fügen wir etwas Farbe hinzu. Fügen Sie die Methode 
level_paint zu 
ground_libray hinzu :
Über die Berge malen  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), }); 
Ich habe nur 3 Farben gewählt: 
Grün , 
Dunkelrot und 
Dunkelgrau .
Natürlich können Sie sowohl die Anzahl der Farben als auch die Werte der einzelnen Farben ändern. Es stellte sich so heraus:
Trotzdem sieht es zu streng aus, um den Farben ein wenig Zufälligkeit zu verleihen. Wir werden diese Eigenschaft in 
ground_libray schreiben:
Zufällige Farben  public static float color_randomize = 0.1f; static float crnd { get { return Random.Range(1.0f - color_randomize, 1.0f + color_randomize); } } 
Und jetzt in den 
Methoden level_paint und 
make_thick , in den Zeilen, in denen wir Farben zuweisen, zum Beispiel in 
make_thick :
 b[x, y] = new block(o); 
Wir werden dies schreiben:
 b[x, y] = new block(o * crnd); 
Und in 
level_paint b[x, y] = new block(all_c[lvl_now] * crnd); 
 Am Ende sollte alles ungefähr so aussehen:
 Nachteile
Angenommen, wir haben ein Feld von 1024 mal 1024, wir müssen 24 Höhlen erzeugen, deren Kantenstärke 4 beträgt und deren Größe 80.000 beträgt.
1024 * 1024 + 24 * 64 * 80.000 = 
5.368.832.000.000 Operationen.
Diese Methode eignet sich nur zum Generieren kleiner Module für die Spielwelt. Es ist 
unmöglich, jeweils etwas sehr Großes 
zu generieren.