Oppdatert primer for å lage isometriske verdener, del 2

Hva du skal skape

I denne siste delen av opplæringsserien bygger vi på den første opplæringen og lærer om implementering av pickup, utløser, nivåbytte, pathfinding, sti etterfølging, nivårulling, isometrisk høyde og isometriske prosjektiler.

1. Pickups

Pickup er elementer som kan samles inn i nivået, vanligvis ved å bare gå over dem, for eksempel mynter, edelstener, kontanter, ammunisjon osv..

Pickup data kan innkvarteres rett inn i våre nivå data som nedenfor:

[[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]]

I dette nivået data bruker vi 8 å betegne en pickup på en gress flis (1 og 0 representere vegger og walkable fliser henholdsvis som tidligere). Dette kan være et enkelt fliser bilde med en gressflise overlaid med pickup bildet. Når vi går etter denne logikken, trenger vi to forskjellige flistilstander for hver flis som har en pickup, det vil si en med pickup og en uten å bli vist etter at samlingen er hentet.

Typisk isometrisk kunst vil ha flere gangbare fliser. Anta at vi har 30. Ovennevnte tilnærming betyr at hvis vi har N pickups, trenger vi N x 30 fliser i tillegg til de 30 originale fliser, da hver flis må ha en versjon med pickups og en uten. Dette er ikke veldig effektivt; I stedet bør vi prøve å dynamisk opprette disse kombinasjonene. 

For å løse dette kunne vi bruke den samme metoden som vi pleide å plassere helten i første opplæring. Når vi kommer over en pickup flis, legger vi først en gressflise og legger plukkingen på toppen av gressflisen. På denne måten trenger vi bare N pickupfliser i tillegg til 30 gangbare fliser, men vi vil trenge tallverdier for å representere hver kombinasjon i nivådataene. For å løse behovet for N x 30 representasjonsverdier, kan vi beholde et eget pickupArray å lagre oppsamlingsdataene utelukkende fra levelData. Det ferdige nivået med henting er vist nedenfor:

For vårt eksempel holder jeg ting enkelt og bruker ikke et ekstra utvalg for pickup.

Picking Up Pickups

Detekteringen av pickups gjøres på samme måte som å oppdage kollisjonsteiler, men etter flytte tegnet.

hvis (onPickupTile ()) pickupItem ();  funksjon onPickupTile () // sjekk om det er en henting på helten fliser tilbake (levelData [heroMapTile.y] [heroMapTile.x] == 8);  

I funksjonen onPickupTile (), vi sjekker om levelData array verdi på heroMapTile koordinat er en pickup flis eller ikke. Tallet i levelData array på den flisekoordinaten angir typen av pickup. Vi kontrollerer for kollisjoner før du flytter tegnet, men må sjekke etter pickup etterpå, fordi i tilfelle kollisjoner skal karakteren ikke okkupert stedet hvis den allerede er opptatt av kollisjonsflisen, men i tilfelle pickup er tegnet fritt til å flytte over det.

En annen ting å merke seg er at kollisionsdataene vanligvis ikke endres, men hentingsdataene endres når vi henter et element. (Dette innebærer vanligvis bare å endre verdien i levelData matrise fra, si, 8 til 0.)

Dette fører til et problem: Hva skjer når vi trenger å starte om nivået, og dermed tilbakestille alle pickupene tilbake til deres opprinnelige posisjoner? Vi har ikke informasjonen til å gjøre dette, som levelData array har blitt endret ettersom spilleren har hentet opp elementer. Løsningen er å bruke en duplikat array for nivået mens du er i spill og for å beholde originalen levelData array intakt. For eksempel bruker vi levelData og levelDataLive [], klone sistnevnte fra førstnevnte ved begynnelsen av nivået, og bare endre levelDataLive [] under spillet.

For eksempel, gyter jeg en tilfeldig pickup på en ledig gressflise etter hver pickup og øker pickupCount. De pickupItem funksjonen ser slik ut.

funksjon pickupItem () pickupCount ++; levelData [heroMapTile.y] [heroMapTile.x] = 0; // gy neste henting spawnNewPickup (); 

Du bør legge merke til at vi sjekker etter pickup når tegnet er på den flisen. Dette kan skje flere ganger i løpet av et sekund (vi sjekker bare når brukeren beveger seg, men vi kan gå rundt og rundt i en flis), men logikken ovenfor vil ikke mislykkes. siden vi satte levelData array data til 0 Første gang vi oppdager en pickup, alle påfølgende onPickupTile () sjekker vil returnere falsk for den flisen. Ta en titt på det interaktive eksempelet nedenfor:

