En Opdateret Primer til Oprettelse af Isometrisk Verdener, Del 1

() translation by (you can also view the original English article)



Vi har alle spillet vores rimelige andel af fantastiske isometriske spil, være det oprindelige Diablo eller alder i empirisk eller kommandosoldater. Første gang du kom på tværs af en isometrisk spil, du måske har spekuleret over, hvis det var en 2D spil, eller et 3D spil eller noget helt andet. En verden af isometrisk spil har sin mystiske attraktion for spiludviklere så godt. Lad os prøve at optrævle mysteriet om isometriske projektion og forsøge at skabe en enkel isometrisk verden i denne tutorial.
Denne tutorial er en opdateret version af min eksisterende tutorial på at skabe isometrisk verdener. Den oprindelige tutorial brugt Flash med ActionScript og er stadig relevante for Flash eller OpenFL udviklere. I denne nye tutorial jeg har besluttet at bruge Phaser med JS kode, skabe dermed interaktive HTML5 output i stedet for SWF output.
Vær opmærksom på, at dette er ikke en Phaser udvikling tutorial, men vi bruger bare Phaser nemt kommunikere centrale begreber for at skabe en isometrisk scene. Desuden er der meget bedre og nemmere måder at oprette isometrisk indhold i Phaser, såsom Phaser isometrisk Plugin.
For nemheds skyld, vil vi bruge den flise-baseret tilgang til at skabe vores isometrisk scene.
1. Flise-Baserede Spil
I 2D spil ved hjælp af metoden med flise-baseret, er hvert visuelle element opdelt i mindre dele, kaldet fliser, en standard størrelse. Disse fliser vil blive arrangeret til at danne spilverdenen forudbestemte niveau data—normalt en todimensional matrix.
Relaterede Stillinger
Flise-baseret spil bruger normalt enten en top-down visning eller en side for spil scenen. Lad os overveje en standard top-down 2D visning med to fliser—en græs flise og en væg flise — som vist her:



Begge af disse fliser er kvadratiske billeder af samme størrelse, dermed flise højde og flise bredde er den samme. Lad os overveje et spil niveau, som er en græsarealer, omgivet på alle sider af vægge. I sådanne tilfælde vil plan data repræsenteret med en todimensional matrix ligne indeværende:
1 |
[ |
2 |
[1,1,1,1,1,1], |
3 |
[1,0,0,0,0,1], |
4 |
[1,0,0,0,0,1], |
5 |
[1,0,0,0,0,1], |
6 |
[1,0,0,0,0,1], |
7 |
[1,1,1,1,1,1] |
8 |
] |
Her, 0
angiver en græs flise og 1
betegner en væg flise. Arrangere fliser niveau data vil producere vores walled græsarealer, som vist på billedet nedenfor:



Vi kan gå lidt længere ved at tilføje hjørnefliser og separate vandrette og lodrette vægfliser, der kræver fem ekstra fliser, som fører os til vores opdaterede niveau data:
1 |
[ |
2 |
[3,1,1,1,1,4], |
3 |
[2,0,0,0,0,2], |
4 |
[2,0,0,0,0,2], |
5 |
[2,0,0,0,0,2], |
6 |
[2,0,0,0,0,2], |
7 |
[6,1,1,1,1,5] |
8 |
] |
Tjek billedet nedenfor, hvor jeg har markeret fliser med deres tilsvarende flise numre i den plan data:



Nu, hvor vi har forstået begrebet den flise-baseret tilgang, lad mig vise dig, hvordan vi kan bruge en ligetil 2D gitter pseudo kode til at gøre vores niveau:
1 |
for (i, loop through rows) |
2 |
for (j, loop through columns) |
3 |
x = j * tile width |
4 |
y = i * tile height |
5 |
tileType = levelData[i][j] |
6 |
placetile(tileType, x, y) |
Hvis vi bruger de ovennævnte flise billeder derefter flise bredde og højde, flise er lige (og det samme for alle fliser), og vil matche fliser billeder dimensioner. Så flise bredde og flise højde for dette eksempel er både 50 px, som udgør den samlede niveau størrelse 300 x 300 px—det vil sige seks rækker og seks kolonner af fliserne måler 50 x 50 px hver.
Som omtalt tidligere, i en normal flise-baseret tilgang, gennemføre vi enten en top-down udsigt eller side; en isometrisk visning skal vi gennemføre de isometriske projektion.
2. Isometriske Projektion
Den bedste tekniske forklaring på hvad isometriske projektion betyder, så vidt jeg ved, er fra denne artikel af Clint Bellanger:
Vi vinkel vores kamera langs to akser (svinge kameraet 45 grader til den ene side, derefter 30 grader ned). Dette skaber en diamant (rhombus) formet gitter hvor gitter rum er dobbelt så bredt som de er høje. Denne stil blev populariseret af strategispil og action RPG. Hvis vi ser på en terning i denne visning, tre sider er synlige (top og to modstående sider).
Selv om det lyder en smule kompliceret, er faktisk gennemfører denne visning meget let. Skal vi forstå er forholdet mellem 2D og de isometriske plads—det er forholdet mellem niveau data og se; transformation fra top-down Kartesiske koordinater til isometrisk koordinater. Billedet nedenfor viser den visuelle transformation:



