En introduksjon til å lage en flate kartmotor

I denne veiledningen hjelper jeg deg med å lage nivåer for enhver spillgenre og gjøre utformingen av nivåer mye enklere. Du skal lære å lage din første fliser kartmotor for bruk i noen av dine fremtidige prosjekter. Jeg bruker Haxe med OpenFL, men du bør kunne følge med på hvilket som helst språk.

Her er hva vi skal dekke:

  • Hva er et fliserbasert spill?
  • Finne eller lage dine egne fliser.
  • Skriver koden for å vise fliser på skjermen.
  • Redigerer flislayouter for forskjellige nivåer.

Du kan også få en god start på en ny spillidee også!


Hva er et fliserbasert spill?

Naturligvis har Wikipedia en grundig definisjon av hva et fliserbasert spill er, men for å få det grunnleggende kjernen er det bare noen få ting du trenger å vite:

  1. EN flis er et lite bilde, vanligvis rektangulært eller isometrisk, som fungerer som et puslespill kunstverk for å bygge større bilder.
  2. EN kart er en gruppering av fliser satt sammen for å skape en (forhåpentligvis) visuelt tiltalende "seksjon" (som et nivå eller et område).
  3. Filbasert refererer til metoden for å bygge nivåer i et spill. Koden vil utforme fliser på bestemte steder for å dekke det tiltenkte området.

For å bli enda mer grunnleggende, vil jeg si det slik:

Et flisbasert spill legger ut fliser for å skape hvert nivå.

I forbindelse med de vanlige fliser - rektangulære og isometriske - vil vi bruke rektangulære fliser i denne artikkelen for deres enkelhet. Hvis du bestemmer deg for å prøve ut isometriske nivåer en dag, er det mer matematikk involvert for å få det til å fungere. (Når du er ferdig her, sjekk ut denne flotte grunnen til å skape isometriske verdener.)

Det er noen ganske kule fordeler som du får fra å bruke en flisemotor. Den mest synlige fordel er at du ikke trenger å lage massive bilder for hånd for hvert enkelt nivå. Dette vil kutte ned på utviklingstiden og kutte ned på filstørrelser. Å ha 50 1280x768px bilder for et 50-nivå spill vs å ha ett bilde med 100 fliser gjør en stor forskjell.

En annen bivirkning er at å finne ting på kartet ved hjelp av kode blir litt lettere. I stedet for å kontrollere ting som kollisjon basert på en eksakt piksel, kan du bruke en rask og enkel formel for å finne ut hvilken flis du trenger for å få tilgang. (Jeg skal gå over det litt senere.)


Finne eller lage dine egne fliser

Det første du trenger når du bygger flisemotoren er et sett med fliser. Du har to alternativer: Bruk andres fliser, eller lag dine egne.

Hvis du velger å bruke fliser som allerede er laget, kan du finne fritt tilgjengelig kunst over hele nettet. Ulempen med dette er at kunsten ikke ble laget spesielt for spillet ditt. På den annen side, hvis du bare prototyperer eller prøver å lære et nytt konsept, vil gratis fliser gjøre trikset.

Hvor finner du dine fliser

Det er ganske mange ressurser der ute for fri og åpen kildekode. Her er noen få steder å starte søket ditt:

  1. OpenGameArt.org
  2. La oss lage spill
  3. Pixel Prospector

Disse tre koblingene skal gi deg mer enn nok plasser for å finne noen anseelige fliser for dine prototyper. Før du blir gal, ta tak i alt du finner, sørg for at du forstår hvilket lisens alt er dekket under, og hvilke restriksjoner de kommer med. Mange lisenser vil tillate deg å bruke kunsten fritt og for kommersiell bruk, men de kan kreve tilskrivning.

For denne opplæringen brukte jeg noen fliser fra en The Open Game Art Bundle for plattformspillere. Du kan laste ned min nedskalerte versjoner eller originalene.

Hvordan lage dine egne fliser

Hvis du ikke har tatt sjansen til å lage kunst for spillene dine, kan det være litt skremmende. Heldigvis er det noen fantastiske og enkle stykker programvare som tar deg inn i det tykke av det, slik at du kan begynne å praktisere.

