Genereer Grotvlakke deur Middel van Mobile Automata
Afrikaans (Afrikaans) translation by Seurion (you can also view the original English article)
Prosesinhoudgenerator is 'n stuk kode wat geskryf is in jou spel wat al die tyd nuwe speletjies van spelinhoud kan skep - selfs terwyl die spel hardloop! Spelontwikkelaars het prosedureel probeer om alles van die 3D wêreld na die musiekklankbaan te produseer. As jou meer generasies by jou spel voeg, is dit 'n goeie manier om ekstra waarde in te sluit: spelers hou daarvan omdat hulle nuwe, onverwagte en interessante inhoud elke keer as hulle speel.
In hierdie, handleiding gaan ons kyk na goeie metodes om willekeurige vlakke te genereer, en probeer om die grense van wat jy dink, gegenereer kan word, te strek.
As jy belangstel om meer oor die onderwerp te lees procedural content generation, level design, AI, of cellular automata, maak seker dat u hierdie ander plasings nagaan:
- Die Skep van die Lewe: Game of Conway Life
- Portal 2 Level Design: Skep Legkaarte om Jou Spelers uit te Daag
- StarCraft II Vlakontwerp: Estetiese Ontwerp en Editor Wenke
- Kodering van 'n aangepaste volgorde generator om 'n Starscape te lewer
- Grotte is wonderlik vir alle soorte spel genres en instellings, maar hulle herinner my veral aan ou kerkers in rolspeletjies.
Welkom by die Grotte!
In hierdie, handleiding sal ons 'n grotgenerator bou. Die grot is perfek vir alle soorte genres en spelinstellings, maar hulle herinner my regtig aan die ou kelder in die rolspel.
Kyk na die onderstaande demo om die soorte uitvoer te sien wat jy sal kan kry. Klik 'New World' om 'n nuwe grot te genereer om te kyk. Ons sal betyds oor verskillende instellings praat.
Hierdie kragopwekker gee ons werklik 'n reuse tweedimensionele reëling, wat elkeen solied of leeg is. So eintlik kan jou hierdie kragopwekker gebruik vir alle vorme van speletjies, behalwe dungeon explorers: ewekansige vlakke vir strategie speletjies, kaarte vir platformspeletjies, miskien selfs as 'n arena vir multiplayer shooters! As jy mooi kyk, draai die soliede en leë blokke ook die eilandgenerator. Dit alles gebruik dieselfde kode en uitvoer, wat dit 'n baie buigsame instrument maak.
Kom ons begin deur 'n eenvoudige vraag te vra: wat is eintlik 'n selfoonautomaat?
Begin met sel
In die 1970's het 'n wiskundige genaamd John Conway 'n beskrywing van The Game of Life gepubliseer, wat soms net Life genoem word. Die lewe was nie regtig 'n spel nie; Dit was meer soos 'n simulasie wat 'n rooster selle geneem het (dit kan lewend of dood wees) en 'n paar eenvoudige reëls aan hulle toegepas het.
Vier reëls het op elke sel in elke simulasie stap toegepas:
- As 'n lewende sel minder as twee lewende bure het, sal dit sterf.
- As 'n lewende sel twee of drie lewende bure het, bly dit lewendig.
- As 'n lewende sel meer as drie lewende bure het, sterf dit.
- As 'n dooie sel presies drie lewende bure het, word dit lewendig.
Mooi en eenvoudig! As jou egter verskillende kombinasies van die aanvangsrooster probeer, kan jou baie vreemde resultate kry. Oneindige lusse, masjiene wat vorms spoeg, en meer. Die spel van die lewe is 'n voorbeeld van 'n sellulêre outomatiese - 'n rooster van selle wat deur sekere reëls beheer word.
Ons gaan 'n stelsel wat baie soos Lewe is, implementeer, maar in plaas van die vervaardiging van snaakse patrone en vorms, gaan dit wonderlike grotstelsels vir ons speletjies skep.
Implementering van Outomatiese Mobiele
Ons gaan ons sellulêre rooster voorstel as 'n tweedimensionele skikking van Boole (waar
of vals
) waardes. Dit pas by ons omdat ons net belangstel of 'n teël solied is of nie.
Hier begin ons die selnetwerk:
boolean[][] cellmap = new boolean[width][height];
Wenk: Let op dat die eerste indeks die x-koördinaat vir die skikking is en die tweede indeks is die y-koördinaat. Dit maak toegang tot die skikking meer natuurlik in kode.
In die meeste programmeertale sal hierdie skikking initialiseer met al sy waardes wat op vals
gestel is. Dit is goed vir ons! As 'n skikking indeks (x, y)
vals
is, sal ons sê dat die sel leeg is; as dit waar
is, sal daardie teël soliede rots wees.
Elk van hierdie skikkingsposisies verteenwoordig een van die 'selle' in ons mobiele netwerk. Nou moet ons ons rooster opstel sodat ons ons grotte kan begin bou.
Ons gaan begin deur ewekansig elke sel tot dood of lewend te stel. Elke sel sal dieselfde ewekansige kans hê om lewend gemaak te word, en jy moet seker maak dat die waarde van hierdie geleentheid iewers in 'n veranderlike gestel word, omdat ons dit beslis later wil verander en dit iewers maklik toeganklik sal maak, help ons daarmee. Ek sal 45%
gebruik om mee te begin.
float chanceToStartAlive = 0.45f; public boolean[][] initialiseMap(boolean[][] map){ for(int x=0; x<width; x++){ for(int y=0; y<height; y++){ if(random() < chanceToStartAlive){ map[x][y] = true; } } } return map; }



