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.