2. Trigger Fliser

Som navnet antyder, forårsaker utløsende fliser noe å skje når spilleren trapper på dem eller trykker på en nøkkel når de er på dem. De kan teleportere spilleren til et annet sted, åpne en port eller gi en fiende til å gi noen få eksempler. På en måte er pickups bare en spesiell form for utløserfliser: Når spilleren går på en flis som inneholder en mynt, forsvinner mynten og deres myntkasse øker.

La oss se på hvordan vi kunne implementere en dør som tar spilleren til et annet nivå. Flisen ved siden av døren vil være en utløserfliser; når spilleren trykker på x nøkkel, de går videre til neste nivå.

For å endre nivåer, er alt vi trenger å bytte gjeldende levelData array med det nye nivået, og sett det nye heroMapTile posisjon og retning for helten karakter. Anta at det er to nivåer med dører for å tillate passering mellom dem. Siden bakken ved siden av døren vil være utløserflisen på begge nivåer, kan vi bruke dette som den nye posisjonen for tegnet når de vises i nivået.

Implementeringslogikken her er den samme som for pickup, og igjen bruker vi levelData array for å lagre utløserverdier. For vårt eksempel, 2 betegner en dørflise, og verdien ved siden av den er utløseren. jeg har brukt 101 og 102 med den grunnleggende konvensjonen at enhver flis med en verdi større enn 100 er en utløserflise og verdien minus 100 kan være nivået som det fører til:

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,1,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,0,101,2], [1,0,1,0,0,1], [1,1,1,1,1,1]];

Koden for å sjekke utløseren er vist nedenfor:

var xKey = game.input.keyboard.addKey (Phaser.Keyboard.X); xKey.onUp.add (triggerListener); // legg til en Signal lytter for opp hendelsesfunksjon triggerListener () var trigger = levelData [heroMapTile.y] [heroMapTile.x]; hvis (utløser> 100) // gyldig utløserfliser utløser- = 100; hvis (trigger == 1) // bytt til nivå 1 levelData = level1Data;  ellers // bytte til nivå 2 levelData = level2Data;  for (var i = 0; i < levelData.length; i++)  for (var j = 0; j < levelData[0].length; j++)  trigger=levelData[i][j]; if(trigger>100) // finn den nye utløseren og legg helten der heroMapTile.y = j; heroMapTile.x = i; heroMapPos = ny Phaser.Point (heroMapTile.y * tileWidth, heroMapTile.x * tileWidth); heroMapPos.x + = (tileWidth / 2); heroMapPos.y + = (tileWidth / 2); 

Funksjonen triggerListener () kontrollerer om utløserdatarabellverdien ved den angitte koordinaten er større enn 100. I så fall finner vi hvilket nivå vi må bytte til ved å subtrahere 100 fra flisverdien. Funksjonen finner utløserflisen i den nye levelData, som vil være gyteposisjonen for vår helt. Jeg har aktivert utløseren når x er utgitt; hvis vi bare lytter etter at nøkkelen blir presset, kommer vi til slutt i en løkke der vi bytter mellom nivåer så lenge nøkkelen holdes nede, siden tegnet alltid sprer seg i det nye nivået på toppen av en utløserfliser.

Her er en fungerende demo. Prøv å plukke opp ting ved å gå over dem og bytte nivåer ved å stå ved siden av dørene og trykke x.

3. Prosjektiler

EN prosjektilet er noe som beveger seg i en bestemt retning med en bestemt hastighet, som en kule, en magisk spell, en ball osv. Alt om prosjektilet er det samme som heltens karakter, bortsett fra høyden: i stedet for å rulle langs bakken, prosjektiler flyter ofte over det i en viss høyde. En kule vil reise over taljenivået av karakteren, og til og med en ball må kanskje hoppe rundt.

En interessant ting å merke seg er at isometrisk høyde er den samme som høyde i en 2D sidevisning, men mindre i verdi. Det er ingen kompliserte konverteringer involvert. Hvis en ball er 10 piksler over bakken i kartesiske koordinater, kan det være 10 eller 6 piksler over bakken i isometriske koordinater. (I vårt tilfelle er den aktuelle aksen y-aksen.)

La oss prøve å implementere en ball som hopper i vårt inngjerdede gresslette. Som et snev av realisme legger vi til en skygge for ballen. Alt vi trenger å gjøre er å legge til hoppehøyden til den isometriske Y-verdien av vår ball. Hopphøydeverdien vil skifte fra ramme til ramme, avhengig av tyngdekraften, og når ballen treffer bakken, vil vi vende den nåværende hastigheten langs y-aksen.