Mange utviklere begynner med pikselkunst for sine spill, og her er noen få gode verktøy for nettopp det:

  1. Aseprite
  2. Pyxel Rediger
  3. Graphics Gale
  4. Pixen for Mac-brukere

Dette er noen av de mest populære programmene for å lage pikselkunst. Hvis du vil ha noe litt mer kraftig, er GIMP et utmerket alternativ. Du kan også gjøre noen vektorkunst med Inkscape og følge noen fantastiske opplæringsprogrammer på 2D Game Art For Programmers.

Når du tar tak i programvaren, kan du begynne å eksperimentere med å lage dine egne fliser. Siden denne opplæringen er ment å vise deg hvordan du oppretter flisekartet ditt motor Jeg vil ikke gå inn i for mye detalj om å lage flisene selv, men det er en ting å alltid huske på:

Pass på at flisene passer sømløst sammen, og legg til litt variasjon for å holde dem interessante.

Hvis flisene dine viser åpenbare linjer mellom dem når de settes sammen, vil spillet ikke se veldig fint ut. Pass på at du tar litt tid til å lage et fint "puslespill" av fliser ved å gjøre dem sømløse og legge til litt variasjon.


Skrive koden for å vise fliser

Nå har vi alle de slags ting ut av veien, vi kan dykke inn i koden for å sette nyoppkjøpte (eller opprettede) fliser på skjermen.

Slik viser du en enkelt flis på skjermen

La oss starte med den helt grunnleggende oppgaven med å vise en enkelt flis på skjermen. Pass på at flisene dine er like store og lagret i separate bildefiler (vi snakker mer om spritark senere).

Når du har flisene i eiendomsmappen din på prosjektet, kan du skrive opp en veldig enkel Tile klasse. Her er et eksempel i Haxe:

importer flash.display.Sprite; importer flash.display.Bitmap; import openfl.Assets; klassen Tile utvider Sprite private var image: Bitmap; offentlig funksjon ny () super (); image = new Bitmap (Assets.getBitmapData ("assets / grassLeftBlock.png")); addChild (image); 

Siden alt vi gjør nå er å sette en enkelt flis på skjermen, er det eneste som klassen gjør er å importere fliser bildet fra eiendeler mappe og legg det som et barn til objektet. Denne klassen vil trolig variere sterkt basert på programmeringsspråket du bruker, men du bør lett kunne finne en veiledning om hvordan du viser et bilde på skjermen.

Nå som vi har a Tile klasse, må vi lage en forekomst av a Tile og legg det til vår hovedklasse:

importer flash.display.Sprite; importere flash.events.Event; importere flash.Lib; Klassen Main utvider Sprite public function new () super (); var flis = ny flis (); addChild (flis);  offentlige statiske funksjon hoved () Lib.current.addChild (new Main ()); 

De Hoved klassen skaper en ny Tile når konstruktøren (den ny() funksjon, kalt når spillet starter) kalles, og legger det til displaylisten.

Når spillet kjøres, vil hoved() funksjonen vil også bli kalt, og en ny Hoved objekt blir lagt til scenen. Du burde nå ha flisene dine øverst til venstre på skjermen. Så langt så bra.

Tips: Hvis ingen av det er fornuftig - ikke bekymre deg! Det er bare standard boilerplate Haxe-kode. Den viktigste tingen å forstå er at dette skaper en Tile objekt og legger den til skjermen.

Bruke Arrays å legge ut en hel skjerm med fliser

Det neste trinnet er å finne en måte å fylle hele skjermen med fliser på. En av de enkleste måtene å gjøre dette på er å fylle opp en rekke med heltall som hver representerer en flis ID. Deretter kan du iterere gjennom matrisen for å bestemme hvilken flis som skal vises og hvor du skal vise den.

Du har muligheten til å bruke et typisk system eller bruke et 2-dimensjonalt array. Hvis du ikke er kjent med 2D-arrays, er det i utgangspunktet et enkelt utvalg som er fylt med flere arrays. De fleste språk vil betegne dette som nameOfArray [x] [y] hvor x og y er indekser for tilgang til et enkelt element.

Siden x og y brukes også til skjermkoordinater, det kan være fornuftig å bruke x og y som koordinater for flisene våre. Trikset med 2D-arrays er å forstå hvordan heltallene er organisert. Du må kanskje reversere y og x når iterating gjennom arrays (eksempel nedenfor).

