Een inleiding tot het maken van een tegelkaart-engine

In deze zelfstudie help ik je levels te maken voor elk gamegenre en maak je het ontwerpen van levels een stuk eenvoudiger. Je gaat leren hoe je je eerste tile map-engine kunt maken voor gebruik in al je toekomstige projecten. Ik gebruik Haxe met OpenFL, maar je zou in elke taal moeten kunnen volgen.

Dit is wat we behandelen:

  • Wat is een spel op basis van tegels?
  • Je eigen tegels vinden of maken.
  • De code schrijven om tegels op het scherm weer te geven.
  • Tegellay-outs bewerken voor verschillende niveaus.

Je kunt ook een goede start krijgen van een nieuw spelidee!


Wat is een op tegels gebaseerd spel?

Natuurlijk, Wikipedia heeft een diepgaande definitie van wat een op tegels gebaseerd spel is, maar om de basiszin te krijgen, zijn er maar een paar dingen die je moet weten:

  1. EEN tegel is een klein beeld, meestal rechthoekig of isometrisch, dat werkt als een puzzelstukje om grotere afbeeldingen te maken.
  2. EEN kaart is een groep tegels in elkaar gezet om een ​​(hopelijk) visueel aantrekkelijke "sectie" te creëren (zoals een niveau of een gebied).
  3. Tegel-gebaseerde verwijst naar de methode voor het bouwen van levels in een game. De code zal tegels in specifieke locaties opmaken om het bedoelde gebied te bedekken.

Om het nog eenvoudiger te maken, zal ik het zo stellen:

Een op tegels gebaseerd spel legt tegels uit om elk niveau te creëren.

In verwijzing naar de gemeenschappelijke tegeltypes - rechthoekig en isometrisch - zullen we rechthoekige tegels in dit artikel gebruiken vanwege hun eenvoud. Als je op een dag besluit om isometrische niveaus uit te proberen, is er extra wiskunde nodig om het te laten werken. (Zodra je hier klaar bent, bekijk deze geweldige inleiding over het maken van isometrische werelden.)

Er zijn een aantal mooie voordelen die je krijgt als je een tile-engine gebruikt. Het meest opvallende voordeel is dat je niet voor elk individueel niveau met de hand massieve afbeeldingen hoeft te maken. Dit verkort de ontwikkeltijd en verlaagt de bestandsgrootte. Het hebben van 50 1280x768px-afbeeldingen voor een game met 50 levels versus één afbeelding met 100 tegels maakt een enorm verschil.

Een ander neveneffect is dat het plaatsen van dingen op uw kaart met behulp van code een beetje gemakkelijker wordt. In plaats van dingen als botsing op basis van een exacte pixel te controleren, kunt u een snelle en eenvoudige formule gebruiken om te bepalen welke tegel u moet openen. (Ik zal dat een beetje later bespreken.)


Uw eigen tegels vinden of maken

Het eerste dat je nodig hebt bij het bouwen van je tile-engine is een set tegels. Je hebt twee opties: gebruik de tegels van iemand anders, of maak er zelf een.

Als je besluit tegels te gebruiken die al zijn gemaakt, kun je overal op internet gratis kunst vinden. Het nadeel hiervan is dat de kunst niet speciaal voor je spel is gemaakt. Aan de andere kant, als je alleen maar een prototype ontwerpt of een nieuw concept probeert te leren, zullen gratis tegels de slag slaan.

Waar vind je jouw tegels

Er zijn nogal wat bronnen beschikbaar voor gratis en open source-kunst. Hier zijn een paar plaatsen om te beginnen met zoeken:

  1. OpenGameArt.org
  2. Laten we spellen maken
  3. Pixel Prospector

Die drie links zouden je meer dan genoeg plaatsen moeten geven om fatsoenlijke tegels voor je prototypen te vinden. Voordat je helemaal gek wordt om alles wat je vindt te pakken te krijgen, zorg er dan voor dat je begrijpt op welke licentie alles valt en welke beperkingen ze met zich meebrengen. Met veel licenties kunt u de kunst vrij en voor commercieel gebruik gebruiken, maar hiervoor is mogelijk attributie vereist.

Voor deze zelfstudie heb ik een aantal tegels uit een The Game Game-bundel voor platformers gebruikt. U kunt mijn verkleinde versies of de originelen downloaden.

Hoe je je eigen tegels kunt maken