Før vi takler hoppende i et isometrisk system, ser vi hvordan vi kan implementere det i et 2D-kartesisk system. La oss representere hoppkraften til ballen med en variabel zValue. Forestill deg at til å begynne med har ballen en hoppekraft på 100, så zValue = 100

Vi bruker to andre variabler: incrementValue, som starter på 0, og tyngde, som har en verdi av -1. Hver ramme trekker vi fra incrementValue fra zValue, og trekke fra tyngde fra incrementValue for å skape en dempende effekt. Når zValue når 0, det betyr at ballen har nådd bakken; På dette punktet vipper vi tegnet på incrementValue ved å multiplisere det med -1, snu den til et positivt tall. Dette betyr at ballen vil bevege seg oppover fra neste ramme, og dermed sprette.

Slik ser det ut i kode:

hvis (game.input.keyboard.isDown (Phaser.Keyboard.X)) zValue = 100;  incrementValue- = tyngdekraften; zValue- = incrementValue; if (zValue<=0) zValue=0; incrementValue*=-1; 

Koden forblir den samme for isometrisk visning også, med den lille forskjellen som du kan bruke en lavere verdi for zValue til å begynne med. Se nedenfor hvordan zValue er lagt til isometrisk y verdien av ballen mens rendering.

funksjon drawBallIso () var isoPt = new Phaser.Point (); // Det er ikke tilrådelig å opprette poeng i oppdateringsløp var ballCornerPt = ny Phaser.Point (ballMapPos.x-ball2DVolume.x / 2, ballMapPos.y-ball2DVolume .Y / 2); isoPt = cartesianToIsometric (ballCornerPt); // finne ny isometrisk posisjon for helt fra 2D kartposisjon gameScene.renderXY (ballShadowSprite, isoPt.x + borderOffset.x + shadowOffset.x, isoPt.y + borderOffset.y + shadowOffset.y, false ); // tegne skygge for å gjøre tekstur gameScene.renderXY (ballSprite, isoPt.x + borderOffset.x + ballOffset.x, isoPt.y + borderOffset.y-ballOffset.y-zValue, false); // tegne helten for å gjengi tekstur

Ta en titt på det interaktive eksempelet nedenfor:

Forstå at rollen som spilles av skyggen, er en svært viktig som legger til virkeligheten av denne illusjonen. Vær også oppmerksom på at vi nå bruker de to skjermkoordinatene (x og y) for å representere tre dimensjoner i isometriske koordinater. Y-aksen i skjermkoordinatene er også z-aksen i isometriske koordinater. Dette kan være forvirrende!

4. Finne og følge en sti

Pathfinding og sti som følger er ganske kompliserte prosesser. Det finnes ulike tilnærminger ved hjelp av forskjellige algoritmer for å finne banen mellom to punkter, men som vår levelData er en 2D-array, ting er enklere enn de ellers ville være. Vi har veldefinerte og unike noder som spilleren kan okkupere, og vi kan enkelt sjekke om de er walkable.

Relaterte innlegg

  • A * Pathfinding for nybegynnere
  • Målbasert Vector Field Pathfinding
  • Fremskynde A * Pathfinding Med Jump Point Search Algorithm
  • "Sti etter" styringsadferd

En detaljert oversikt over patfindingsalgoritmer er utenfor rammen av denne artikkelen, men jeg vil prøve å forklare den vanligste måten den virker på: Den korteste banealgoritmen, hvorav A * og Dijkstras algoritmer er berømte implementeringer.

Vi tar sikte på å finne noder som forbinder en startknutepunkt og en sluttknutepunkt. Fra startnoden besøker vi alle åtte nærliggende noder og markerer dem alle som besøkt; Denne kjerneprosessen gjentas for hver ny besøkte node, rekursivt. 

Hver tråd sporer nodene besøkt. Når du hopper til nærliggende noder, hoppes noder som allerede er besøkt (hoppet tilbake). ellers fortsetter prosessen til vi når sluttkoden, der rekursjonen slutter og den fulle banen fulgt returneres som en nodestruktur. Noen ganger slutten node er aldri nådd, i så fall feilsøking mislykkes. Vi ender opp med å finne flere stier mellom de to noder, i så fall tar vi den med det minste antall noder.

Stifinning

Det er uklokt å gjenoppfinne hjulet når det kommer til veldefinerte algoritmer, så vi ville bruke eksisterende løsninger for våre veiforbindelser. For å bruke Phaser trenger vi en JavaScript-løsning, og den jeg har valgt, er EasyStarJS. Vi initialiserer sti-søkemotoren som nedenfor.

