Atunci când umpleți o zonă aleatoriu cu obiecte, cum ar fi camere într-o temniță aleatorie, riscați să faceți lucruri de asemenea aleatoriu, ducând la aglomerare sau doar la o mizerie inutilizabilă. În acest tutorial, vă voi arăta cum să utilizați Partiționarea spațială binară Pentru a rezolva această problemă.
Vă voi conduce prin niște pași generali pentru a utiliza BSP pentru a face o hartă simplă, 2D, care ar putea fi folosită pentru un aspect de dungeon pentru un joc. Vă voi arăta cum să faceți un lucru de bază Frunze
obiect, pe care îl vom folosi pentru a împărți o zonă în segmente mici; apoi, cum să generați o cameră aleatoare în fiecare Frunze
; și, în sfârșit, cum să conectați toate camerele împreună cu holurile.
Am creat un program demo care arată unele dintre puterea BSP. Demo-ul este scris folosind Flixel, o librărie liberă, open-source AS3 pentru jocuri.
Când faceți clic pe Genera , trece prin același cod ca mai sus pentru a genera unele Leafs
, și apoi le atrage spre a BitmapData
obiect, pe care apoi îl afișează (scalat pentru a umple ecranul).
Când ați lovit Joaca buton, acesta trece harta generata Bitmap
peste la FlxTilemap
obiect, care generează apoi o tabelă tactilă și o afișează pe ecran pentru a vă plimba în:
Utilizați tastele săgeată pentru a vă deplasa.
Partiționarea spațială binară este o metodă de împărțire a unei zone în bucăți mai mici.
Practic, luați o zonă, numită a Frunze
, și împărțiți - fie pe verticală, fie pe orizontală - în două Leafuri mai mici, apoi repetați procesul pe suprafețele mai mici din nou și până când fiecare zonă este cel puțin la fel de mică ca o valoare maximă setată.
Când ați terminat, aveți o ierarhie de partiții Leafs
, cu care puteți face tot felul de lucruri. În graficele 3D, este posibil să utilizați BSP pentru a sorta ce obiecte sunt vizibile pentru player sau pentru a ajuta la detectarea coliziunilor în bucăți mai mici și mai mici.
Dacă doriți să generați o hartă aleatorie, există tot felul de moduri de abordare. S-ar putea să scrieți o logică simplă pentru a crea dreptunghiuri de dimensiune aleatorie în locații aleatorii, dar acest lucru vă poate lăsa cu hărți care sunt pline de camere suprapuse, aglomerate sau strâns distanțate. De asemenea, este mult mai dificil să se conecteze camerele între ele și să se asigure că nu există încă camere orfane.
Cu BSP, puteți garanta camere mai spațioase, asigurându-vă, în același timp, că puteți conecta toate camerele împreună.
Primul lucru de care avem nevoie este să ne creăm Frunze
clasă. Practic, nostru Frunze
va fi un dreptunghi, cu unele funcționalități suplimentare. Fiecare Frunze
va conține fie o pereche de copii Leafs
, sau o pereche de camere
, precum și un hol sau două.
Iată ce e al nostru Frunze
se pare ca:
clasa publică Leaf private const MIN_LEAF_SIZE: uint = 6; public var y: int, x: int, lățime: int, înălțime: int; // Poziția și mărimea acestui Leaf public var varChild: Leaf; // Copilul stâng al Leafului Leaf public var rightChild: Leaf; // Leaf-ul copilului drept Leaf public var room: Rectangle; // camera care se află în interiorul acestei săli publice publice de var: Vector; // holuri pentru a conecta această frunză la alte frunze funcționale publice Leaf (X: int, Y: int, Lățime: int, Înălțimea: int) // inițializați frunza x = X; y = Y; width = Width; inaltime = inaltime; funcția publică split (): Boolean // începe să împartă frunza în doi copii dacă (leftChild! = null || rightChild! = null) return false; // suntem deja împărțiți! Abandonați! // determina direcția împărțirii // dacă lățimea este> 25% mai mare decât înălțimea, divizăm vertical // dacă înălțimea este> 25% mai mare decât lățimea, divizată orizontal // altfel am împărțit întâmplător var splitH: Boolean = FlxG.random ()> 0,5; dacă (lățime> înălțime && lățime / înălțime> = 1,25) splitH = false; altfel dacă (înălțime> lățime && înălțime / lățime> = 1,25) splitH = adevărat; var max: int = (înălțimea lui splitH: lățime) - MIN_LEAF_SIZE; // determinați înălțimea sau lățimea maximă dacă (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!
Acum, trebuie să vă creați de fapt Leafs
:
const MAX_LEAF_SIZE: uint = 20; var _leafs: Vector= Vector nou ; var l: frunze; // helper Leaf // în primul rând, creați un Leaf ca fiind "rădăcina" tuturor Leaf-urilor. vară rădăcină: Leaf = foaie nouă (0, 0, _sprMap.width, _sprMap.height); _leafs.push (root); var did_split: Boolean = adevărat; // ne batem prin fiecare frunză în Vectorul nostru din nou și din nou, până când nu mai pot fi împărțite frunze. în timp ce (did_split) did_split = false; pentru fiecare (l în _leafs) if (l.leftChild == null && l.rightChild == null) // dacă acest Leaf nu este deja divizat ... // dacă această frunză este prea mare sau 75% șansă ... dacă (l.width> MAX_LEAF_SIZE || l.height> MAX_LEAF_SIZE || FlxG.random ()> 0.25) dacă (l.split ()) // împărțiți frunza! // dacă am împărțit-o, împingem frunzele copilului la Vector, astfel încât să putem intra în ele în continuare _leafs.push (l.leftChild); _leafs.push (l.rightChild); did_split = true;
După ce această buclă se termină, veți rămâne cu a Vector
(o matrice tipărită) plină de toate Leafs
.
Iată un exemplu cu linii care separă fiecare Frunze
:
Acum, că dvs. Leafs
sunt definite, trebuie să facem camerele. Vrem un fel de efect "înăbușire" în care mergem de la cel mai mare, "rădăcină" Frunze
până la cel mai mic Leafs
fără copii, și apoi faceți o cameră în fiecare dintre acestea.
Deci, adăugați această funcție la dvs. Frunze
clasă:
funcția publică createRooms (): void // această funcție generează toate camerele și holurile pentru acest Leaf și pentru toți copiii săi. dacă (leftChild! = null || rightChild! = null) // această frunză a fost împărțită, așa că intrați în frunzele copiilor dacă (leftChild! = null) leftChild.createRooms (); dacă (rightChild! = null) rightChild.createRooms (); altfel // acest Leaf este gata pentru a face o cameră var varSize: Point; var roomPos: Point; // camera poate fi cuprinsă între 3 x 3 plăci la dimensiunea frunzei - 2. roomSize = nou punct (Registry.randomNumber (3, lățime - 2), Registry.randomNumber (3, înălțimea -2)); // așezați camera în interiorul frunzei, dar nu puneți-o corectă // pe partea laterală a frunzei (care ar fi combinarea camerelor împreună) roomPos = nou punct (Registry.randomNumber (1, width - roomSize.x - 1) , Registry.randomNumber (1, înălțime - camerăSize.y - 1)); cameră = nou dreptunghi (x + roomPos.x, y + roomPos.y, roomSize.x, roomSize.y);
Apoi, după ce v-ați creat Vector
de Leafs
, apelați noua funcție din rădăcina dvs. Frunze
:
_leafs = Vector nou; var l: frunze; // helper Leaf // în primul rând, creați un Leaf ca fiind "rădăcina" tuturor Leaf-urilor. vară rădăcină: Leaf = foaie nouă (0, 0, _sprMap.width, _sprMap.height); _leafs.push (root); var did_split: Boolean = adevărat; // ne batem prin fiecare frunză în Vectorul nostru din nou și din nou, până când nu mai pot fi împărțite frunze. în timp ce (did_split) did_split = false; pentru fiecare (l în _leafs) if (l.leftChild == null && l.rightChild == null) // dacă acest Leaf nu este deja divizat ... // dacă această frunză este prea mare sau 75% șansă ... dacă (l.width> MAX_LEAF_SIZE || l.height> MAX_LEAF_SIZE || FlxG.random ()> 0.25) dacă (l.split ()) // împărțiți frunza! // dacă ne-am împărțit, împingeți frunzele copilului la Vector, astfel încât să le putem introduce în următoarea _leafs.push (l.leftChild); _leafs.push (l.rightChild); did_split = true; //, apoi iterați prin fiecare frunză și creați o cameră în fiecare. root.createRooms ();
Iată un exemplu de unele generate Leafs
cu camere în interiorul lor:
După cum puteți vedea, fiecare Frunze
conține o cameră singulară cu o mărime și poziție aleatoare. Puteți juca cu valorile minime și maxime Frunze
dimensiunea și modificați modul în care determinați dimensiunea și poziția fiecărei camere, pentru a obține efecte diferite.
Dacă ne îndepărtăm pe noi Frunze
linii de separare, puteți observa că camerele umple întreaga hartă frumos - nu există o mulțime de spațiu pierdut - și să aibă un simt ceva mai organic pentru ei.
Leafs
cu o cameră în interiorul fiecăruia, cu linii de separare îndepărtate. Acum, tot ce trebuie să facem este să conectăm fiecare cameră. Din fericire, deoarece avem relațiile construite între ele Leafs
, tot ce trebuie să faceți este să vă asigurați că fiecare dintre ele Frunze
care are copil Leafs
are un hol care le conectează copiii.
O vom lua Frunze
, uitați-vă la fiecare dintre copiii săi Leafs
, du-te tot drumul prin fiecare copil până ajungem la Frunze
cu o cameră, apoi conectați camerele împreună. Putem face acest lucru în același timp când generăm camerele noastre.
În primul rând, avem nevoie de o nouă funcție de a itera de la orice Frunze
într-una din încăperile care sunt în interiorul unuia dintre copii Leafs
:
funcția publică getRoom (): Rectangle // iterați tot timpul prin aceste frunze pentru a găsi o cameră, dacă există. dacă (camera! = null) sala de retur; altfel var lRoom: dreptunghi; var rRom: dreptunghi; dacă (leftChild! = null) lRoom = leftChild.getRoom (); dacă (rightChild! = null) rRomă = rightChild.getRoom (); dacă (lRoom == null && rRoom == null) returnați null; altfel dacă (rRoom == null) returnați lRoom; altfel dacă (lRoom == null) returnați rRoom; altfel dacă (FlxG.random ()> .5) returnați lRoom; altceva returnează rRoom;
Apoi, avem nevoie de o funcție care să ia o pereche de camere, să aleagă un punct aleatoriu în interiorul ambelor camere și apoi să creeze fie unul sau două dreptunghiuri cu două tigle groase pentru a conecta punctele împreună.
funcția publică createHall (l: dreptunghi, r: dreptunghi): void // acum conectăm aceste două camere împreună cu holurile. // arata destul de complicat, dar incearca doar sa intelege care este punctul unde si apoi trage o linie dreapta sau o pereche de linii pentru a face un unghi drept pentru a le conecta. // ai putea să faci o logică suplimentară pentru a-ți face halele mai îndoielnice sau să faci niște lucruri mai avansate dacă vrei. hale = Vector nou; var punctul1: Punct = nou punct (Registry.randomNumber (l.left + 1, l.right - 2), Registry.randomNumber (l.top + 1, l.bottom - 2)); var punct2: Punct = nou punct (Registry.randomNumber (r.left + 1, r.right - 2), Registry.randomNumber (r.top + 1, r.bottom - 2)); var w: Număr = punct2.x - punct1.x; var h: Număr = punct2.y - punct1.y; dacă (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 (nou dreptunghi (punctul1.x, point1.y, 1, Math.abs (h)));
În cele din urmă, schimba-ți createRooms ()
funcția de a apela createHall ()
funcție pe orice Frunze
care are o pereche de copii:
funcția publică createRooms (): void // această funcție generează toate camerele și holurile pentru acest Leaf și pentru toți copiii săi. dacă (leftChild! = null || rightChild! = null) // această frunză a fost împărțită, așa că intrați în frunzele copiilor dacă (leftChild! = null) leftChild.createRooms (); dacă (rightChild! = null) rightChild.createRooms (); // dacă există copii în stânga și în dreapta în acest Leaf, creați un hol între ele dacă (leftChild! = null && rightChild! = null) createHall (leftChild.getRoom (), rightChild.getRoom ()); altfel // acest Leaf este gata pentru a face o cameră var varSize: Point; var roomPos: Point; // camera poate fi cuprinsă între 3 x 3 plăci la dimensiunea frunzei - 2. roomSize = nou punct (Registry.randomNumber (3, lățime - 2), Registry.randomNumber (3, înălțimea -2)); // așezați camera în interiorul frunzei, dar nu o puneți direct pe partea laterală a foii (care ar combina camerele împreună) roomPos = nou punct (Registry.randomNumber (1, width - roomSize.x - 1), Registru .randomNumber (1, înălțime - camerăSize.y - 1)); cameră = nou dreptunghi (x + roomPos.x, y + roomPos.y, roomSize.x, roomSize.y);
Camerele și coridoarele ar trebui să pară așa:
Leafs
umplut cu camere aleatorii conectate prin holuri. După cum puteți vedea, pentru că ne asigurăm să conectăm fiecare Frunze
, nu suntem lasati cu camere orfane. Evident, logica holului ar putea fi puțin mai rafinată pentru a evita să fie prea aproape de alte holuri, dar funcționează destul de bine.
Aceasta este în esență! Am descoperit cum să creați un (relativ) simplu Frunze
obiect, pe care îl puteți folosi pentru a genera un copac de frunze divizate, generează o cameră aleatorie în interiorul fiecăruia Frunze
, și conectați camerele prin holuri.
În prezent, toate obiectele pe care le-am creat sunt în esență dreptunghiuri, dar în funcție de modul în care intenționați să folosiți temnița rezultată, le puteți trata în tot felul de moduri.
Acum puteți folosi BSP pentru a face orice fel de hărți aleatoare pe care le doriți sau pentru a le folosi pentru a distribui în mod egal puteri sau dușmani într-o zonă ... sau orice vreți!