Prosess Generering for enkle puslespill

Hva du skal skape

Puslespill er en integrert del av gameplay for mange sjangere. Enten det er enkelt eller komplekst, å utvikle puslespill manuelt, kan raskt bli tungvint. Denne opplæringen tar sikte på å lette den byrden og bane vei for andre, morsomme aspekter av design.

Sammen skal vi lage en generator for å komponere enkle prosessoriske "nestede" puslespill. Typen av puslespill som vi vil fokusere på, er den tradisjonelle "låsen og nøkkelen" som oftest ererert som: få x element for å låse opp y-området. Disse typer puslespill kan bli kjedelig for lag som arbeider med bestemte typer spill, spesielt dungeon crawlers, sandkasser og rollespill hvor puslespill er oftere avhengig av innhold og utforskning.

Ved å bruke prosedyregenerering er målet vårt å skape en funksjon som tar noen parametere og gir en mer komplisert ressurs for spillet vårt. Bruk av denne metoden gir en eksponentiell avkastning på utvikletid uten å ofre spillkvaliteten. Utviklerens bedrøvelse kan også falle som en lykkelig bivirkning.

Hva trenger jeg å vite?

For å følge med, må du være kjent med et programmeringsspråk som du selv velger. Siden det meste av det vi diskuterer kun er data og generalisert til pseudokode, vil ethvert objektorientert programmeringsspråk være tilstrekkelig. 

Faktisk vil noen dra-og-slipp-redaktører også fungere. Hvis du ønsker å lage en spillbar demonstrasjon av generatoren nevnt her, vil du også trenge litt kjennskap til ditt foretrukne spillbibliotek.

Opprette generatoren

La oss begynne med en titt på noen pseudokode. De mest grunnleggende byggeblokkene i vårt system skal være nøkler og rom. I dette systemet er en spiller sperret fra å komme inn i et roms dør med mindre de har nøkkelen. Her er hva de to objektene vil se ut som klasser:

klasse nøkkel var playerHas; Var sted; Funksjon init (setLocation) Plassering = setLocation; PlayerHas = false;  Funksjon pickUp () this.playerHas = true;  Klasserom Var er Låst; Var assocKey; Funksjon init () isLocked = true; assocKey = ny nøkkel (dette);  Lås opp funksjon () this.isLocked = false;  Funksjon canUnlock Hvis (this.key.PlayerHas) Return true;  Else Return false; 

Vår nøkkelklasse holder kun to deler av informasjonen akkurat nå: plasseringen av nøkkelen, og hvis spilleren har nøkkelen i hans eller hennes beholdning. De to funksjonene er initialisering og opphenting. Initialisering bestemmer grunnleggende om en ny nøkkel, mens pickup er for når en spiller samhandler med nøkkelen.

Våre romklasse inneholder i sin tur to variabler: er låst, som holder den nåværende tilstanden til rommet lås, og assocKey, som har nøkkelobjektet som låser opp dette bestemte rommet. Den inneholder også en funksjon for initialisering, en å ringe for å låse opp døren, og en annen for å sjekke om døren for øyeblikket kan åpnes.

En enkelt dør og nøkkel er morsomt, men vi kan alltid krydre det opp med nesting. Ved å implementere denne funksjonen kan vi lage dører innenfor dørene mens de fungerer som vår primære generator. For å opprettholde nesting, må vi legge til noen ekstra variabler til vår dør også:

Klasserom Var er Låst; Var assocKey; Var parentRoom; Var dybde; Funksjon init (setParentRoom, setDepth) Hvis (setParentRoom) parentRoom = setParentRoom;  Ellers parentRoom = none;  Dybde = setDepth; isLocked = true; assocKey = ny nøkkel (dette);  Lås opp funksjon () this.isLocked = false;  Funksjon canUnlock Hvis (this.key.playerHas) Return true;  Else Return false;  Funksjon roomGenerator (depthMax) Array roomsToCheck; Array finishedRooms; Rom initialRoom.init (ingen, 0); roomsToCheck.add (initialRoom); Mens (roomsToCheck! = Tom) Hvis (currentRoom.depth == depthMax) finishedRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  Ellers Room newRoom.init (currentRoom, currentRoom.depth + 1); roomsToCheck.add (NEWROOM); finishedRooms.add (currentRoom); roomsToCheck.remove (currentRoom); 

Denne generalkoden gjør følgende:

  1. Tar i parameteren for vårt genererte puslespill (spesifikt hvor mange lag dypt et nestet rom skal gå).

  2. Opprette to arrays: en for rom som blir sjekket for potensiell nesting, og en annen for registrering av rom som allerede er nestet.

  3. Opprette et innledende rom for å inneholde hele scenen, og legg den til i arrayet for å sjekke senere.

  4. Tar rommet på forsiden av matrisen for å sette igjennom sløyfen.

  5. Kontrollerer dybden på det nåværende rommet mot det maksimale dybden som tilbys (dette bestemmer om vi lager et ytterligere barnrom eller hvis vi fullfører prosessen).

  6. Etablere et nytt rom og fylle det med nødvendig informasjon fra foreldrommet.

  7. Legge til det nye rommet til roomsToCheck array og flytte det forrige rommet til den ferdige gruppen.

  8. Gjenta denne prosessen til hvert rom i gruppen er fullført.

Nå kan vi få så mange rom som vår maskin kan håndtere, men vi trenger fortsatt nøkler. Nøkkelplassering har en stor utfordring: løslighet. Uansett hvor vi legger nøkkelen, må vi sørge for at en spiller har tilgang til det! Uansett hvor utmerket den skjulte nøkkelbufferen virker, hvis spilleren ikke kan nå det, er han eller hun effektivt fanget. For at spilleren skal fortsette gjennom puslespillet, må nøklene være tilgjengelig.

Den enkleste metoden for å sikre løsbarhet i puslespillet vårt er å bruke det hierarkiske systemet for foreldre-barnobjektrelasjoner. Siden hvert rom ligger i en annen, forventer vi at en spiller må ha tilgang til foreldrene til hvert rom for å nå det. Så, så lenge nøkkelen er over rommet på den hierarkiske kjeden, garanterer vi at spilleren vår er i stand til å få tilgang.

For å legge til nøkkelgenerering til vår prosessgenerering, legger vi følgende kode inn i vår hovedfunksjon:

 Funksjon roomGenerator (depthMax) Array roomsToCheck; Array finishedRooms; Rom initialRoom.init (ingen, 0); roomsToCheck.add (initialRoom); Mens (roomsToCheck! = Tom) Hvis (currentRoom.depth == depthMax) finishedRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  Ellers Room newRoom.init (currentRoom, currentRoom.depth + 1); roomsToCheck.add (NEWROOM); finishedRooms.add (currentRoom); roomsToCheck.remove (currentRoom); Array allParentRooms; roomCheck = newRoom; Mens (roomCheck.parent) allParentRooms.add (roomCheck.parent); roomCheck = roomCheck.parent;  Nøkkel newKey.init (Tilfeldig (allParentRooms)); newRoom.Key = newKey; Return finishedRooms;  Else finishedRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  

Denne ekstra koden vil nå lage en liste over alle rommene som er over ditt nåværende rom i karthierarkiet. Da velger vi en av disse tilfeldig, og setter nøkkelens plassering til det rommet. Deretter tilordner vi nøkkelen til rommet det låser opp.

Når vi ringes, vil vår generasjonsfunksjon nå opprette og returnere et gitt antall rom med nøkler, som kan spare tid for utviklingstiden!

Det bryter opp pseudokode-delen av vår enkle puslespilleren, så la oss nå sette den i gang.

Procedural Puzzle Generation Demo

Vi bygger vår demo ved hjelp av JavaScript og Crafty.js biblioteket for å holde det så lett som mulig, slik at vi kan holde programmet under 150 linjer med kode. Det er tre hovedkomponenter i vår demo som beskrevet nedenfor:

  1. Spilleren kan bevege seg gjennom hvert nivå, henting av nøkler og låse opp dører.

  2. Generatoren som vi vil bruke til å lage et nytt kart automatisk hver gang demoen kjøres.

  3. En utvidelse for generatoren vår å integrere med Crafty.js, som tillater oss å lagre objekt-, kollisjon- og enhetinformasjon.

Pseudokoden ovenfor fungerer som et redskap for forklaring, så implementering av systemet på ditt eget programmeringsspråk vil kreve noe modifikasjon.

For vår demo forenkles en del av klassene for effektivere bruk i JavaScript. Dette inkluderer å slippe visse funksjoner relatert til klassene, da JavaScript muliggjør lettere tilgang til variabler i klasser.

For å lage spilldelen av vår demo, initierer vi Crafty.js, og deretter en spillerenhet. Deretter gir vi spillerens enhet de grunnleggende fire retningskontrollene og litt mindre kollisjonsdeteksjon for å forhindre å komme inn i låste rom.

Rom er nå gitt en Crafty enhet, lagring av informasjon om deres størrelse, plassering og farge for visuell representasjon. Vi vil også legge til tegnefunksjon slik at vi kan lage et rom og tegne det på skjermen.

Vi vil gi nøkler med lignende tillegg, inkludert lagring av sin Crafty enhet, størrelse, plassering og farge. Tastene vil også være fargekodede for å matche rommene de låser opp. Til slutt kan vi nå legge nøklene og lage sine enheter ved hjelp av en ny tegnefunksjon.

Sist men ikke minst utvikler vi en liten hjelpefunksjon som skaper og returnerer en tilfeldig heksadesimal fargeverdi for å fjerne byrden av å velge farger. Med mindre du liker å fargelegge farger, selvfølgelig.

Hva gjør jeg neste?

Nå som du har din egen enkle generator, er det noen ideer for å utvide eksemplene våre:

  1. Port generatoren for å aktivere bruk i ditt programmerte språk.

  2. Utvid generatoren til å inkludere skaper forgreningsrom for videre tilpasning.

  3. Legg til muligheten til å håndtere flere rom innganger til vår generator for å tillate mer komplekse gåter.

  4. Utvid generatoren for å muliggjøre nøkkelplassering på mer kompliserte steder for å forbedre spillerens problemløsning. Dette er spesielt interessant når det er sammenkoblet med flere baner for spillere.

Wrapping Up

Nå som vi har opprettet denne puslespillgeneratoren sammen, bruk konseptene som er vist for å forenkle din egen utviklingssyklus. Hvilke repeterende oppgaver finner du selv å gjøre? Hva plager deg mest med å skape ditt spill? 

Sjansene er, med en liten planlegging og prosessorgenerering, kan du gjøre prosessen vesentlig enklere. Forhåpentligvis vil vår generator tillate deg å fokusere på de mer tiltalende delene av spillproduksjon mens du skjærer ut det verdslige.

Lykke til, og jeg ser deg i kommentarene!