easystar = nye EasyStar.js (); easystar.setGrid (levelData); easystar.setAcceptableTiles ([0]); easystar.enableDiagonals (); // vi vil ha vei til å ha diagonaler easystar.disableCornerCutting (); // ingen diagonal sti når du går på veggen hjørner

Som vår levelData har bare 0 og 1, vi kan direkte sende det inn som node array. Vi setter verdien av 0 som walkable node. Vi aktiverer diagonal gående evne, men deaktiver dette når du går nær hjørner av ikke-walkable fliser. 

Dette er fordi, hvis aktivert, kan helten kutte inn i den ikke-walkable flisen mens du gjør en diagonal spasertur. I så fall vil vår kollisjonsdeteksjon ikke tillate helten å passere gjennom. Vær også oppmerksom på at i eksemplet har jeg helt fjernet kollisjonsdeteksjonen, da det ikke lenger er nødvendig for et AI-basert tureksempel. 

Vi vil oppdage trykk på noen frie fliser inne i nivået og beregne banen ved hjelp av findPath funksjon. Tilbakekallingsmetoden plotAndMove mottar noden array av den resulterende banen. Vi markerer minimap med den nylig funnet banen.

game.input.activePointer.leftButton.onUp.add (findPath) funksjon findPath () hvis (isFindingPath || isWalking) returnere; var pos = game.input.activePointer.position; var isoPt = ny Phaser.Point (pos.x-borderOffset.x, pos.y-borderOffset.y); tapPos = isometricToCartesian (isoPt); tapPos.x- = tileWidth / 2; // justering for å finne riktig flis for feil på grunn av avrunding tapPos.y + = fliserBredde / 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);   

Sti etter

Når vi har banen som et node-array, må vi få karakteren til å følge den.

Si at vi vil gjøre tegnet til en flis som vi klikker på. Vi må først se etter en sti mellom noden som tegnet for tiden okkuperer og noden der vi klikket. Hvis en vellykket sti er funnet, må vi flytte tegnet til den første noden i node-oppsettet ved å angi det som destinasjon. Når vi kommer til destinasjonsnoden, kontrollerer vi om det er flere noder i nodearrangementet, og i så fall sett den neste noden som destinasjon - og så videre til vi når den endelige noden.

Vi vil også endre retningen til spilleren basert på gjeldende knutepunkt og den nye destinasjonsnoden hver gang vi kommer til en knutepunkt. Mellom noder går vi bare i ønsket retning til vi når destinasjonsnoden. Dette er en veldig enkel AI, og i eksemplet er dette gjort i metoden aiWalk vist delvis nedenfor.

funksjon aiWalk () hvis (path.length == 0) // banen er avsluttet hvis (heroMapTile.x == destination.x && heroMapTile.y == destination.y) dX = 0; dY = 0; isWalking = false; komme tilbake;  isWalking = true; hvis (heroMapTile.x == destination.x && heroMapTile.y == destination.y) // nådde nåværende destinasjon, sett ny, endre retning // vent til vi er få skritt inn i flisen før vi setter trinnTaken ++; if (stepsTakendestinasjon.x) dX = -1;  ellers dX = 0;  hvis (heroMapTile.ydestinasjon.y) dY = -1;  ellers dY = 0;  hvis (heroMapTile.x == destinasjon.x) dX = 0;  annet hvis (heroMapTile.y == destination.y) dY = 0;  // ...

Vi gjøre må filtrere ut gyldige klikkpunkter ved å avgjøre om vi har klikket i det walkable området, i stedet for en veggflis eller annen ikke-walkable flis.

Et annet interessant poeng for koding av AI: Vi vil ikke at tegnet skal vende til ansiktet neste fliser i nodearrangementet så snart han er kommet til den nåværende, da en slik umiddelbar sving resulterer i at vår karakter går på grensen til fliser. I stedet bør vi vente til tegnet er noen få skritt inne i flisen før vi ser etter neste destinasjon. Det er også bedre å manuelt plassere helt i midten av nåværende flis like før vi slår, for å få det til å føle seg perfekt.

Ta en titt på den fungerende demoen nedenfor:

5. Isometrisk rulling

Når nivåområdet er mye større enn det tilgjengelige skjermområdet, må vi gjøre det bla.

Det synlige skjermområdet kan betraktes som et mindre rektangel i det større rektangel av hele nivåområdet. Rulling er i hovedsak bare å flytte det indre rektangelet inne i den større. Vanligvis, når slik rulling skjer, forblir stillingen av helten den samme med hensyn til skjermrektangel, vanligvis på skjermsenteret. Interessant, alt vi trenger for å implementere rulling er å spore hjørnepunktet til det indre rektangel.