privat var eksempelArr = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]];

Legg merke til at i denne implementeringen er "0" -elementet i arrayet i seg selv et utvalg av fem heltall. Dette vil bety at du får tilgang til hvert element med y først, deretter x. Hvis du prøver å få tilgang til exampleArr [1] [0], du ville få tilgang til sjette flis.

Hvis du ikke forstår hvordan 2D-arrayene fungerer akkurat nå, ikke bekymre deg. For denne opplæringen vil jeg bruke et normalt utvalg for å holde ting enkelt og for å gjøre det enklere å visualisere:

privat var eksempelArr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , 0, 0];

Eksemplet ovenfor viser hvordan et vanlig system kan være litt enklere. Vi kan visualisere nøyaktig hvor flisen skal være, og alt vi trenger å gjøre er å bruke en enkel formel (ikke bekymre deg, det kommer!) For å få flisen vi vil ha.

La oss nå skrive noen kode for å lage vårt utvalg og fylle det med dem. Nummeret vil være ID som representerer vår første flis.

Først må vi opprette en variabel inne i hovedklassen for å holde vårt utvalg:

privat var kart: Array;

Dette kan se litt rart ut, så jeg slår det ned for deg.

Det variable navnet er kart, og jeg har gitt det en veldig spesifikk type: Array. De delen forteller bare vårt program at arrayet bare vil inneholde heltall. Arrays kan holde omtrent hvilken som helst type du vil ha, så hvis du bruker noe annet for å identifisere fliser, kan du endre parameteren her.

Deretter må vi legge til noen koden til vår Hoved klassens konstruktør (husk, dette er ny() funksjon) slik at vi kan lage en forekomst av kartet vårt:

map = nytt Array();

Denne linjen vil opprette et tomt utvalg som vi snart kan fylle ut med våre for å teste det ut. La oss først definere noen verdier som vil hjelpe oss med vår matte:

offentlig statisk var TILE_WIDTH = 60; offentlig statisk var TILE_HEIGHT = 60; offentlig statisk var SCREEN_WIDTH = 600; offentlig statisk var SCREEN_HEIGHT = 360;

Jeg har laget disse verdiene offentlig statisk fordi dette vil gi oss tilgang til dem fra hvor som helst i vårt program (via Main.Tile_WIDTH og så videre). Du har kanskje også lagt merke til at det er delt SCREEN_WIDTH av TILE_WIDTH gir oss 10 og deling SCREEN_HEIGHT av TILE_HEIGHT gir oss 6. Disse to tallene vil bli brukt til å bestemme hvor mange heltall som skal lagres i vårt utvalg.

 var w = Std.int (SCREEN_WIDTH / TILE_WIDTH); var h = Std.int (SCREEN_HEIGHT / TILE_HEIGHT); for (jeg i 0 ... w * h) map [i] = 1

I denne blokk av kode tilordner vi 10 og 6 til w og h, som jeg nevnte ovenfor. Da må vi hoppe inn i en til loop for å lage en matrise som passer 10 * 6 heltall. Dette vil stå for nok fliser for å fylle hele skjermen.

Nå har vi vårt grunnleggende kart bygget, men hvordan skal vi fortelle fliser å komme seg inn i deres rette sted? For det må vi gå tilbake til Tile klasse og lag en funksjon som gjør at vi kan flytte fliser rundt etter ønske:

 offentlig funksjon setLoc (x: Int, y: Int) image.x = x * Main.TILE_WIDTH; image.y = y * Main.TILE_HEIGHT; 

Når vi kaller setLoc () funksjon, passerer vi i x og y koordinater i henhold til vår kartklasse (formel kommer snart, jeg lover!). Funksjonen tar disse verdiene og oversetter dem til pikselkoordinater ved å multiplisere dem med TILE_WIDTH og TILE_HEIGHT, henholdsvis.

Det eneste du har å gjøre for å få våre fliser på skjermen, er å fortelle vårt Hoved klasse for å lage fliser og sette dem på plass basert på deres plasseringer i kartet. La oss gå tilbake til Hoved og implementere det:

 for (jeg i 0 ... map.length) var tile = new Tile (); var x = i% w; var y = Math.floor (i / w); tile.setLoc (x, y); addChild (flis); 