As ons hierdie kode hardloop, eindig ons met 'n groot rooster van selle soos die een wat ewekansig lewendig of dood is. Dit is rommelig, en dit lyk beslis nie soos 'n grotstelsel wat ek nog ooit gesien het nie. So wat is volgende?
Groei ons Grotte
Onthou die reëls wat die selle in die Spel van Lewe Reguleer? Elke keer as die simulasie met een stap vorentoe beweeg, sal elke sel die Lewensreël nagaan en kyk of dit na die lewe of dood sal verander. Ons sal dieselfde idee gebruik om ons grot te bou - ons sal die huidige funksie wat elke sel op die rooster dek, skryf en basiese reëls toepas om te besluit of dit aan of af is.
Soos u later sal sien, gaan ons meer as een keer hierdie kode gebruik, dus om dit in sy eie funksie te stel, kan ons dit soveel of so min as wat ons wil, noem. Ons gee dit 'n lekker insiggewende naam soos DoSimulationStep (),
ook.
Wat moet die funksie doen? Wel eerste, ons gaan 'n nuwe rooster maak wat ons ons opgedateerde selwaardes kan plaas. Om te verstaan waarom ons dit moet doen, onthou dat om die nuwe waarde van 'n sel in die rooster te bereken, ons moet na sy agt bure kyk:



Maar as ons reeds die nuwe waarde van sommige van die selle bereken het en dit terug in die rooster sit, sal ons berekening 'n mengsel van nuwe en ou data wees, soos hierdie:



Oops! Dit is glad nie wat ons wil hê nie. So elke keer bereken ons 'n nuwe selwaarde, in plaas daarvan om dit terug in die ou kaart te plaas, gaan ons dit na 'n nuwe een skryf.
Kom ons begin met die skryf van die doSimulationStep ()
funksie, dan:
public doSimulationStep(boolean[][] oldMap){ boolean[][] newMap = new boolean[width][height]; //...
Ons wil elke sel op die rooster wisselvallig oorweeg, en tel hoeveel van sy bure woon en sterf. Om jou bure in 'n skikking te tel is een van daardie vervelige stukkies kode wat jou 'n miljoen keer moet skryf. Hier is 'n vinnige implementering in 'n funksie wat ek noem, CountAliveNeighbours ()
:
//Returns the number of cells in a ring around (x,y) that are alive. public countAliveNeighbours(boolean[][] map, int x, int y){ int count = 0; for(int i=-1; i<2; i++){ for(int j=-1; j<2; j++){ int neighbour_x = x+i; int neighbour_y = y+j; //If we're looking at the middle point if(i == 0 && j == 0){ //Do nothing, we don't want to add ourselves in! } //In case the index we're looking at it off the edge of the map else if(neighbour_x < 0 || neighbour_y < 0 || neighbour_x >= map.length || neighbour_y >= map[0].length){ count = count + 1; } //Otherwise, a normal check of the neighbour else if(map[neighour_x][neighbour_y]){ count = count + 1; } } } }
'N Paar dinge oor hierdie funksie:
Eerstens, vir
'n lus is dit 'n bietjie vreemd as jou nog nooit so iets gedoen het nie. Die idee is dat ons na al die selle wat rondom die punt is wil kyk (x, y)
. As jou na die onderstaande illustrasie kyk, kan u sien hoe die indekse wat ons wil hê, een minder is, gelyk aan en een meer as die oorspronklike indeks. Ons twee vir
lusse gee ons net dit, begin by -1
, en loop deur tot 1
. Ons voeg dit dan by die oorspronklike indeks in die vir
lus om elke buurman te vind.



Tweedens, let op hoe ons die onwerklike verwysingsrooster nagaan (byvoorbeeld, dit is van die rand van die kaart). Ons tel dit as 'n buurman. Ek verkies dit vir die grotgenerasie omdat dit geneig is om die kante van die kaart te vul, maar jou kan eksperimenteer deur dit nie te doen as jou wil nie.
So nou, kom ons gaan terug na ons doSimulationStep ()
funksie en voeg nog 'n paar kode by:
public boolean[][] doSimulationStep(boolean[][] oldMap){ boolean[][] newMap = new boolean[width][height]; //Loop over each row and column of the map for(int x=0; x<oldMap.length; x++){ for(int y=0; y<oldMap[0].length; y++){ int nbs = countAliveNeighbours(oldMap, x, y); //The new value is based on our simulation rules //First, if a cell is alive but has too few neighbours, kill it. if(oldMap[x][y]){ if(nbs < deathLimit){ newMap[x][y] = false; } else{ newMap[x][y] = true; } } //Otherwise, if the cell is dead now, check if it has the right number of neighbours to be 'born' else{ if(nbs > birthLimit){ newMap[x][y] = true; } else{ newMap[x][y] = false; } } } } return newMap; }
Dit loop oor die hele kaart en pas ons reëls toe op elke raadsel om die nuwe waarde te bereken en dit in newMap
te plaas. Die reëls is eenvoudiger as die spel van die lewe - ons het twee spesiale veranderlikes, een vir lewende dooie selle (geboorteLimit
) en een vir lewende selle (deathLimit
). As die lewende selle omring word deur minder as doodLimit
selle wat hulle sterf, en as die sel ten minste doodgaan geboortegrens
hul selle kom lewendig. Mooi en eenvoudig!
Al wat aan die einde oor is, is 'n finale aanraking om die opgedateerde kaart terug te stuur. Hierdie funksie verteenwoordig 'n stap uit ons mobiele outomatiese reël - die volgende stap is om te verstaan wat gebeur wanneer ons dit een keer, twee keer of meer keer aan ons oorspronklike opstartkaart toedien.
Tweaking en Tuning
Kom ons kyk hoe die hoofgenereringskode nou lyk, met die kode wat ons tot dusver geskryf het.
public boolean[][] generateMap(){ //Create a new map boolean[][] cellmap = new boolean[width][height]; //Set up the map with random values cellmap = initialiseMap(cellmap); //And now run the simulation for a set number of steps for(int i=0; i<numberOfSteps; i++){ cellmap = doSimulationStep(cellmap); } }
Die enigste baie nuwe kode is 'n vir
lus wat ons simulasiemetode 'n aantal keer keer. Weereens, druk dit in 'n veranderlike sodat ons dit kan verander, want ons gaan nou met hierdie waardes begin speel!
Tot dusver het ons hierdie veranderlikes gestel:
-
kansToStartAlive
bepaal hoe dig die aanvanklike rooster is met lewende selle. -
hongersnoodLimit
is die onderste buurlimiet waarop selle begin sterf. -
oorpopLimit
is die boonste grensgrens waarmee selle begin sterf. -
geboorteNommer
is die aantal bure wat veroorsaak dat 'n dooie sel lewendig word. -
numberOfSteps
is die aantal kere wat ons die simulasiestap uitvoer.



Jou kan met hierdie veranderlikes in die demo bo aan die bladsy finaal. Elke waarde sal die demo dramaties verander, so speel 'n toneelstuk en kyk wat pas.
Een van die interessantste veranderinge wat jou kan maak is die numberOfSteps
veranderlike. Soos jou die simulasie vir meer stappe uitvoer, verdwyn die ruheid van die kaart, en eilande vloei glad nie in nie. Ek het 'n knoppie bygevoeg sodat jou die funksie self kan noem en die effek sien. Eksperimenteer 'n bietjie en jou sal 'n kombinasie van instellings vind wat pas by jou styl en die vlakke van jou spel se vlakke.



Daarmee, is dit klaar. Veels geluk - jou het pas 'n prosedure vlakgenerator gemaak, goed gedoen! Sit terug, hardloop en herloop jou kode, en glimlag by die vreemde en wonderlike grotstelsels wat uitkom. Welkom in die wêreld van prosesgenerasie.
Verder
As jou na jou lieflike grotgenerator staar en wonder wat jou meer daarmee kan doen, hier is 'n paar ekstra kredietopdrag idees:
Gebruik 'n Vloedlading om 'n Kwaliteitstoets af te Lê
Vloedvulling is 'n baie eenvoudige algoritme wat jou kan gebruik om al die spasies in 'n skikking te vind wat met 'n bepaalde punt verbind word. Net soos die naam aandui, werk die algoritme 'n bietjie soos om 'n emmer water in jou vlak te giet. Dit versprei vanaf die beginpunt en vul al die hoeke in.
Vloedvul is ideaal vir selfoonautomaat, want jou kan dit gebruik om te sien hoe groot 'n bepaalde grot is. As jou die demo 'n paar keer hardloop, sal jou sien dat sommige kaarte uit een groot grot bestaan, terwyl ander verskeie klein grotte bevat wat van mekaar geskei is. Vloedvulling kan jou help om vas te stel hoe groot 'n grot is, en dan moet die vlak regenereer as dit te klein is, of besluit waar die speler wil begin as jou dink dit is groot genoeg. Daar is 'n oorsig van die opvolging van die vloed op Wikipedia.
Plasing van Skatte Vinnig en Eenvoudig
Om skatte in koel areas te plaas, neem soms baie kode, maar ons kan eintlik genoeg eenvoudige kode skryf om die skat uit die weg te ruim in ons grotstelsel. Ons het reeds ons kode wat bereken hoeveel bure 'n vierkant het, dus deur ons voltooide grotstelsel te herhaal, kan ons sien hoe dit omring word deur 'n spesifieke vierkantige muur.
As 'n leë roostersel omring word deur baie soliede mure, miskien aan die einde van die gang of in die mure van die grotstelsel. Dit is 'n goeie plek om 'n skat weg te steek - dus, deur 'n eenvoudige tjek van ons bure te doen, kan ons skatte in hoeke en paaie gly.
public void placeTreasure(boolean[][] world){ //How hidden does a spot need to be for treasure? //I find 5 or 6 is good. 6 for very rare treasure. int treasureHiddenLimit = 5; for (int x=0; x < worldWidth; x++){ for (int y=0; y < worldHeight; y++){ if(!world[x][y]){ int nbs = countAliveNeighbours(world, x, y); if(nbs >= treasureHiddenLimit){ placeTreasure(x, y); } } } } }
Dit is nie perfek nie. Soms word skatte in gate geplaas wat nie in 'n grotstelsel toeganklik is nie, en soms sal kolle ook redelik voor die hand liggend wees. Maar, in 'n noodgeval, is dit 'n goeie manier om die versameling om jou vlak te versprei. Probeer dit op demo deur te druk placeTreasure ()!
Gevolgtrekkings en Verdere Leeswerk
Hierdie handleiding wys hoe om 'n basiese maar volledige prosedure generator te bou. Met net 'n paar eenvoudige stappe skryf ons kode wat nuwe vlakke in die oogpunt kan skep. Hopelik het dit jou 'n moontlike proe gegee om 'n prosesinhoudgenerator vir jou spel te skep!
As jou meer wil lees, is Roguebasin 'n goeie bron van inligting oor prosedurele generasie stelsels. Dit fokus hoofsaaklik op roguelike-speletjies, maar baie van die tegnieke kan in ander vorme van speletjies gebruik word, en daar is baie inspirasie om ook ander dele van 'n spel procedureel te produseer!
As jou meer wil hê in die Procedural Content Generation of Mobile Automata, is dit die aanlyn weergawe van Game The Life (hoewel ek sterk aanbeveel om "Conway se spel van lewe" in Google te tik). Miskien hou jou ook van Wolfram Tones, 'n pragtige eksperiment in die gebruik van selfoonautomaat om musiek te produseer!
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Update me weekly