Dette hjørnepunktet, som vi representerer i kartesiske koordinater, vil falle innenfor en flis i nivådataene. For å bla, øker vi x- og y-posisjonen til hjørnepunktet i kartesiske koordinater. Nå kan vi konvertere dette punktet til isometriske koordinater og bruke det til å tegne skjermen. 

De nylig konverterte verdiene, i isometrisk plass, må også være hjørnet på skjermen, noe som betyr at de er de nye (0, 0). Så, mens vi analyserer og tegner nivådata, trekker vi denne verdien fra den isometriske posisjonen til hver flis, og kan avgjøre om flisens nye posisjon faller innenfor skjermen. 

Alternativt kan vi bestemme at vi skal tegne bare en X x Y isometrisk fliseregliste på skjermen for å gjøre tegningsløyfen effektiv for større nivåer. 

Vi kan uttrykke dette i trinn som slik:

  • Oppdater kartesiske hjørnepunktets x- og y-koordinater.
  • Konverter dette til isometrisk plass.
  • Trekk denne verdien fra den isometriske trekkposisjonen til hver flis.
  • Tegn bare et begrenset forhåndsdefinert antall fliser på skjermen som starter fra dette nye hjørnet.
  • Valgfritt: Tegn kun flisen hvis den nye isometriske trekkposisjonen faller innenfor skjermen.
var cornerMapPos = ny Phaser.Point (0,0); var cornerMapTile = ny Phaser.Point (0,0); var visibleTiles = ny Phaser.Point (6,6); // ... funksjon oppdatering () // ... hvis (isWalkable ()) heroMapPos.x + = heroSpeed ​​* dX; heroMapPos.y + = heroSpeed ​​* dY; // flytt hjørnet i motsatt retning cornerMapPos.x - = heroSpeed ​​* dX; cornerMapPos.y - = heroSpeed ​​* dY; cornerMapTile = getTileCoordinates (cornerMapPos, tileWidth); // få den nye heltenskappen tile heroMapTile = getTileCoordinates (heroMapPos, tileWidth); // deepsort & tegne ny scene renderScene ();  funksjon renderScene () gameScene.clear (); // slette forrige ramme og tegne igjen var tileType = 0; // la oss begrense løkkene innenfor synlig område 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); // sjekk for grensebetingelse for (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);  

Vær oppmerksom på at hjørnepunktet økes i motsatte retning til heltens posisjonoppdatering når han beveger seg. Dette sørger for at helten blir der han er med hensyn til skjermen. Sjekk ut dette eksemplet (bruk pilene til å bla, trykk for å øke det synlige rutenettet).

Et par notater:

  • Mens du ruller, må vi kanskje trekke flere fliser på skjermens grenser, ellers kan vi se at fliser forsvinner og dukker opp på skjermens ytterpunkter.
  • Hvis du har fliser som tar opp mer enn ett mellomrom, må du tegne flere fliser ved kantene. For eksempel, hvis den største flisen i hele settet måler X ved Y, må du tegne X flere fliser til venstre og høyre og Y flere fliser til toppen og bunnen. Dette sørger for at hjørnene til den større flisen fortsatt er synlige når du ruller inn eller ut av skjermen.
  • Vi må fortsatt sørge for at vi ikke har tomme områder på skjermen mens vi trekker nær grensene til nivået.
  • Nivået skal bare rulle til den mest ekstreme flisen blir trukket på den tilsvarende skjermen ekstremt - etter dette skal tegnet fortsette å bevege seg i skjermrommet uten nivået ruller. For dette må vi spore alle fire hjørnene på det indre skjermrektangelet, og smelte på rulle- og spillerbevegelseslogikken tilsvarende. Er du opptatt av å prøve å implementere det selv?

Konklusjon

Denne serien er spesielt rettet mot nybegynnere som prøver å utforske isometriske spillverdener. Mange av de forklarte konseptene har alternative tilnærminger som er litt mer kompliserte, og jeg har bevisst valgt de enkleste. 

De kan ikke oppfylle de fleste scenariene du kan støte på, men kunnskapen som er oppnådd, kan brukes til å bygge på disse konseptene for å skape mer kompliserte løsninger. For eksempel vil den enkle dybdsorteringen implementeres når vi har flerlagsnivåer og plattformfliser som beveger seg fra en historie til den andre. 

Men det er en opplæring for en annen gang.