Oh yeah! Det er riktig, vi har nå en skjerm full av fliser. La oss bryte ned hva som skjer over.

Formelen

Formelen som jeg fortsetter å nevne er endelig her.

Vi beregner x ved å ta modul (%) av Jeg og w (som er 10, husk).

Modulen er bare resten etter heltall divisjon: \ (14 \ div 3 = 4 \ text rest \\\\\\\\\\\\\ modulus 3 = 2 \).

Vi bruker dette fordi vi vil ha vår verdi av x å tilbakestille tilbake til 0 på begynnelsen av hver rad, så tegner vi respektive flis helt til venstre:


Som for y, vi tar gulv() av i / w (det vil si, vi runder det resultatet ned) fordi vi bare vil y å øke når vi har nådd slutten av hver rad og flyttet ned ett nivå:


Tips: De fleste språk krever ikke at du ringer Math.floor () når det deles heltall Haxe håndterer heltall divisjon annerledes. Hvis du bruker et språk som har heltall divisjon, kan du bare bruke i / w (forutsatt at de begge er heltall).

Til slutt ønsket jeg å nevne litt om rullingsnivåer. Vanligvis oppretter du ikke nivåer som passer perfekt i visningsporten din. Kartene dine vil trolig være mye større enn skjermen, og du vil ikke fortsette å tegne bilder som spilleren ikke vil kunne se. Med litt rask og enkel matte, bør du kunne beregne hvilke fliser som skal være på skjermen, og hvilke fliser som ikke skal tegnes.

For eksempel: Skjermstørrelsen din er 500x500, flisene dine er 100x100, og verdensstørrelsen er 1000x1000. Du trenger bare å gjøre en rask sjekk før du tegner fliser for å finne ut hvilke fliser som er på skjermen. Bruk plasseringen av visningsporten din - la oss si 400x500 - du trenger bare å tegne fliser fra radene 4 til 9 og kolonnene 5 til 10. Du kan få disse tallene ved å dele plasseringen av flisestørrelsen, og deretter kompensere disse verdiene med skjermen størrelse dividert med flisestørrelsen. Enkel.

Det ser kanskje ikke ut som mye siden alle fliser er de samme, men grunnlaget er nesten der. De eneste tingene du må gjøre er å skape forskjellige typer fliser og designe vårt kart slik at de retter seg og skaper noe fint.


Redigerer Tile Layouts for forskjellige nivåer

Vi har nå et kart som er fullt av de som dekker skjermen. Ved dette punktet bør du ha mer enn en flisertype, noe som betyr at vi må endre vår Tile konstruktør for å redegjøre for det:

offentlig funksjon ny (id: Int) super (); bytte (id) tilfelle 1: image = new Bitmap (Assets.getBitmapData ("assets / grassLeftBlock.png")); tilfelle 2: image = ny Bitmap (Assets.getBitmapData ("assets / grassCenterBlock.png")); tilfelle 3: image = ny Bitmap (Assets.getBitmapData ("assets / grassRightBlock.png")); tilfelle 4: image = ny Bitmap (Assets.getBitmapData ("assets / goldBlock.png")); sak 5: image = ny bitmap (Assets.getBitmapData ("assets / globe.png")); tilfelle 6: image = new Bitmap (Assets.getBitmapData ("assets / mushroom.png"));  addChild (bilde); 

Siden jeg brukte seks forskjellige fliser til kartet, trengte jeg en bytte om uttalelse som dekker tallene ett til seks. Du vil legge merke til at konstruktøren nå tar et heltall som en parameter slik at vi vet hva slags fliser å lage.

Nå må vi gå tilbake til vår Hoved konstruktør og fikse flisene i vår til løkke:

 for (jeg i 0 ... map.length) var tile = new Tile (map [i]); var x = i% w; var y = Math.floor (i / w); tile.setLoc (x, y); addChild (flis); 

Alt vi måtte gjøre var å passere kart [i] til Tile konstruktør for å få det til å fungere igjen. Hvis du prøver å løpe uten å sende et heltall til Tile, det vil gi deg noen feil.

