In dit laatste deel van de zelfstudiereeks bouwen we verder op de eerste zelfstudie en leren we over het implementeren van elementen, triggers, niveau-uitwisseling, padvinden, pad volgen, niveau scrollen, isometrische hoogte en isometrische projectielen.
Pickups zijn items die binnen het niveau kunnen worden verzameld, normaal gesproken gewoon door eroverheen te lopen, bijvoorbeeld munten, edelstenen, contanten, munitie, enz..
Ophaalgegevens kunnen direct in onze niveaugegevens worden ondergebracht, zoals hieronder:
[[1,1,1,1,1,1], [1,0,0,0,0,1], [1,0,8,0,0,1], [1,0,0, 8,0,1], [1,0,0,0,0,1], [1,1,1,1,1,1]]
In deze niveau-gegevens gebruiken we 8
om een pick-up op een grastegel aan te duiden (1
en 0
vertegenwoordig respectievelijk muren en beloopbare tegels, zoals eerder). Dit kan een afbeelding met één tegel zijn met een grastegel die is bedekt met de afbeelding van de afbeelding. Gaan volgens deze logica, hebben we twee verschillende tegelstatussen nodig voor elke tegel met een ophaalelement, dat wil zeggen een met pickup en een zonder te worden getoond nadat de pickup wordt verzameld.
Typische isometrische kunst heeft meerdere beloopbare tegels - stel dat we er 30 hebben. De bovenstaande benadering betekent dat als we N-pickups hebben, we N x 30 tegels nodig hebben naast de 30 originele tegels, omdat elke tegel één versie met pickups en één zonder. Dit is niet erg efficiënt; in plaats daarvan moeten we proberen deze combinaties dynamisch te maken.
Om dit op te lossen, kunnen we dezelfde methode gebruiken die we hebben gebruikt om de held in de eerste zelfstudie te plaatsen. Wanneer we een pick-uptegel tegenkomen, plaatsen we eerst een grastegel en plaatsen we de pick-up bovenop de grastegel. Op deze manier hebben we alleen N ophaaltegels nodig naast 30 beloopbare tegels, maar we zouden getalwaarden nodig hebben om elke combinatie in de niveaugegevens te vertegenwoordigen. Om de noodzaak van N x 30 representatiewaarden op te lossen, kunnen we een aparte houden pickupArray
om de pickup-gegevens exclusief op te slaan van de levelData
. Het voltooide niveau met de pickup wordt hieronder getoond:
Voor ons voorbeeld houd ik dingen eenvoudig en gebruik ik geen extra array voor pickups.
Het detecteren van pickups gebeurt op dezelfde manier als het detecteren van botsingstegels, maar na het personage verplaatsen.
if (onPickupTile ()) pickupItem (); function onPickupTile () // controleer of er een pickup is op hero tile return (levelData [heroMapTile.y] [heroMapTile.x] == 8);
In de functie onPickupTile ()
, we controleren of het levelData
matrixwaarde op de heroMapTile
coördinaat is een ophaaltegel of niet. Het nummer in de levelData
matrix op die tegelcoördinaat geeft het type ophaling aan. We controleren op botsingen voordat we het personage verplaatsen, maar moeten achteraf controleren of er pickups zijn, want in het geval van botsingen mag het personage de plek niet innemen als het al bezet is door de aanvaringssteen, maar in het geval van pickups is het personage vrij om te bewegen overheen.
Een ander ding om op te merken is dat de aanvaringsgegevens meestal nooit veranderen, maar de pickup-gegevens veranderen telkens wanneer we een item oppakken. (Dit gaat meestal alleen over het wijzigen van de waarde in de levelData
array van, laten we zeggen, 8
naar 0
.)
Dit leidt tot een probleem: wat gebeurt er wanneer we het niveau opnieuw moeten opstarten en dus alle elementen terugzetten naar hun oorspronkelijke posities? We hebben niet de informatie om dit te doen, zoals de levelData
array is gewijzigd toen de speler items oppikte. De oplossing is om tijdens het spelen een dubbele array te gebruiken voor het niveau en om het origineel te behouden levelData
array intact. We gebruiken bijvoorbeeld levelData
en levelDataLive []
, kloon de laatste uit de eerste aan het begin van het level en verander alleen levelDataLive []
tijdens het spelen.
Voor het voorbeeld paai ik een willekeurige pick-up op een lege grastegel na elke pick-up en verhoog de pickupCount
. De pickupItem
functie ziet er zo uit.
function pickupItem () pickupCount ++; levelData [heroMapTile.y] [heroMapTile.x] = 0; // spawn volgende pickup spawnNewPickup ();
Je zou moeten opmerken dat we op zoek gaan naar pickups wanneer het personage op die tegel staat. Dit kan meerdere keren binnen een seconde gebeuren (we controleren alleen wanneer de gebruiker beweegt, maar we kunnen binnen een tegel rond en rond gaan), maar de bovenstaande logica zal niet falen; sinds we de levelData
matrixgegevens naar 0
de eerste keer dat we een pick-up detecteren, alle volgende onPickupTile ()
cheques komen terug vals
voor die tegel. Bekijk het interactieve voorbeeld hieronder:
Zoals de naam al doet vermoeden, veroorzaken triggertegels dat er iets gebeurt wanneer de speler er op stapt of op een toets drukt wanneer deze wordt ingedrukt. Ze kunnen de speler naar een andere locatie teleporteren, een poort openen of een vijand spawnen om een paar voorbeelden te geven. In zekere zin zijn pick-ups slechts een speciale vorm van triggertegels: wanneer de speler op een tegel met een munt stapt, verdwijnt de munt en neemt de muntteller toe..
Laten we eens kijken naar hoe we een deur kunnen implementeren die de speler naar een ander niveau tilt. De tegel naast de deur is een triggertegel; wanneer de speler op de X toets, ze gaan naar het volgende niveau.
Om niveaus te veranderen, hoeven we alleen de stroom om te wisselen levelData
matrix met die van het nieuwe niveau en stel de nieuwe in heroMapTile
positie en richting voor het heldenkarakter. Stel dat er twee niveaus zijn met deuren om tussen hen door te kunnen gaan. Omdat de grondtegel naast de deur de triggertegel is in beide niveaus, kunnen we deze gebruiken als de nieuwe positie voor het personage wanneer deze in het niveau verschijnen.
De implementatielogica is hier hetzelfde als voor pickups, en opnieuw gebruiken we de levelData
array om triggerwaarden op te slaan. Voor ons voorbeeld, 2
duidt een deurtegel aan en de waarde ernaast is de trigger. ik heb gebruikt 101
en 102
met de basisconventie dat elke tegel met een waarde groter dan 100 een triggertegel is en de waarde minus 100 het niveau kan zijn waartoe het leidt:
var level1Data = [[1,1,1,1,1,1], [1,1,0,0,0,1], [1,0,0,0,0,1], [2,102,0 , 0,0,1], [1,0,0,0,1,1], [1,1,1,1,1,1]]; var level2Data = [[1,1,1,1,1,1], [1,0,0,0,0,1], [1,0,8,0,0,1], [1,0 0,0101,2], [1,0,1,0,0,1], [1,1,1,1,1,1]];
De code voor het controleren op een triggergebeurtenis wordt hieronder weergegeven:
var xKey = game.input.keyboard.addKey (Phaser.Keyboard.X); xKey.onUp.add (triggerListener); // voeg een signaallistener toe voor de functie up-gebeurtenis triggerListener () var trigger = levelData [heroMapTile.y] [heroMapTile.x]; if (trigger> 100) // geldige trigger tile trigger-= 100; if (trigger == 1) // schakel over naar level 1 levelData = level1Data; else // schakel naar niveau 2 levelData = level2Data; voor (var i = 0; i < levelData.length; i++) for (var j = 0; j < levelData[0].length; j++) trigger=levelData[i][j]; if(trigger>100) // vind de nieuwe triggertegel en plaats de held daar heldMapTile.y = j; heroMapTile.x = i; heroMapPos = nieuwe Phaser.Point (heroMapTile.y * tileWidth, heroMapTile.x * tileWidth); heroMapPos.x + = (tileWidth / 2); heroMapPos.y + = (tileWidth / 2);
De functie triggerListener ()
controleert of de waarde van de triggergegevensmatrix op de gegeven coördinaat groter is dan 100. Zo ja, dan vinden we op welk niveau we moeten overschakelen door 100 van de tegeldoelwaarde af te trekken. De functie vindt de triggertegel in de nieuwe levelData
, wat de spawn-positie voor onze held zal zijn. Ik heb de trigger geactiveerd om te activeren wanneer X is vrijgelaten; als we gewoon luisteren naar de ingedrukte toets, komen we in een lus terecht waar we tussen niveaus wisselen zolang de toets ingedrukt wordt gehouden, omdat het personage altijd in het nieuwe level spaait bovenop een triggertegel.
Hier is een werkende demo. Probeer voorwerpen op te pakken door erover te lopen en niveaus te wisselen door naast deuren te staan en te slaan X.
EEN projectiel is iets dat met een bepaalde snelheid in een bepaalde richting beweegt, zoals een kogel, een magische spreuk, een bal, enz. Alles aan het projectiel is hetzelfde als het heldenkarakter, behalve de hoogte: in plaats van over de grond te rollen, projectielen zweven vaak boven op een bepaalde hoogte. Een kogel zal boven het middel van het personage uitkomen, en zelfs een bal moet misschien rondkaatsen.
Een interessant ding om op te merken is dat de isometrische hoogte hetzelfde is als de hoogte in een 2D zijaanzicht, hoewel kleiner in waarde. Er zijn geen ingewikkelde conversies bij betrokken. Als een bal 10 tekens boven de grond ligt in cartesiaanse coördinaten, kan deze 10 of 6 pixels boven de grond liggen in isometrische coördinaten. (In ons geval is de relevante as de y-as.)
Laten we proberen een bal te laten stuiteren in ons ommuurde grasland. Als een vleugje realisme voegen we een schaduw toe voor de bal. Het enige wat we moeten doen is de bouncehoogtewaarde toevoegen aan de isometrische Y-waarde van onze bal. De spronghoogtewaarde verandert van frame naar frame afhankelijk van de zwaartekracht. Zodra de bal de grond raakt, schakelen we de huidige snelheid langs de y-as om.
Voordat we gaan stuiteren in een isometrisch systeem, zullen we zien hoe we het kunnen implementeren in een 2D Cartesiaans systeem. Laten we de sprongkracht van de bal voorstellen met een variabele zValue
. Stel je voor dat, om te beginnen, de bal een sprongkracht van 100 heeft, dus zValue = 100
.
We gebruiken nog twee variabelen: incrementValue
, die begint om 0
, en zwaartekracht
, met een waarde van -1
. Elk frame trekken we af incrementValue
van zValue
, en trek af zwaartekracht
van incrementValue
om een dempend effect te creëren. Wanneer zValue
bereikt 0
, het betekent dat de bal de grond heeft bereikt; op dit punt draaien we het teken van incrementValue
door het te vermenigvuldigen met -1
, verander het in een positief aantal. Dit betekent dat de bal vanaf het volgende frame naar boven zal bewegen, en dus stuitert.
Dit is hoe dat eruit ziet in de code:
if (game.input.keyboard.isDown (Phaser.Keyboard.X)) zValue = 100; incrementValue- = zwaartekracht; zValue- = incrementValue; if (zValue<=0) zValue=0; incrementValue*=-1;
De code blijft hetzelfde voor de isometrische weergave, met het kleine verschil dat u een lagere waarde kunt gebruiken voor zValue
om te beginnen met. Zie hieronder hoe het zValue
wordt toegevoegd aan de isometrische Y
waarde van de bal tijdens het renderen.
functie drawBallIso () var isoPt = new Phaser.Point (); // Het is niet raadzaam om punten te maken in de updatelus var ballCornerPt = new Phaser.Point (ballMapPos.x-ball2DVolume.x / 2, ballMapPos.y-ball2DVolume .Y / 2); isoPt = cartesianToIsometric (ballCornerPt); // vind nieuwe isometrische positie voor held uit 2D-kaartpositie gameScene.renderXY (ballShadowSprite, isoPt.x + borderOffset.x + shadowOffset.x, isoPt.y + borderOffset.y + shadowOffset.y, false ) // schaduw tekenen om textuur te maken gameScene.renderXY (ballSprite, isoPt.x + borderOffset.x + ballOffset.x, isoPt.y + borderOffset.y-ballOffset.y-zValue, false); // teken held om weer te geven textuur
Bekijk het interactieve voorbeeld hieronder:
Begrijp wel dat de rol die de schaduw speelt een zeer belangrijke is die bijdraagt aan het realisme van deze illusie. Merk ook op dat we nu de twee schermcoördinaten (x en y) gebruiken om drie dimensies in isometrische coördinaten te vertegenwoordigen - de y-as in schermcoördinaten is ook de z-as in isometrische coördinaten. Dit kan verwarrend zijn!
Pathfinding en pad volgen zijn vrij gecompliceerde processen. Er zijn verschillende benaderingen met verschillende algoritmen om het pad tussen twee punten te vinden, maar dan als onze levelData
is een 2D-array, dingen zijn gemakkelijker dan ze anders zouden zijn. We hebben goed gedefinieerde en unieke knooppunten die de speler kan bezetten, en we kunnen gemakkelijk controleren of ze beloopbaar zijn.
Een gedetailleerd overzicht van padbepalingsalgoritmen valt buiten de scope van dit artikel, maar ik zal proberen uit te leggen wat de meest gebruikelijke manier is: het kortste padalgoritme, waarvan A * en Dijkstra's algoritmen beroemde implementaties zijn.
We streven ernaar om knooppunten te vinden die een startknooppunt en een eindknooppunt met elkaar verbinden. Vanaf het startknooppunt bezoeken we alle acht aangrenzende knooppunten en markeren ze allemaal als bezocht; dit kernproces wordt recursief herhaald voor elk nieuw bezocht knooppunt.
Elke thread volgt de bezochte knooppunten. Bij het springen naar naburige knooppunten worden knooppunten die al zijn bezocht overgeslagen (de recursie stopt); anders gaat het proces door totdat we het eindknooppunt bereiken, waar de recursie eindigt en het volledige gevolgde pad wordt geretourneerd als een knooppuntarray. Soms wordt het eindknooppunt nooit bereikt, in welk geval de routering mislukt. Meestal vinden we uiteindelijk meerdere paden tussen de twee knooppunten, in welk geval we degene nemen met het kleinste aantal knooppunten.
Het is niet verstandig om het wiel opnieuw uit te vinden als het gaat om goed gedefinieerde algoritmen, dus zouden we bestaande oplossingen gebruiken voor onze path-finding doeleinden. Om Phaser te gebruiken, hebben we een JavaScript-oplossing nodig, en degene die ik heb gekozen is EasyStarJS. We initialiseren de path-finding engine zoals hieronder.
easystar = nieuwe EasyStar.js (); easystar.setGrid (levelData); easystar.setAcceptableTiles ([0]); easystar.enableDiagonals (); // we willen dat pad diagonalen heeft easystar.disableCornerCutting (); // geen diagonaal pad bij wandhoeken
Als onze levelData
heeft alleen 0
en 1
, we kunnen het direct doorgeven als de knooppuntarray. We hebben de waarde ingesteld van 0
als het beloopbare knooppunt. We schakelen diagonaal loopvermogen in maar schakelen dit uit wanneer we dicht bij de hoeken van niet-beloopbare tegels lopen.
Dit is omdat, indien ingeschakeld, de held tijdens het diagonaal stappen in de niet-beloopbare tegel kan snijden. In een dergelijk geval zal onze botsingdetectie de held niet laten passeren. Houd er ook rekening mee dat ik in het voorbeeld de botsingdetectie volledig heb verwijderd, omdat dat niet langer nodig is voor een op een AI gebaseerd voorbeeld van een wandeling.
We zullen de tik op een willekeurige vrije tegel binnen het niveau detecteren en het pad berekenen met behulp van de FindPath
functie. De callback-methode plotAndMove
ontvangt de knooppuntarray van het resulterende pad. We markeren het minimap
met het nieuw gevonden pad.
game.input.activePointer.leftButton.onUp.add (findPath) functie findPath () als (isFindingPath || isWalking) terug; var pos = game.input.activePointer.position; var isoPt = nieuwe Phaser.Point (pos.x-borderOffset.x, pos.y-borderOffset.y); tapPos = isometricToCartesian (isoPt); tapPos.x- = tileWidth / 2; // aanpassing om de juiste tegel voor fouten te vinden vanwege afronding tapPos.y + = tileWidth / 2; tapPos = getTileCoordinates (tapPos, tileWidth); if (tapPos.x> -1 && tapPos.y> -1 && tapPos.x<7&&tapPos.y<7)//tapped within grid if(levelData[tapPos.y][tapPos.x]!=1)//not wall tile isFindingPath=true; //let the algorithm do the magic easystar.findPath(heroMapTile.x, heroMapTile.y, tapPos.x, tapPos.y, plotAndMove); easystar.calculate(); function plotAndMove(newPath) destination=heroMapTile; path=newPath; isFindingPath=false; repaintMinimap(); if (path === null) console.log("No Path was found."); else path.push(tapPos); path.reverse(); path.pop(); for (var i = 0; i < path.length; i++) var tmpSpr=minimap.getByName("tile"+path[i].y+"_"+path[i].x); tmpSpr.tint=0x0000ff; //console.log("p "+path[i].x+":"+path[i].y);
Zodra we het pad als een knooppuntarray hebben, moeten we ervoor zorgen dat het personage dit blijft volgen.
Stel dat we het personage willen laten lopen naar een tegel waarop we klikken. We moeten eerst zoeken naar een pad tussen het knooppunt dat het personage momenteel inneemt en het knooppunt waarop we hebben geklikt. Als een succesvol pad wordt gevonden, moeten we het teken naar het eerste knooppunt in de knooppuntarray verplaatsen door het als bestemming in te stellen. Zodra we het bestemmingsknooppunt hebben bereikt, controleren we of er meer knooppunten in de knooppuntarray zijn en, zo ja, stel het volgende knooppunt in als bestemming - en zo verder totdat we het laatste knooppunt bereiken.
We zullen ook de richting van de speler veranderen op basis van het huidige knooppunt en het nieuwe bestemmingsknooppunt telkens wanneer we een knooppunt bereiken. Tussen knooppunten lopen we gewoon in de gewenste richting totdat we het bestemmingsknooppunt bereiken. Dit is een heel eenvoudige AI, en in het voorbeeld gebeurt dit in de methode aiWalk
hieronder gedeeltelijk weergegeven.
function aiWalk () if (path.length == 0) // pad is geëindigd als (heroMapTile.x == destination.x && heroMapTile.y == destination.y) dX = 0; dY = 0; isWalking = false; terug te keren; isWalking = true; if (heroMapTile.x == destination.x && heroMapTile.y == destination.y) // bereikte huidige bestemming, stel nieuw in, verander van richting // wacht tot we enkele stappen in de tegel zijn voordat we stepsTaken ++ draaien; if (stepsTakendestination.x) dX = -1; else dX = 0; if (heroMapTile.y destination.y) dY = -1; else dY = 0; if (heroMapTile.x == destination.x) dX = 0; else if (heroMapTile.y == destination.y) dY = 0; // ...
Wij do moeten geldige klikpunten filteren door te bepalen of we binnen het beloopbare gebied hebben geklikt in plaats van een muurtegel of een andere niet-betreedbare tegel.
Nog een interessant punt voor het coderen van de AI: we willen niet dat het personage zich naar de volgende tegel in de knooppuntarray wendt zodra hij in de huidige is gearriveerd, omdat een dergelijke onmiddellijke draai resulteert in ons personage dat op de grenzen van tegels. In plaats daarvan moeten we wachten tot het personage een paar stappen binnen de tegel is voordat we naar de volgende bestemming zoeken. Het is ook beter om de held handmatig in het midden van de huidige tegel te plaatsen vlak voordat we ons draaien, om alles perfect te laten voelen.
Bekijk de werkende demo hieronder:
Wanneer het niveaugebied veel groter is dan het beschikbare schermgebied, zullen we het moeten maken rol.
Het zichtbare schermgebied kan worden beschouwd als een kleinere rechthoek binnen de grotere rechthoek van het volledige niveaugedeelte. Scrollen is in wezen gewoon het verplaatsen van de binnenste rechthoek in de grotere. Meestal, wanneer een dergelijk scrollen gebeurt, blijft de positie van de held hetzelfde met betrekking tot de schermrechthoek, meestal op het midden van het scherm. Interessant genoeg hoeven we alleen het hoekpunt van de binnenste rechthoek te volgen om scrollen mogelijk te maken.
Dit hoekpunt, dat we in cartesiaanse coördinaten weergeven, zal binnen een tegel in de niveaugegevens vallen. Voor scrollen verhogen we de x- en y-positie van het hoekpunt in cartesiaanse coördinaten. Nu kunnen we dit punt omzetten naar isometrische coördinaten en het gebruiken om het scherm te tekenen.
De nieuw geconverteerde waarden, in de isometrische ruimte, moeten ook de hoek van ons scherm zijn, wat betekent dat ze de nieuwe zijn (0, 0)
. Dus tijdens het ontleden en tekenen van de niveaugegevens, trekken we deze waarde af van de isometrische positie van elke tegel en kunnen we bepalen of de nieuwe positie van de tegel binnen het scherm valt.
Als alternatief kunnen we besluiten dat we alleen een X x Y isometrisch tegelraster op het scherm om de tekenlus efficiënt te maken voor grotere niveaus.
We kunnen dit in stappen als volgt uitdrukken:
var cornerMapPos = nieuwe Phaser.Point (0,0); var cornerMapTile = nieuwe Phaser.Point (0,0); var visibleTiles = nieuwe Phaser.Point (6,6); // ... functie-update () // ... if (isWalkable ()) heroMapPos.x + = heroSpeed * dX; heroMapPos.y + = heroSpeed * dY; // verplaats de hoek in tegenovergestelde richting cornerMapPos.x - = heroSpeed * dX; cornerMapPos.y - = heroSpeed * dY; cornerMapTile = getTileCoordinates (cornerMapPos, tileWidth); // verkrijg de nieuwe hero map tile heroMapTile = getTileCoordinates (heroMapPos, tileWidth); // depthsort & draw new scene renderScene (); functie renderScene () gameScene.clear (); // wis het vorige frame en teken opnieuw var tileType = 0; // laat ons de lussen binnen het zichtbare gebied beperken var startTileX = Math.max (0,0-cornerMapTile.x); var startTileY = Math.max (0,0-cornerMapTile.y); var endTileX = Math.min (levelData [0] .length, startTileX + visibleTiles.x); var endTileY = Math.min (levelData.length, startTileY + visibleTiles.y); startTileX Math.max = (0, endTileX-visibleTiles.x); startTileY Math.max = (0, endTileY-visibleTiles.y); // controleer voor randvoorwaarde voor (var i = startTileY; i < endTileY; i++) for (var j = startTileX; j < endTileX; j++) tileType=levelData[i][j]; drawTileIso(tileType,i,j); if(i==heroMapTile.y&&j==heroMapTile.x) drawHeroIso(); function drawHeroIso() var isoPt= new Phaser.Point();//It is not advisable to create points in update loop var heroCornerPt=new Phaser.Point(heroMapPos.x-hero2DVolume.x/2+cornerMapPos.x,heroMapPos.y-hero2DVolume.y/2+cornerMapPos.y); isoPt=cartesianToIsometric(heroCornerPt);//find new isometric position for hero from 2D map position gameScene.renderXY(sorcererShadow,isoPt.x+borderOffset.x+shadowOffset.x, isoPt.y+borderOffset.y+shadowOffset.y, false);//draw shadow to render texture gameScene.renderXY(sorcerer,isoPt.x+borderOffset.x+heroWidth, isoPt.y+borderOffset.y-heroHeight, false);//draw hero to render texture function drawTileIso(tileType,i,j)//place isometric level tiles var isoPt= new Phaser.Point();//It is not advisable to create point in update loop var cartPt=new Phaser.Point();//This is here for better code readability. cartPt.x=j*tileWidth+cornerMapPos.x; cartPt.y=i*tileWidth+cornerMapPos.y; isoPt=cartesianToIsometric(cartPt); //we could further optimise by not drawing if tile is outside screen. if(tileType==1) gameScene.renderXY(wallSprite, isoPt.x+borderOffset.x, isoPt.y+borderOffset.y-wallHeight, false); else gameScene.renderXY(floorSprite, isoPt.x+borderOffset.x, isoPt.y+borderOffset.y, false);
Houd er rekening mee dat het hoekpunt wordt verhoogd in de tegenover richting naar de positie van de held bij te werken als hij beweegt. Dit zorgt ervoor dat de held blijft waar hij is ten opzichte van het scherm. Bekijk dit voorbeeld (gebruik de pijlen om te scrollen, tik om het zichtbare raster te vergroten).
Een paar opmerkingen:
Deze serie is vooral bedoeld voor beginners die isometrische spelwerelden willen verkennen. Veel van de toegelichte concepten hebben alternatieve benaderingen die wat gecompliceerder zijn en ik heb bewust de eenvoudigste gekozen.
Ze voldoen mogelijk niet aan de meeste van de scenario's die u tegen kunt komen, maar de opgedane kennis kan worden gebruikt om op deze concepten voort te bouwen om meer gecompliceerde oplossingen te creëren. De eenvoudige dieptesortering die is geïmplementeerd, breekt bijvoorbeeld wanneer we niveaus met meerdere verdiepingen en platformtegels hebben die van het ene verhaal naar het andere gaan.
Maar dat is een tutorial voor een andere keer.