Als je nog niet de stap hebt genomen om nog kunst te maken voor je games, is het misschien een beetje intimiderend. Gelukkig zijn er een aantal verbazingwekkende en eenvoudige stukjes software die je er diep in krijgen, zodat je kunt beginnen met oefenen.

Veel ontwikkelaars beginnen met pixelart voor hun games en hier zijn een paar geweldige tools voor:

  1. Aseprite
  2. Pyxel Bewerken
  3. Graphics Gale
  4. Pixen voor de Mac-gebruikers

Dit zijn enkele van de meest populaire programma's voor het maken van pixelart. Als u iets krachtigers wilt, is GIMP een uitstekende optie. Je kunt ook wat vectorkunst met Inkscape doen en een paar geweldige tutorials volgen bij 2D Game Art For Programmers.

Zodra je de software hebt gepakt, kun je gaan experimenteren met het maken van je eigen tegels. Aangezien deze zelfstudie bedoeld is om u te laten zien hoe u uw tegelkaart kunt maken motor Ik zal niet te veel ingaan op het maken van de tegels zelf, maar er is één ding om altijd in gedachten te houden:

Zorg ervoor dat je tegels naadloos in elkaar passen en voeg wat variatie toe om ze interessant te houden.

Als je tegels duidelijke lijnen tussen hen laten zien wanneer ze worden samengevoegd, ziet je spel er niet erg goed uit. Zorg ervoor dat je wat tijd besteedt aan het maken van een mooie "puzzel" van tegels door ze naadloos te maken en wat variatie toe te voegen.


De code schrijven voor het weergeven van tegels

Nu hebben we al dat kunst spul uit de weg, kunnen we in de code duiken om je nieuw verworven (of gemaakte) tegels op het scherm te zetten.

Hoe een enkele tegel op het scherm wordt weergegeven

Laten we beginnen met de allereenvoudigste taak om een ​​enkele tegel op het scherm weer te geven. Zorg ervoor dat je tegels allemaal hetzelfde formaat hebben en worden opgeslagen in afzonderlijke afbeeldingsbestanden (we zullen later meer praten over Sprite-bladen).

Zodra u de tegels in uw activamap van uw project hebt, kunt u een heel eenvoudig schrijven Tegel klasse. Hier is een voorbeeld in Haxe:

import flash.display.Sprite; import flash.display.Bitmap; import openfl.Assets; class Tile breidt Sprite uit private var image: Bitmap; publieke functie nieuw () super (); image = new Bitmap (Assets.getBitmapData ("assets / grassLeftBlock.png")); addChild (afbeelding); 

Omdat het enige wat we nu doen, is om een ​​enkele tegel op het scherm te plaatsen, het enige dat de klasse doet, is de tegelafbeelding importeren uit de middelen map en voeg het als een kind toe aan het object. Deze klasse zal waarschijnlijk sterk variëren op basis van de programmeertaal die u gebruikt, maar u moet gemakkelijk een handleiding kunnen vinden over hoe u een afbeelding op het scherm kunt weergeven.

Nu dat we een hebben Tegel klasse, moeten we een instantie van een maken Tegel en voeg het toe aan onze hoofdklasse:

import flash.display.Sprite; import flash.events.Event; import flash.Lib; class Main breidt Sprite uit public function new () super (); var tile = new Tile (); addChild (tegel);  public static function main () Lib.current.addChild (new Main ()); 

De Hoofd klas maakt een nieuw Tegel wanneer de constructor (de nieuwe() functie, genoemd wanneer het spel start) wordt aangeroepen en wordt toegevoegd aan de weergavelijst.

Wanneer het spel wordt uitgevoerd, de hoofd() functie zal ook worden aangeroepen, en een nieuwe Hoofd object wordt toegevoegd aan het werkgebied. Je zou nu je tegel helemaal bovenaan links op het scherm moeten laten verschijnen. Tot nu toe, zo goed.

Tip: Als dat allemaal niet klopt, maak je geen zorgen! Het is gewoon de standaard Haxe-code. Het belangrijkste om te begrijpen is dat dit een Tegel object en voegt het toe aan het scherm.

Arrays gebruiken om een ​​volledig tegelscherm op te stellen

De volgende stap is om een ​​manier te vinden om het volledige scherm met tegels te vullen. Een van de gemakkelijkste manieren om dit te doen is om een ​​array te vullen met gehele getallen die elk een a vertegenwoordigen tegel-ID. Vervolgens kunt u de array doorlopen om te beslissen welke tegel moet worden weergegeven en waar deze moet worden weergegeven.

U hebt de mogelijkheid om een ​​standaardarray te gebruiken of een tweedimensionale array te gebruiken. Als u niet bekend bent met 2D-arrays, is het in feite een enkele array die is gevuld met meerdere arrays. De meeste talen zullen dit aangeven nameOfArray [x] [y] waar X en Y zijn indices voor toegang tot een enkel element.

Sinds X en Y worden ook gebruikt voor schermcoördinaten, het kan zinvol zijn om te gebruiken X en Y als coördinaten voor onze tegels. De truc met 2D-arrays is het begrijpen hoe de gehele getallen zijn georganiseerd. Mogelijk moet u het Y en X bij het itereren door de arrays (zie voorbeeld hieronder).

privé var voorbeeldArr = [[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]];

Merk op dat in deze implementatie het "0e" -element in de array zelf een array is, bestaande uit vijf gehele getallen. Dit zou betekenen dat je elk element opent met Y eerste, dan X. Als u probeert toegang te krijgen exampleArr [1] [0], je zou toegang hebben tot de zesde tegel.

Als u niet helemaal begrijpt hoe de 2D-arrays nu werken, hoeft u zich geen zorgen te maken. Voor deze zelfstudie zal ik een normale array gebruiken om dingen eenvoudig te houden en dingen gemakkelijker te visualiseren:

private var exampleArr = [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];

Het bovenstaande voorbeeld laat zien hoe een normale array een beetje eenvoudiger kan zijn. We kunnen precies visualiseren waar de tegel zal zijn en alles wat we moeten doen is een eenvoudige formule gebruiken (maak je geen zorgen, het komt eraan!) Om de tegel te krijgen die we willen.

Laten we nu wat code schrijven om onze array te maken en deze te vullen met enen. De nummer één is de ID die onze eerste tegel vertegenwoordigt.

Eerst moeten we een variabele binnen onze hoofdklasse maken om onze array vast te houden:

privé var-kaart: Array;

Dit ziet er misschien een beetje vreemd uit, dus ik zal het voor je opsplitsen.

De naam van de variabele is kaart en ik heb het een heel specifiek type gegeven: reeks. De gedeelte vertelt ons programma gewoon dat de array alleen gehele getallen zal bevatten. Arrays kunnen vrijwel elk type bevatten dat u wilt, dus als u iets anders gebruikt om uw tegels te identificeren, kunt u hier de parameter wijzigen.

Vervolgens moeten we wat code toevoegen aan onze Hoofd class's constructor (onthoud, dit is het nieuwe() functie) zodat we een exemplaar van onze kaart kunnen maken:

kaart = nieuwe matrix();

Deze regel maakt een lege array die we binnenkort kunnen vullen met onze array om deze uit te testen. Laten we eerst enkele waarden definiëren die ons zullen helpen met onze wiskunde:

openbare statische var TILE_WIDTH = 60; openbare statische var TILE_HEIGHT = 60; openbare statische var SCREEN_WIDTH = 600; openbare statische var SCREEN_HEIGHT = 360;

Ik heb deze waarden gemaakt openbare statische omdat dit ons toegang geeft vanaf waar dan ook in ons programma (via Main.Tile_WIDTH enzovoorts). Je hebt misschien ook die verdeling opgemerkt SCREEN_WIDTH door TILE_WIDTH geeft ons 10 en delen SCREEN_HEIGHT door TILE_HEIGHT geeft ons 6. Deze twee getallen worden gebruikt om te bepalen hoeveel gehele getallen in onze array moeten worden opgeslagen.

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

In dit blok code wijzen we toewijzen 10 en 6 naar w en h, zoals ik hierboven vermeldde. Dan moeten we in a springen voor loop om een ​​array te maken die past 10 * 6 integers. Dit zorgt voor voldoende tegels om het hele scherm te vullen.

Nu hebben we onze basiskaart gebouwd, maar hoe gaan we de tegels vertellen om op hun juiste plaats te komen? Daarvoor moeten we teruggaan naar de Tegel class en creëer een functie waarmee we tegels naar believen kunnen verplaatsen:

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

Wanneer we de setLoc () functie, passeren we in de X en Y coördineert volgens onze kaartklasse (formule komt binnenkort, dat beloof ik!). De functie neemt deze waarden en vertaalt deze in pixelcoördinaten door ze te vermenigvuldigen met TILE_WIDTH en TILE_HEIGHT, respectievelijk.

Het enige dat je hoeft te doen om onze tegels op het scherm te krijgen, is door het onze te vertellen Hoofd klasse om de tegels te maken en deze op hun plaats te zetten op basis van hun locaties op de kaart. Laten we teruggaan naar Hoofd en implementeer dat:

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

O ja! Dat klopt, we hebben nu een scherm vol met tegels. Laten we opsplitsen wat er hierboven gebeurt.

De Formule

De formule die ik blijf noemen is eindelijk hier.

We berekenen X door de modulus (%) van ik en w (dat is 10, onthoud).

De modulus is slechts de rest na integer deling: \ (14 \ div 3 = 4 \ text remainder 2 \) so \ (14 \ text modulus 3 = 2 \).

We gebruiken dit omdat we onze waarde van willen X terugzetten naar 0 aan het begin van elke rij, dus we tekenen de respectieve steen helemaal links:


Wat betreft Y, we nemen de verdieping() van i / w (dat wil zeggen, we rond dat resultaat af) omdat we alleen willen Y toenemen zodra we het einde van elke rij hebben bereikt en een niveau omlaag zijn gegaan:


Tip: Voor de meeste talen is het niet nodig om te bellen Math.floor () bij het delen van gehele getallen; Haxe behandelt integer delen gewoon anders. Als je een taal gebruikt die integer delen doet, kun je die gewoon gebruiken i / w (ervan uitgaande dat ze beide gehele getallen zijn).

Ten slotte wilde ik iets vertellen over schuifniveaus. Meestal maakt u geen levels die perfect passen in uw viewport. Uw kaarten zullen waarschijnlijk veel groter zijn dan het scherm en u wilt niet doorgaan met het tekenen van afbeeldingen die de speler niet kan zien. Met wat snelle en eenvoudige wiskunde moet je kunnen berekenen welke tegels op het scherm moeten staan ​​en welke tegels je moet tekenen.

Bijvoorbeeld: uw schermgrootte is 500x500, uw tegels zijn 100x100 en uw wereldformaat is 1000x1000. U hoeft alleen maar een snelle controle uit te voeren voordat u tegels tekent om erachter te komen welke tegels op het scherm staan. Als u de locatie van uw viewport gebruikt, laten we zeggen 400x500, hoeft u alleen tegels te tekenen uit rijen 4 tot 9 en kolommen 5 tot 10. U kunt die getallen opvragen door de locatie te delen door de tegelgrootte en vervolgens die waarden te verrekenen met het scherm grootte gedeeld door de tegelgrootte. Eenvoudig.

Het ziet er misschien nog niet zo uit, omdat alle tegels hetzelfde zijn, maar de basis is er bijna. De enige dingen die je nog moet doen, zijn verschillende soorten tegels maken en onze kaart zo ontwerpen dat ze in de rij staan ​​en iets leuks maken.


Tegellijsten voor verschillende niveaus bewerken

Oké, we hebben nu een kaart vol met die welke het scherm bedekt. Op dit punt zou je meer dan één tegeltype moeten hebben, wat betekent dat we onze tegels moeten veranderen Tegel constructeur om dat te verklaren:

publieke functie nieuw (id: Int) super (); switch (id) case 1: image = new Bitmap (Assets.getBitmapData ("assets / grassLeftBlock.png")); case 2: image = new Bitmap (Assets.getBitmapData ("assets / grassCenterBlock.png")); case 3: image = new Bitmap (Assets.getBitmapData ("assets / grassRightBlock.png")); case 4: image = new Bitmap (Assets.getBitmapData ("assets / goldBlock.png")); case 5: image = new Bitmap (Assets.getBitmapData ("assets / globe.png")); case 6: image = new Bitmap (Assets.getBitmapData ("assets / mushroom.png"));  addChild (afbeelding); 

Omdat ik zes verschillende tegels heb gebruikt voor mijn kaart, had ik een schakelaar verklaring die de nummers één tot en met zes behandelt. Je zult merken dat de constructor nu een geheel getal als parameter neemt, zodat we weten wat voor soort tegel je moet creëren.

Nu moeten we teruggaan naar onze Hoofd constructor en repareer de tegelcreatie in onze voor lus:

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

Het enige wat we moesten doen was slagen voor de map [i] naar de Tegel constructeur om het weer te laten werken. Als u probeert uit te voeren zonder een geheel getal door te geven aan Tegel, het geeft je een aantal fouten.