Placere Isometrisk Fliser
Lad mig forsøge at forenkle forholdet mellem niveau data gemmes som en 2D array og isometrisk—det vil sige, hvordan vi omdanne kartesiske koordinater i isometrisk koordinater. Vi vil forsøge at skabe isometrisk for vores nu berømte muromkransede græsarealer. 2D-visning gennemførelsen af plan var en ligetil iteration med to løkker, placere kvadratiske fliser modregning hver med faste flise højde og flise breddeværdier. For isometrisk forbliver pseudo koden den samme, men placeTile()
funktionen ændringer.
Den oprindelige funktion lige trækker flise billeder på de angivne koordinater x
og y
, men for en isometrisk, vi har brug for at beregne de tilsvarende isometrisk koordinater. Ligninger til at gøre dette er saaledes, hvor isoX
og isoY
repræsenterer isometrisk x - og y-koordinater, og cartX
og cartY
repræsenterer kartesiske x - og y-koordinaterne:
1 |
//Cartesian to isometric:
|
2 |
|
3 |
isoX = cartX - cartY; |
4 |
isoY = (cartX + cartY) / 2; |
1 |
//Isometric to Cartesian:
|
2 |
|
3 |
cartX = (2 * isoY + isoX) / 2; |
4 |
cartY = (2 * isoY - isoX) / 2; |
Ja, det er det. Disse enkle ligninger er magien bag isometriske projektion. Her er Phaser hjælpefunktioner, der kan bruges til at konvertere fra et system til et andet ved hjælp af meget bekvemt Punkt
klasse:
1 |
function cartesianToIsometric(cartPt){ |
2 |
var tempPt=new Phaser.Point(); |
3 |
tempPt.x=cartPt.x-cartPt.y; |
4 |
tempPt.y=(cartPt.x+cartPt.y)/2; |
5 |
return (tempPt); |
6 |
}
|
1 |
function isometricToCartesian(isoPt){ |
2 |
var tempPt=new Phaser.Point(); |
3 |
tempPt.x=(2*isoPt.y+isoPt.x)/2; |
4 |
tempPt.y=(2*isoPt.y-isoPt.x)/2; |
5 |
return (tempPt); |
6 |
}
|
Så kan vi bruge metoden cartesianToIsometric
hjælper hen til omforme de indgående 2D koordinater i isometrisk koordinater inde i placeTile
metoden. Bortset fra dette, rendering koden forbliver den samme, men vi skal have nye billeder til fliser. Vi kan ikke bruge de gamle firkantede fliser bruges til vores top-down rendering. Billedet nedenfor viser de nye isometrisk græs og vægfliser sammen med det afsmeltede isometriske niveau:



Utroligt, er det ikke? Lad os se, hvordan en typisk 2D holdning bliver omdannet til en isometrisk position:
1 |
2D point = [100, 100]; |
2 |
// isometric point will be calculated as below
|
3 |
isoX = 100 - 100; // = 0 |
4 |
isoY = (100 + 100) / 2; // = 100 |
5 |
Iso point == [0, 100]; |
Ligeledes et input af [0, 0
] vil resultere i [0, 0
], og [10, 5
] vil give [5, 7,5
].
For vores walled græsarealer, kan vi bestemme en walkable område ved at kontrollere, om arrayelement er 0
ved at koordinere, der derved viser græs. Til dette skal vi bestemme array koordinater. Vi kan finde den flise koordinater i niveau data fra sin kartesiske koordinater ved hjælp af denne funktion:
1 |
function getTileCoordinates(cartPt, tileHeight){ |
2 |
var tempPt=new Phaser.Point(); |
3 |
tempPt.x=Math.floor(cartPt.x/tileHeight); |
4 |
tempPt.y=Math.floor(cartPt.y/tileHeight); |
5 |
return(tempPt); |
6 |
}
|
(Her, vi hovedsageligt antager at flise højde og flise bredde er lig, som i de fleste tilfælde.)
Derfor, fra et par af skærmen (isometrisk) koordinater, kan vi finde flise koordinater ved at ringe:
1 |
getTileCoordinates(isometricToCartesian(screen point), tile height); |
Denne skærm punkt kunne være, siger, en mus klik eller en pick-up brugsstilling.
Registrering Point
I Flash, kunne vi indstille vilkårlige punkter for et grafikobjekt som sit midtpunkt eller [0,0
]. Phaser tilsvarende er Pivot
. Når du indsætter grafikken på sige [10,20
], så denne Pivot
point vil blive justeret med [10,20
]. Som standard, den øverste venstre hjørne af et grafikobjekt er betragtes som sin [0,0
] eller Pivot
. Hvis du forsøger at oprette det ovenstående niveau ved hjælp af den kode, der, vil så du ikke få det viste resultat. I stedet, vil du få et fladt land uden for murene, som nedenfor:



Dette er fordi flise billeder er af forskellige størrelser og vi tager ikke fat på attributten højde af væg flise. Den nedenstående billede viser de forskellige flise billeder, som vi bruger med deres afgrænsningsrammer og en hvid cirkel hvor deres standard [0,0] er:



Se hvordan helten får skævt, når du tegner ved hjælp af standard omdrejningspunkterne. Bemærk også hvordan vi mister højden af væg flise udtrukket ved hjælp af standard omdrejningspunkterne. Billedet til højre viser, hvordan de skal være korrekt justeret således at væg flise får sin højde og helten får placeret i midten af græsset flise. Dette problem kan løses på forskellige måder.
- Gøre alle fliser i den samme billedstørrelse med grafikken justeret korrekt i billedet. Dette skaber en masse tomme områder inden for hver flise grafik.
- Angive pivot point manuelt for hver flise, så de justeres korrekt.
- Trække fliser med bestemte forskydninger, så de justeres korrekt.
For denne tutorial, har jeg valgt at bruge den tredje metode, så det virker endda med en ramme uden mulighed for at angive pivot point.
3. Flytter i Isometrisk Koordinater
Vi vil aldrig forsøge at flytte vores karakter eller projektil i isometrisk koordinater direkte. I stedet vil vi manipulere vores spilverdenen data i kartesiske koordinater og bare bruge ovenstående funktioner for at opdatere dem på skærmen. For eksempel, hvis du vil flytte en karakter frem i den positive y-retning, kan du blot forøges sin y
ejendom i 2D koordinater og derefter konvertere den deraf følgende holdning til isometrisk koordinater:
1 |
y = y + speed; |
2 |
placetile(cartesianToIsometric(new Phaser.Point(x, y))) |
Dette vil være et godt tidspunkt til at gennemgå alle de nye begreber, som vi har lært indtil nu, og at forsøge at skabe et godt eksempel på noget bevæger sig i en isometrisk verden. Du kan finde de nødvendige billede aktiver i mappen aktiver
kilde git repository.
Dybde Sortering
Hvis du prøvede at flytte bolden billede i vores walled garden ønsker du støder på problemer med dybde sortering. Ud over normale placering, bliver vi nødt til at tage sig af dybde sortering til at tegne isometrisk verden, hvis der flytter elementer. Korrekt dybde sortering gør sikker på, at emner tættere til skærmen tegnes oven på varer længere væk.
Den enkleste dybde sorteringsmetoden er simpelthen at bruge værdien kartesiske y-koordinaten som nævnt i denne Hurtige Tip: yderligere op skærmen objektet er, jo tidligere det bør drages. Dette kan fungere godt for meget enkel isometrisk scener, men en bedre måde vil være at gentegne isometrisk scenen, når en bevægelse sker, ifølge den flise array koordinater. Lad mig forklare dette begreb i detaljer med vores pseudo kode for plan tegning:
1 |
for (i, loop through rows) |
2 |
for (j, loop through columns) |
3 |
x = j * tile width |
4 |
y = i * tile height |
5 |
tileType = levelData[i][j] |
6 |
placetile(tileType, x, y) |
Forestil dig vores vare eller karakter er på flise [1,1]
— det vil sige, den øverste grønne fliser i isometrisk. For at korrekt niveau, karakter skal drages efter tegning hjørne væg flise, som både venstre og højre vægfliser og jorden flise, nedenfor:



Hvis vi følger vores draw løkke som pr pseudo koden ovenfor, vi vil trække den midterste hjørne væg først, og derefter vil fortsætte med at tegne alle væggene i øverste højre sektion, indtil den når det højre hjørne.
Derefter, i næste loop, vil det trække væggen på venstre for tegnet, og så græs flisen som karakteren stående. Når vi finde ud af dette er den flise, som optager vores karakter, vil vi trække karakter efter tegning græs flise. Denne måde, hvis der var vægge på de tre gratis græs fliser tilsluttet den, som karakteren stående, overlapper disse vægge karakter, hvilket resulterer i korrekt dybde sorteret rendering.
4. at Skabe Kunst
Isometrisk kunst kan være pixel kunst, men det behøver ikke at være. Når der beskæftiger sig med isometrisk pixel art, fortæller Rhysd's guide dig næsten alt hvad du behøver at vide. Nogle teori kan findes på Wikipedia så godt.
Når du opretter isometrisk kunst, er de generelle regler:
- Starte med en tom isometriske gitter og overholde pixel-perfekt præcision.
- Forsøge at bryde kunst i enkelt isometrisk flise billeder.
- Forsøge at sikre, at hver flise er enten walkable eller ikke-walkable. Det bliver kompliceret, hvis vi har brug for til at rumme en enkelt flise, der indeholder både walkable og ikke-walkable områder.
- De fleste fliser skal problemfrit flise i en eller flere retninger.
- Skygger kan være vanskelig at gennemføre, medmindre vi bruge en lagdelt tilgang, hvor vi trækker skygger på jorden lag og derefter tegne helten (eller træer eller andre objekter) på det øverste lag. Hvis den metode, du bruger, ikke er multi-lag, gør sikker på skygger falder på forsiden, således at de ikke vil falde på, siger, helten, når han står bag et træ.
- I tilfælde du skal bruge en flise billede større end standard isometrisk flisestørrelsen, prøve at bruge en dimension, som er et multiplum af flisestørrelsen iso. Det er bedre at have en lagdelt tilgang i sådanne tilfælde, hvor vi kan opdele kunsten i forskellige stykker baseret på dens højde. For eksempel, et træ kan opdeles i tre stykker: roden, stammen og løv. Dette gør det nemmere at sortere dybder, som vi kan trække stykker i tilsvarende lag, som svarer til deres højder.
Isometrisk fliser, der er større end enkelt flise dimensioner vil skabe problemer med dybde sortering. Nogle af spørgsmålene, der diskuteres i disse links:
Relaterede Stillinger
- Større fliser
- Opdele og Malerens algoritme
- OpenSpaces indlæg om effektive metoder til at opdele større fliser
5. Isometrisk Tegn
Vi skal først fastsætte, hvor mange retninger af bevægelse er tilladt i vores spil—normalt spil vil give fire-vejs bevægelse eller ottevejs bevægelse. Tjek billedet nedenfor for at forstå sammenhængen mellem 2D rum og isometrisk rum:



Bemærk, at en karakter ville være bevæger sig lodret op når vi trykker på pil op tasten i en top-down spil, men for en isometrisk spil karakter vil bevæge sig på en 45-graders vinkel mod øverste højre hjørne.
For en top-down se, kunne vi skabe et sæt af tegn animationer står i én retning, og blot rotere dem for alle de andre. Vi skal igen gøre hver animation i hver af de tilladte retninger for isometrisk karakter kunst—så for otte-måde bevægelse, skal vi skabe otte animationer til hver handling.
For at lette forståelse betegne vi normalt retninger som Nord, Nordvest, Vest, Sydvest, Syd, Syd-Ost, Mod Ost, Og Nord-Ost. Karakter rammer nedenfor viser tomgang rammer start fra Syd-Ost og går med uret:



Vi vil placere tegn på samme måde at vi lagt fliser. Flytning af en karakter opnås ved beregning af bevægelse i kartesiske koordinater og derefter konvertere til isometrisk koordinater. Lad os antage vi bruger tastaturet til at styre karakteren.
Vi vil sætte to variabler, dX
og dY
, baseret på retningstasterne trykket. Som standard disse variabler vil være 0
, og vil blive opdateret efter diagrammet nedenfor, hvor U
, D
, F
og L
betegne Op, Ned, Højre og Venstre piletasterne, henholdsvis. En værdi af 1
under en nøgle repræsenterer der trykkes på tasten 0
angiver, at nøglen ikke trykkes.
1 |
Key Pos |
2 |
U D R L dX dY |
3 |
================ |
4 |
0 0 0 0 0 0 |
5 |
1 0 0 0 0 1 |
6 |
0 1 0 0 0 -1 |
7 |
0 0 1 0 1 0 |
8 |
0 0 0 1 -1 0 |
9 |
1 0 1 0 1 1 |
10 |
1 0 0 1 -1 1 |
11 |
0 1 1 0 1 -1 |
12 |
0 1 0 1 -1 -1 |
Nu, ved hjælp af værdierne af dX
og dY
, vi kan opdatere de kartesiske koordinater som så:
1 |
newX = currentX + (dX * speed); |
2 |
newY = currentY + (dY * speed); |
Så dX
og dY
står for ændring i x- og y-position karakter, baseret på at taster trykkes ned. Vi kan let beregne de nye isometrisk koordinater, som vi allerede har drøftet:
1 |
Iso = cartesianToIsometric(new Phaser.Point(newX, newY)) |
Når vi har den nye isometriske holdning, vi har brug at flytte karakter til denne holdning. Baseret på de værdier, vi har for dX
og dY
, kan vi afgøre, hvilken retning karakteren står og bruge den tilsvarende karakter kunst. Når karakteren er flyttet, så glem ikke at genfremstille plan med den korrekte dybde sortering som flise koordinaterne for tegnet, der kan have ændret.
Sammenstød Afsløring
Kollisionsdetektion sker ved at kontrollere, om fliser på den nyligt beregnede position er en ikke-walkable flise. Så når vi finder den nye holdning, vi straks flytte ikke karakter der, men første check at se, hvad flise indtager rummet.
1 |
tile coordinate = getTileCoordinates(isometricToCartesian(current position), tile height); |
2 |
if (isWalkable(tile coordinate)) { |
3 |
moveCharacter(); |
4 |
} else { |
5 |
//do nothing;
|
6 |
}
|
I funktion isWalkable()
undersøge vi, om niveau data array-værdi på en given koordinat er en walkable flise eller ej. Vi skal sørge for at opdatere den retning hvor karakteren står—selvom han ikke bevæger sig, som i tilfælde af ham at ramme en ikke-walkable flise.
Nu dette kan lyde som en ordentlig løsning, men det virker kun for varer uden volumen. Dette skyldes, at vi overvejer kun et enkelt punkt, som er midtpunktet af karakteren, til at beregne kollision. Hvad vi virkelig skal gøre er at finde alle de fire hjørner af karakteren fra sin rådighed 2D midtpunkt koordinere og beregne kollisioner for alle dem. Hvis nogen hjørne falder inde i en ikke-walkable flise, bør vi ikke flytte karakter.
Dybde Sortering Med Tegn
Overveje et tegn og et træ fliser i isometrisk verden, og de begge har de samme billedstørrelser, men urealistisk at lyde.
For korrekt at forstå dybden sortering, må vi forstå, at når figurens x- og y-koordinaterne er mindre end dem af træet, træet overlapper karakter. Når figurens x- og y-koordinaterne er større end i træet, overlapper tegnet træet. Når de har den samme x-koordinat, så vi beslutter, baseret på den y-koordinat alene: der har højere y-koordinaten overlapper den anden. Når de har den samme y-koordinat, så vi beslutter, baseret på den x-koordinat alene: der har højere x-koordinaten overlapper den anden.
Som forklaret tidligere, en forenklet version af denne er man blot sekventielt tegner de niveauer, startende fra den fjerneste flise—det vil sige fliser [0] [0]
— og derefter tegne alle brikkerne i hver række én efter én. Hvis en person indtager en flise, vi drage jorden flise først og derefter gøre karakter flise. Dette vil fungere fint, fordi karakteren ikke kan indtage en væg flise.
6. Demo Tid!
Dette er en demo i Phaser. Klik for at fokusere på det interaktive område og brug din piletasterne til at flytte karakter. Du kan bruge to piletasterne til at flytte i de diagonale retninger.
Du kan finde den komplette kilde til demo i arkivet kilde for denne tutorial. De vigtigste JS koden er angivet nedenfor: