Wenn Sie einen Bereich zufällig mit Objekten füllen, wie z. B. Räume in einem zufälligen Dungeon, laufen Sie Gefahr, Dinge zu erstellen auch zufällig, was zu einem Klumpen oder nur zu einem unbrauchbaren Durcheinander führt. In diesem Tutorial zeige ich Ihnen, wie Sie es verwenden Binäre Raumaufteilung um dieses Problem zu lösen.
Ich werde Sie durch einige allgemeine Schritte führen, um mit BSP eine einfache 2D-Karte zu erstellen, die für ein Dungeon-Layout für ein Spiel verwendet werden kann. Ich werde dir zeigen, wie man ein Basic macht Blatt
Objekt, das wir verwenden werden, um einen Bereich in kleine Segmente aufzuteilen; dann, wie man einen zufälligen Raum in jedem erzeugt Blatt
; und schließlich, wie man alle Räume mit Fluren verbindet.
Ich habe ein Demo-Programm erstellt, das die Leistungsfähigkeit von BSP demonstriert. Die Demo wurde mit Flixel geschrieben, einer kostenlosen Open-Source-AS3-Bibliothek zum Erstellen von Spielen.
Wenn Sie auf klicken Generieren Wenn Sie diese Taste drücken, wird derselbe Code wie oben ausgeführt, um einige zu generieren Blätter
, und zieht sie dann an BitmapData
Objekt, das dann angezeigt wird (vergrößert, um den Bildschirm auszufüllen).
Wenn Sie auf die abspielen Mit der Schaltfläche wird die erzeugte Karte übergeben Bitmap
rüber zum FlxTilemap
object, das dann eine abspielbare Tilemap generiert und auf dem Bildschirm anzeigt, auf der Sie sich bewegen können:
Benutze die Pfeiltasten zur Bewegung.
Binary Space Partitioning ist eine Methode zur Unterteilung eines Bereichs in kleinere Teile.
Grundsätzlich nehmen Sie ein Gebiet, genannt a Blatt
, und teilen Sie es - entweder vertikal oder horizontal - in zwei kleinere Blätter, und wiederholen Sie dann den Vorgang an den kleineren Bereichen immer wieder, bis jeder Bereich mindestens so klein ist wie ein festgelegter Maximalwert.
Wenn Sie fertig sind, haben Sie eine Hierarchie der Partitionierung Blätter
, mit denen du alle möglichen Dinge tun kannst. In 3D-Grafiken können Sie mit BSP sortieren, welche Objekte für den Player sichtbar sind, oder bei der Kollisionserkennung in kleineren, mundgerechten Teilen helfen.
Wenn Sie eine zufällige Karte erstellen möchten, gibt es verschiedene Möglichkeiten, dies zu tun. Sie können eine einfache Logik zum Erstellen von zufällig großen Rechtecken an zufälligen Positionen schreiben. Dies kann jedoch zu Landkarten führen, die voll von sich überlappenden, zusammengefügten oder seltsam beabstandeten Räumen sind. Es ist auch etwas schwieriger, die Räume miteinander zu verbinden und sicherzustellen, dass keine verwaisten Räume vorhanden sind.
Mit BSP können Sie gleichmäßigere Räume garantieren und gleichzeitig sicherstellen, dass Sie alle Räume miteinander verbinden können.
Das erste, was wir brauchen, ist unser zu schaffen Blatt
Klasse. Grundsätzlich unser Blatt
wird ein Rechteck mit zusätzlichen Funktionen sein. Jeder Blatt
wird entweder ein paar Kinder enthalten Blätter
, oder ein Paar von Räume
, sowie ein oder zwei Flure.
Hier ist was unser Blatt
sieht aus wie:
öffentliche Klasse Leaf private const MIN_LEAF_SIZE: uint = 6; public var y: int, x: int, Breite: int, Höhe: int; // die Position und Größe dieses Blattes public var leftChild: Leaf; // das linke Kind des Blattes Leaf public var rightChild: Leaf; // das rechte Kind des Blattes Leaf public var room: Rectangle; // der Raum, der sich in dieser öffentlichen Halle von Leaf befindet: Vector .; // Flure, um dieses Blatt mit anderen öffentlichen Funktionen von Leafs zu verbinden Blatt (X: int, Y: int, Breite: int, Höhe: int) // initialisiert unser Blatt x = X; y = Y; Breite = Breite; Höhe = Höhe; public function split (): Boolean // fängt an, das Blatt in zwei Kinder aufzuteilen, wenn (leftChild! = null || rightChild! = null) false zurückgibt; // wir sind schon gespalten! Abbrechen! // Richtung der Aufteilung bestimmen // Wenn die Breite> 25% größer als die Höhe ist, teilen wir uns vertikal auf // Wenn die Höhe> 25% größer ist als die Breite, teilen wir uns horizontal auf // Ansonsten teilen wir uns willkürlich auf: splitH: Boolean = FlxG.random ()> 0,5; if (width> height && width / height> = 1,25) splitH = false; sonst if (height> width && height / width> = 1,25) splitH = true; var max: int = (splitH? height: width) - MIN_LEAF_SIZE; // Bestimmen Sie die maximale Höhe oder Breite wenn (max <= MIN_LEAF_SIZE) return false; // the area is too small to split any more… var split:int = Registry.randomNumber(MIN_LEAF_SIZE, max); // determine where we're going to split // create our left and right children based on the direction of the split if (splitH) leftChild = new Leaf(x, y, width, split); rightChild = new Leaf(x, y + split, width, height - split); else leftChild = new Leaf(x, y, split, height); rightChild = new Leaf(x + split, y, width - split, height); return true; // split successful!
Nun müssen Sie tatsächlich Ihre erstellen Blätter
:
const MAX_LEAF_SIZE: uint = 20; var _leafs: Vektor= neuer Vektor ; Var l: Blatt; // helfer Blatt // erstelle ein Blatt als 'Wurzel' aller Blätter. var root: Blatt = neues Blatt (0, 0, _sprMap.width, _sprMap.height); _leafs.push (Wurzel); var did_split: Boolean = true; // wir durchlaufen jedes Blatt in unserem Vector immer wieder, bis keine Blätter mehr geteilt werden können. while (did_split) did_split = falsch; für jedes (l in _leafs) if (l.leftChild == null && l.rightChild == null) // wenn dieses Blatt nicht bereits geteilt ist… // wenn dieses Blatt zu groß ist, oder eine Chance von 75%… wenn (l.width> MAX_LEAF_SIZE || l.height> MAX_LEAF_SIZE || FlxG.random ()> 0,25) if (l.split ()) // das Blatt teilen! // Wenn wir geteilt haben, schieben Sie die untergeordneten Blätter in den Vektor, damit wir als nächstes in die Schleife springen können. _leafs.push (l.leftChild); _leafs.push (l.rightChild); did_split = true;
Nachdem diese Schleife beendet ist, werden Sie mit einem Vektor
(ein typisiertes Array) voll von all Ihren Blätter
.
Hier ist ein Beispiel, bei dem die Zeilen voneinander getrennt sind Blatt
:
Nun das dein Blätter
definiert sind, müssen wir die Räume machen. Wir wollen eine Art "Rieseleffekt", bei dem wir von unserer größten "Wurzel" gehen Blatt
bis zum kleinsten Blätter
ohne Kinder, und dann machen Sie in jedem von ihnen ein Zimmer.
Fügen Sie diese Funktion also Ihrem hinzu Blatt
Klasse:
public function createRooms (): void // Diese Funktion generiert alle Räume und Flure für dieses Blatt und alle seine Kinder. if (leftChild! = null || rightChild! = null) // Dieses Blatt wurde geteilt. Gehen Sie also in die untergeordneten Blätter, wenn (leftChild! = null) leftChild.createRooms (); if (rightChild! = null) rightChild.createRooms (); else // Dieses Blatt ist bereit, einen Raum zu erstellen. var roomSize: Point; var roomPos: Punkt; // der Raum kann zwischen 3 x 3 Kacheln bis zur Größe des Blattes sein - 2. roomSize = new Point (Registry.randomNumber (3, width - 2), Registry.randomNumber (3, height - 2)); // Platziere den Raum innerhalb des Blattes, aber lege ihn nicht richtig // gegen die Seite des Blattes (wodurch Räume zusammengefügt werden) roomPos = new Point (Registry.randomNumber (1, Breite - roomSize.x - 1)) , Registry.randomNumber (1, height - roomSize.y - 1)); room = new Rectangle (x + roomPos.x, y + roomPos.y, roomSize.x, roomSize.y);
Dann, nachdem Sie Ihre erstellt haben Vektor
von Blätter
, Rufen Sie unsere neue Funktion von Ihrem Stamm auf Blatt
:
_leafs = neuer Vektor; Var l: Blatt; // helfer Blatt // erstelle ein Blatt als 'Wurzel' aller Blätter. var root: Blatt = neues Blatt (0, 0, _sprMap.width, _sprMap.height); _leafs.push (Wurzel); var did_split: Boolean = true; // wir durchlaufen jedes Blatt in unserem Vector immer wieder, bis keine Blätter mehr geteilt werden können. while (did_split) did_split = falsch; für jedes (l in _leafs) if (l.leftChild == null && l.rightChild == null) // wenn dieses Blatt nicht bereits geteilt ist… // wenn dieses Blatt zu groß ist, oder eine Chance von 75% ... (l.width> MAX_LEAF_SIZE || l.height> MAX_LEAF_SIZE || FlxG.random ()> 0,25) if (l.split ()) // das Blatt teilen! // Wenn wir uns geteilt haben, schieben Sie die untergeordneten Blätter in den Vektor, damit wir als nächstes in sie hineinlaufen können. _leafs.push (l.leftChild); _leafs.push (l.rightChild); did_split = true; // Als nächstes durchlaufen Sie jedes Blatt und erstellen einen Raum in jedem. root.createRooms ();
Hier ist ein Beispiel für einige generierte Blätter
mit Räumen in ihnen:
Wie Sie sehen können, jeder Blatt
enthält einen Raum mit zufälliger Größe und Position. Sie können mit den Werten für Minimum und Maximum spielen Blatt
Größe und ändern Sie, wie Sie die Größe und Position jedes Raums bestimmen, um unterschiedliche Effekte zu erzielen.
Wenn wir unsere entfernen Blatt
Trennlinien können Sie sehen, dass die Räume die gesamte Karte gut ausfüllen - es gibt nicht viel Platzverschwendung - und sie wirken ein bisschen organischer.
Blätter
mit einem Raum in jedem Raum, wobei Trennlinien entfernt wurden. Jetzt müssen wir nur noch jeden Raum miteinander verbinden. Zum Glück haben wir die eingebauten Beziehungen zwischen Blätter
, Wir müssen nur sicherstellen, dass jeder Blatt
das hat Kind Blätter
hat einen Flur, der seine Kinder verbindet.
Wir nehmen eine Blatt
, Schauen Sie sich jedes seiner Kinder an Blätter
, gehen Sie den ganzen Weg durch jedes Kind bis wir zu einem kommen Blatt
mit einem Raum und verbinden Sie dann die Räume miteinander. Wir können dies gleichzeitig tun, wenn wir unsere Räume generieren.
Erstens brauchen wir eine neue Funktion, um von jeder zu iterieren Blatt
in einen der Räume, die sich in einem der Kinder befinden Blätter
:
public function getRoom (): Rectangle // Durchlaufe alle Blätter, um einen Raum zu finden, falls vorhanden. if (room! = null) Rückführungsraum; else var lRoom: Rechteck; var rRoom: Rechteck; if (leftChild! = null) lRoom = leftChild.getRoom (); if (rightChild! = null) rRoom = rightChild.getRoom (); if (lRoom == null && rRoom == null) gibt null zurück; else if (rRoom == null) gibt lRoom zurück; sonst if (lRoom == null) return rRoom; else if (FlxG.random ()> .5) return lRoom; sonst Rückkehr rRoom;
Als Nächstes benötigen wir eine Funktion, die ein Raumpaar benötigt, einen zufälligen Punkt in beiden Räumen auswählt und dann entweder ein oder zwei Rechtecke mit zwei Kacheln erstellt, um die Punkte miteinander zu verbinden.
public function createHall (l: Rechteck, r: Rechteck): void // jetzt verbinden wir diese beiden Räume mit Fluren. // Das sieht ziemlich kompliziert aus, aber es versucht nur herauszufinden, wo sich der Punkt befindet, und dann entweder eine gerade Linie oder ein Linienpaar zu zeichnen, um einen rechten Winkel zu bilden, um sie zu verbinden. // Sie könnten eine zusätzliche Logik verwenden, um Ihre Hallen biegsamer zu gestalten, oder einige fortgeschrittenere Dinge tun, wenn Sie dies wünschen. Hallen = neuer Vektor; var point1: Punkt = neuer Punkt (Registry.randomNumber (l.lft + 1, l.right - 2), Registry.randomNumber (l.top + 1, l.bottom - 2)); var point2: Punkt = neuer Punkt (Registry.randomNumber (r.left + 1, r.right - 2), Registry.randomNumber (r.top + 1, r.bottom - 2)); var w: Number = Punkt 2.x - Punkt 1.x; var h: Zahl = Punkt 2.y - Punkt 1.y; wenn (w < 0) if (h < 0) if (FlxG.random() < 0.5) halls.push(new Rectangle(point2.x, point1.y, Math.abs(w), 1)); halls.push(new Rectangle(point2.x, point2.y, 1, Math.abs(h))); else halls.push(new Rectangle(point2.x, point2.y, Math.abs(w), 1)); halls.push(new Rectangle(point1.x, point2.y, 1, Math.abs(h))); else if (h > 0) if (FlxG.random () < 0.5) halls.push(new Rectangle(point2.x, point1.y, Math.abs(w), 1)); halls.push(new Rectangle(point2.x, point1.y, 1, Math.abs(h))); else halls.push(new Rectangle(point2.x, point2.y, Math.abs(w), 1)); halls.push(new Rectangle(point1.x, point1.y, 1, Math.abs(h))); else // if (h == 0) halls.push(new Rectangle(point2.x, point2.y, Math.abs(w), 1)); else if (w > 0) if (h < 0) if (FlxG.random() < 0.5) halls.push(new Rectangle(point1.x, point2.y, Math.abs(w), 1)); halls.push(new Rectangle(point1.x, point2.y, 1, Math.abs(h))); else halls.push(new Rectangle(point1.x, point1.y, Math.abs(w), 1)); halls.push(new Rectangle(point2.x, point2.y, 1, Math.abs(h))); else if (h > 0) if (FlxG.random () < 0.5) halls.push(new Rectangle(point1.x, point1.y, Math.abs(w), 1)); halls.push(new Rectangle(point2.x, point1.y, 1, Math.abs(h))); else halls.push(new Rectangle(point1.x, point2.y, Math.abs(w), 1)); halls.push(new Rectangle(point1.x, point1.y, 1, Math.abs(h))); else // if (h == 0) halls.push(new Rectangle(point1.x, point1.y, Math.abs(w), 1)); else // if (w == 0) if (h < 0) halls.push(new Rectangle(point2.x, point2.y, 1, Math.abs(h))); else if (h > 0) halls.push (neues Rechteck (Punkt 1.x, Punkt 1.j, 1, Math.abs (h)));
Schließlich ändern Sie Ihre createRooms ()
Funktion zum Aufrufen der createHall ()
Funktion auf jedem Blatt
das hat ein paar kinder:
public function createRooms (): void // Diese Funktion generiert alle Räume und Flure für dieses Blatt und alle seine Kinder. if (leftChild! = null || rightChild! = null) // Dieses Blatt wurde geteilt. Gehen Sie also in die untergeordneten Blätter, wenn (leftChild! = null) leftChild.createRooms (); if (rightChild! = null) rightChild.createRooms (); // Wenn sich in diesem Blatt sowohl linke als auch rechte Kinder befinden, erstellen Sie einen Flur zwischen if (leftChild! = null && rightChild! = null) createHall (leftChild.getRoom (), rightChild.getRoom ()); else // Dieses Blatt ist bereit, einen Raum zu erstellen. var roomSize: Point; var roomPos: Punkt; // der Raum kann zwischen 3 x 3 Kacheln bis zur Größe des Blattes sein - 2. roomSize = new Point (Registry.randomNumber (3, width - 2), Registry.randomNumber (3, height - 2)); // Platziere den Raum innerhalb des Blattes, aber lege ihn nicht direkt an die Seite des Blattes (dies würde Räume zusammenfügen) roomPos = new Point (Registry.randomNumber (1, Breite - roomSize.x - 1), Registry .andomNumber (1, height - roomSize.y - 1)); room = new Rectangle (x + roomPos.x, y + roomPos.y, roomSize.x, roomSize.y);
Ihre Räume und Flure sollten jetzt etwa so aussehen:
Blätter
gefüllt mit zufälligen Räumen, die über Flure miteinander verbunden sind. Wie Sie sehen können, stellen wir sicher, dass wir alle miteinander verbinden Blatt
, Wir haben keine verwaisten Zimmer mehr. Offensichtlich könnte die Flurlogik etwas verfeinert werden, um zu vermeiden, zu nahe an anderen Fluren zu laufen, aber sie funktioniert gut genug.
Das ist es im Grunde! Wir haben beschrieben, wie Sie ein (relativ) einfaches erstellen Blatt
Objekt, mit dem Sie einen Baum aus unterteilten Blättern erzeugen können, erzeugen Sie einen zufälligen Raum in jedem Blatt
, und verbinden Sie die Räume über Flure.
Derzeit sind alle Objekte, die wir erstellt haben, im Wesentlichen Rechtecke. Abhängig davon, wie Sie den resultierenden Dungeon verwenden möchten, können Sie sie auf verschiedene Arten handhaben.
Jetzt können Sie BSP verwenden, um beliebige Arten von zufälligen Karten zu erstellen, oder es verwenden, um Power-Ups oder Feinde gleichmäßig über ein Gebiet zu verteilen ... oder was immer Sie möchten!