Bijna alles is op zijn plaats, maar nu hebben we een manier nodig om kaarten te ontwerpen in plaats van ze alleen te vullen met willekeurige tegels. Verwijder eerst de voor loop in de Hoofd constructor die elk element op één instelt. Dan kunnen we onze eigen kaart met de hand maken:

 kaart = [0, 4, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 0, 0, 0, 0, 6 , 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 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];

Als je de array opmaakt zoals hierboven, kun je gemakkelijk zien hoe onze tegels worden georganiseerd. Je kunt de array ook gewoon invoeren als een lange rij getallen, maar de eerste is leuk omdat je 10 over en 6 naar beneden kunt zien.

Wanneer u het programma nu probeert uit te voeren, krijgt u enkele uitzonderingen voor ongeldige aanwijzers. Het probleem is dat we nullen gebruiken op onze kaart en onze Tegel klasse weet niet wat te doen met een nul. Eerst repareren we de constructor door een vinkje toe te voegen voordat we het afbeelding-kind toevoegen:

 if (image! = null) addChild (afbeelding);

Deze snelle controle zorgt ervoor dat we geen nul kinderen toevoegen aan de Tegel objecten die we maken. De laatste wijziging voor deze klasse is de setLoc () functie. Op dit moment proberen we de X en Y waarden voor een variabele die niet is geïnitialiseerd.

Laten we nog een snelle controle toevoegen:

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

Met deze twee eenvoudige oplossingen en de tegels die ik hierboven heb opgegeven, zou je in staat moeten zijn om het spel uit te voeren en een eenvoudig niveau van een platformer te zien. Omdat we 0 als een "niet-tegel" hebben achtergelaten, kunnen we deze transparant (leeg) laten. Als je het niveau wat meer pit wilt geven, kun je een achtergrond plaatsen voordat je de tegels neerzet; Ik heb zojuist een lichtblauw verloop toegevoegd om eruit te zien als een lucht op de achtergrond.

Dat is vrijwel alles wat je nodig hebt om een ​​eenvoudige manier in te stellen om je niveaus te bewerken. Probeer een beetje met de array te experimenteren en zie welke ontwerpen je kunt bedenken voor andere niveaus.

Software van derden voor het ontwerpen van niveaus

Zodra je de basis hebt, kun je overwegen om een ​​aantal andere hulpmiddelen te gebruiken om snel levels te ontwerpen en te zien hoe ze eruit zien voordat je ze in het spel gooit. Een optie is om uw eigen niveau-editor te maken. Het alternatief is om wat software te pakken die gratis beschikbaar is om te gebruiken. De twee populaire hulpmiddelen zijn de Tiled Map Editor en Ogmo Editor. Beide maken het bewerken op niveau veel gemakkelijker met meerdere exportopties.

gerelateerde berichten
  • Introductie tot Tiled Map Editor: een geweldige platform-agnostische tool voor het maken van level maps
  • Kennismaken met Ogmo Editor: een geavanceerde en robuuste level-editor

U hebt zojuist een tegel-gebaseerde motor gemaakt!

Nu weet je wat een spel op tegels is, hoe je wat tegels kunt gebruiken voor je levels en hoe je je handen vies kunt maken en de code voor je engine kunt schrijven, en je kunt zelfs wat basisniveaubewerking doen met een eenvoudige array . Onthoud alleen dat dit slechts het begin is; er wordt veel geëxperimenteerd om een ​​nog betere motor te maken.

Ik heb ook de bronbestanden opgegeven die u kunt uitchecken. Dit is hoe de laatste SWF eruitziet:

Download hier de SWF.

... en hier, nogmaals, is de array waaruit hij is gegenereerd:

 kaart = [0, 4, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 0, 0, 0, 0, 6 , 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 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];

Stop hier niet!

Je volgende taak is om wat onderzoek te doen en enkele nieuwe dingen uit te proberen om wat je hier hebt gemaakt te verbeteren. Sprite-vellen zijn een geweldige manier om de prestaties te verbeteren en het leven een beetje gemakkelijker te maken. De kunst is om een ​​degelijke code op te stellen voor het lezen van een sprite-blad, zodat je spel niet elke afbeelding afzonderlijk hoeft te lezen.

Je moet ook beginnen met het oefenen van je kunstvaardigheden door zelf een paar tilesets te maken. Op die manier kun je de juiste kunst voor je toekomstige spellen krijgen.

Laat zoals altijd wat opmerkingen achter over hoe jouw engine voor jou werkt - en misschien zelfs een paar demo's, zodat we je volgende tegelgame kunnen uitproberen.