Nesten alt er på plass, men nå trenger vi en måte å designe kart i stedet for bare å fylle dem med tilfeldige fliser. Fjern først til sløyfe i Hoved konstruktør som setter hvert element til ett. Da kan vi lage vårt eget kart for hånd:

 map = [0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 0, 0, 0, 0, 6 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3];

Hvis du formaterer matrisen som jeg har over, kan du lett se hvordan flisene våre blir organisert. Du kan også bare angi arrayet som en lang linje med tall, men førstnevnte er fint fordi du kan se 10 over og 6 ned.

Når du prøver å kjøre programmet nå, bør du få noen nullpeker unntak. Problemet er at vi bruker nuller i vårt kart og vår Tile klassen vet ikke hva jeg skal gjøre med null. Først vil vi fikse konstruktøren ved å legge til i en sjekk før vi legger til bildebarnet:

 hvis (image! = null) addChild (bilde);

Denne hurtige sjekken sørger for at vi ikke legger noen nullbarn til Tile objekter vi lager. Den siste endringen for denne klassen er setLoc () funksjon. Akkurat nå prøver vi å sette x og y verdier for en variabel som ikke er initialisert.

La oss legge til en annen rask sjekk:

 offentlig funksjon setLoc (x: Int, y: Int) if (image! = null) image.x = x * Main.TILE_WIDTH; image.y = y * Main.TILE_HEIGHT; 

Med disse to enkle løsningene på plass, og flisene som jeg oppgav ovenfor, bør du kunne kjøre spillet og se et enkelt plattformsnivå. Siden vi forlot 0 som en "ikke-fliser", kan vi la den være gjennomsiktig (tom). Hvis du vil spice opp nivået, kan du sette en bakgrunn inn før du legger ut flisene; Jeg har nettopp lagt til en lyseblå gradient som ser ut som en himmel i bakgrunnen.

Det er stort sett alt du trenger for å sette opp en enkel måte å redigere nivåene på. Prøv å eksperimentere litt med matrisen og se hvilke design du kan komme opp på for andre nivåer.

Tredjeparts programvare for utforming av nivåer

Når du har det grunnleggende nede, kan du vurdere å bruke noen andre verktøy for å hjelpe deg med å designe nivåer raskt og se hvordan de ser ut før du kaster dem inn i spillet. Ett alternativ er å lage din egen nivåredigerer. Alternativet er å få tak i noen programvare som er tilgjengelig for fri bruk. De to populære verktøyene er Tiled Map Editor og Ogmo Editor. De gjør begge nivåredigering mye enklere med flere eksportalternativer.

Relaterte innlegg
  • Introduksjon til flislagt kartredigerer: Et flott plattform-agnostisk verktøy for å lage nivåkort
  • Bli kjent med Ogmo Editor: En avansert og robust nivåredigerer

Du har nettopp laget en fliserbasert motor!

Nå vet du hva et flisebasert spill er, hvordan får du noen fliser som skal brukes til nivåene dine, og hvordan du får hendene skitne og skriver koden til motoren din, og du kan til og med gjøre noen grunnleggende redigeringsoppgaver med en enkel rekkefølge . Bare husk at dette bare er begynnelsen; Det er mange eksperimenter du kan gjøre for å gjøre en enda bedre motor.

Også, jeg har gitt kildefilene for deg å sjekke ut. Slik ser den endelige SWF ut:

Last ned SWF her.

... og her, igjen, er arrayet det genereres av:

 map = [0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 0, 0, 0, 0, 6 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3];

Ikke Stopp Her!

Din neste oppgave er å gjøre litt forskning og prøve noen nye ting for å forbedre det du gjorde her. Sprite-ark er en fin måte å forbedre ytelsen på og gjøre livet lettere. Trikset er å få noen solid kode lagt ut for å lese fra et spritark slik at spillet ditt ikke trenger å lese hvert bilde individuelt.

Du bør også begynne å øve dine kunstferdigheter ved å lage noen få fliser til deg selv. På den måten kan du få den rette kunsten til fremtidige spill.

Som alltid, la noen kommentarer nedenfor om hvordan din motor jobber for deg - og kanskje til og med noen demoer slik at vi kan prøve ut ditt neste flisespill.