Oprette isometrisk verdener: En Primer for Spilu Dviklere
() translation by (you can also view the original English article)
I denne vejledning giver jeg dig et bredt overblik over, hvad du behøver at vide for at skabe isometriske verdener. Du vil lære de isometriske projektion er, og hvordan til at repræsentere isometrisk niveauer som 2D arrays. Vi vil formulere relationer mellem visningen og logik, således at vi kan nemt manipulere objekter på skærmen og håndtag flise-baseret kollisions. Vi vil også se på sortering af dybde og karakter animation.
Vil endnu flere tips om at skabe isometrisk verdener? Tjek den opfølgn-ing post, Oprette Isometrisk Verdener: En Primer for Gamedevs, Fortsat, og Juwals bog, Stær Spil Udvikling Essentials.
1. De Isometriske Verden
Isometrisk er en visning, der bruges til at skabe en illusion af 3D for et ellers 2D spil - undertiden benævnt pseudo 3D eller 2.5 D. Disse billeder (taget fra Diablo 2 og Age of Empires) illustrerer, hvad jeg mener:






Gennemføre en isometrisk kan gøres på mange måder, men for nemheds skyld vil jeg fokusere på en flise-baseret tilgang, som er den mest effektive og udbredte metode. Jeg har overlejret hvert skærmbillede ovenfor med en diamant gitter viser hvor terrænet er delt op i fliser.
2. Flise-Baserede Spil
I den flise-baseret tilgang, er hver 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 2D array.
For eksempel Lad os betragte en standard top-down 2D visning med to fliser - en græs flise og en væg flise - som vist her:

Disse fliser er hver af samme størrelse som hinanden, og hver firkant, så flise højde og flise bredde er den samme.
For en plan med græsarealer afgrænses på alle sider af vægge, vil den plan data 2D array ligne indeværende:
1 |
[[1,1,1,1,1,1], |
2 |
[1,0,0,0,0,1], |
3 |
[1,0,0,0,0,1], |
4 |
[1,0,0,0,0,1], |
5 |
[1,0,0,0,0,1], |
6 |
[1,1,1,1,1,1]] |
Her, 0
angiver en græs flise og 1
betegner en væg flise. Arrangere fliser niveau data vil producere den nedenstående niveau billede:

Vi kan forbedre dette ved at tilføje hjørnefliser og separate vandrette og lodrette vægfliser, der kræver fem ekstra fliser:
1 |
[[3,1,1,1,1,4], |
2 |
[2,0,0,0,0,2], |
3 |
[2,0,0,0,0,2], |
4 |
[2,0,0,0,0,2], |
5 |
[2,0,0,0,0,2], |
6 |
[6,1,1,1,1,5]] |

Jeg håber, at begrebet den flise-baseret tilgang er nu klar. Dette er en enkel 2D gitter gennemførelse, som vi kunne kode som så:
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) |
Her antager vi at flise bredde og flise højde er lige (og det samme for alle fliser), og matche fliser billeder dimensioner. Så er flise bredde og flise højde for dette eksempel både 50px, som udgør den samlede niveau størrelse af 300x300px - dvs, seks rækker og seks kolonner af fliser måling 50x50px hver.
I en normal flise-baseret tilgang gennemføre vi enten en top-down udsigt eller side; for en isometrisk skal vi gennemføre de isometriske projektion.
3. 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 ligetil. Skal vi forstå er forholdet mellem 2D og de isometriske plads - det vil sige forholdet mellem niveau data og se; transformation fra top-down "Kartesiske" koordinater til isometrisk koordinater.



(Vi overvejer ikke en sekskantet flise baseret teknik, som er en anden måde at gennemføre isometrisk verdener.)
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 til isometrisk koordinater.
Vi vil forsøge at skabe isometrisk til vores væg-lukket græsarealer niveau data:
1 |
[[1,1,1,1,1,1], |
2 |
[1,0,0,0,0,1], |
3 |
[1,0,0,0,0,1], |
4 |
[1,0,0,0,0,1], |
5 |
[1,0,0,0,0,1], |
6 |
[1,1,1,1,1,1]] |
I dette scenario kan vi bestemme en walkable område ved at kontrollere, om arrayelement er 0
ved at koordinere, der derved viser græs. 2D-visning gennemførelsen af de ovennævnte plan var en ligetil iteration med to løkker, placere kvadratiske fliser modregning hver med faste flise højde og flise breddeværdier.
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) |
For isometrisk forbliver koden den samme, men placeTile()
funktionen ændringer.
For en isometrisk skal vi beregne de tilsvarende isometrisk koordinater inde sløjfer.
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; |
Disse funktioner viser, hvordan du kan konvertere fra et system til et andet:
1 |
function isoTo2D(pt:Point):Point{ |
2 |
var tempPt:Point = new Point(0, 0); |
3 |
tempPt.x = (2 * pt.y + pt.x) / 2; |
4 |
tempPt.y = (2 * pt.y - pt.x) / 2; |
5 |
return(tempPt); |
6 |
}
|
1 |
function twoDToIso(pt:Point):Point{ |
2 |
var tempPt:Point = new Point(0,0); |
3 |
tempPt.x = pt.x - pt.y; |
4 |
tempPt.y = (pt.x + pt.y) / 2; |
5 |
return(tempPt); |
6 |
}
|
Pseudokode for løkken så ligner dette:
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, twoDToIso(new Point(x, y))) |



Som et eksempel, lad os se, hvordan en typisk 2D holdning bliver omdannet til en isometrisk position:
1 |
2D point = [100, 100]; |
2 |
// twoDToIso(2D 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
].
Ovenstående metode gør det muligt for os at skabe en direkte sammenhæng mellem den 2D plan data og isometrisk koordinaterne. Vi kan finde den flise koordinater i niveau data fra sin kartesiske koordinater ved hjælp af denne funktion:
1 |
function getTileCoordinates(pt:Point, tileHeight:Number):Point{ |
2 |
var tempPt:Point = new Point(0, 0); |
3 |
tempPt.x = Math.floor(pt.x / tileHeight); |
4 |
tempPt.y = Math.floor(pt.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(isoTo2D(screen point), tile height); |
Denne skærm punkt kunne være, siger, en mus klik eller en pick-up brugsstilling.
Tip: En anden metode til placering er Zigzag-model, som tager en anden tilgang helt.
Bevæger sig i Isometrisk Koordinater
Bevægelse er meget nemt: du manipulere dataene spillets verden med kartesiske koordinater og bare bruge ovenstående funktioner for at opdatere det på skærmen. For eksempel, hvis du vil flytte en karakter frem i den positive y-retning, kan du blot forøge egenskaben y
og derefter konvertere sin holdning til isometrisk koordinater:
1 |
y = y + speed; |
2 |
placetile(twoDToIso(new Point(x, y))) |
Dybde Sortering
Ud over normale placering, bliver vi nødt til at tage sig af dybde sortering for tegning af isometrisk verden. Det sikrer, at emner tættere til spilleren er trukket 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 arbejde godt, så længe vi ikke har nogen sprites, der indtager mere end en enkelt flise plads.
Den mest effektive måde at dybde sortering for isometrisk verdener er at bryde alle brikkerne i single-flise standardmål og ikke at tillade større billeder. For eksempel, her er en flise, hvilke gør ikke passe ind i standard flisestørrelsen - Se hvordan vi kan opdele det i flere fliser, der hver især passer flise dimensioner:



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:
5. Isometrisk Tegn
Gennemføre tegn i isometrisk er ikke kompliceret, som det kan lyde. Karakter kunst skal være oprettet i henhold til bestemte standarder. Først vi bliver nødt til at 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.



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. For isometrisk karakter kunst skal vi igen gøre hver animation i hver af de tilladte retninger - så for otte-måde bevægelse skal vi oprette otte animationer til hver handling. For at lette forståelse betegne vi normalt retninger som Nord, Nord-Vest, Vest, Sydvest, Syd, Syd-Ost, Mod Ost, og Nord-Ost, mod uret, i nævnte rækkefølge.



Vi placerer tegn på samme måde, vi lægge 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 som pr nedenfor diagram, 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 nøglen er trykket; 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 = twoDToIso(new Point(newX, newY)) |
Når vi har den nye isometriske position, skal vi flytte karakteren til denne position. 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.
Sammenstød Afsløring
Kollisionsdetektion sker ved at kontrollere, om fliser på den beregnede nye 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(isoTo2D(iso point), 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 over for - selv om han ikke flyttes, som i tilfælde af ham at ramme en ikke-walkable flise.
Dybde Sortering Med Tegn
Overveje et tegn og et træ fliser i isometrisk verden.
For at forstå korrekt dybde 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 samme y-koordinat, så vi beslutter, baseret på den x-koordinat alene: der har højere x-koordinaten overlapper den anden.
En forenklet version af dette er at bare sekventielt tegn niveauer fra den fjerneste flise, flise [0] [0]
- 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.
Dybde sortering skal gøres, hver gang nogen flise ændringer holdning. For eksempel, skal vi gøre det når som helst tegn flytte. Vi opdaterer derefter viste scenen, efter at have udført den dybde form, for at afspejle ændringerne, dybde.
6. Har en Go!
Nu, sætte din nye viden til god brug ved at skabe en fungerende prototype, med tastatur kontrol og korrekt dybde sortering og sammenstød afsløring. Her er min demo:
Klik her for at give SWF fokus og derefter bruge piletasterne. Klik her for fuld-størrelse version.
Du kan finde denne nytte klasse nyttig (jeg har skrevet det i AS3, men du skal kunne forstå det i alle andre programmeringssprog):
1 |
package com.csharks.juwalbose |
2 |
{
|
3 |
import flash.display.Sprite; |
4 |
import flash.geom.Point; |
5 |
|
6 |
public class IsoHelper |
7 |
{
|
8 |
/**
|
9 |
* convert an isometric point to 2D
|
10 |
* */
|
11 |
public static function isoTo2D(pt:Point):Point{ |
12 |
//gx=(2*isoy+isox)/2;
|
13 |
//gy=(2*isoy-isox)/2
|
14 |
var tempPt:Point=new Point(0,0); |
15 |
tempPt.x=(2*pt.y+pt.x)/2; |
16 |
tempPt.y=(2*pt.y-pt.x)/2; |
17 |
return(tempPt); |
18 |
}
|
19 |
/**
|
20 |
* convert a 2d point to isometric
|
21 |
* */
|
22 |
public static function twoDToIso(pt:Point):Point{ |
23 |
//gx=(isox-isoxy;
|
24 |
//gy=(isoy+isox)/2
|
25 |
var tempPt:Point=new Point(0,0); |
26 |
tempPt.x=pt.x-pt.y; |
27 |
tempPt.y=(pt.x+pt.y)/2; |
28 |
return(tempPt); |
29 |
}
|
30 |
|
31 |
/**
|
32 |
* convert a 2d point to specific tile row/column
|
33 |
* */
|
34 |
public static function getTileCoordinates(pt:Point, tileHeight:Number):Point{ |
35 |
var tempPt:Point=new Point(0,0); |
36 |
tempPt.x=Math.floor(pt.x/tileHeight); |
37 |
tempPt.y=Math.floor(pt.y/tileHeight); |
38 |
|
39 |
return(tempPt); |
40 |
}
|
41 |
|
42 |
/**
|
43 |
* convert specific tile row/column to 2d point
|
44 |
* */
|
45 |
public static function get2dFromTileCoordinates(pt:Point, tileHeight:Number):Point{ |
46 |
var tempPt:Point=new Point(0,0); |
47 |
tempPt.x=pt.x*tileHeight; |
48 |
tempPt.y=pt.y*tileHeight; |
49 |
|
50 |
return(tempPt); |
51 |
}
|
52 |
|
53 |
}
|
54 |
}
|
Hvis du virkelig sidder fast, er her den fulde kode fra min demo (i Flash og AS3 tidslinje kodeform):
1 |
// Uses senocular's KeyObject class
|
2 |
// http://www.senocular.com/flash/actionscript/?file=ActionScript_3.0/com/senocular/utils/KeyObject.as
|
3 |
|
4 |
import flash.display.Sprite; |
5 |
import com.csharks.juwalbose.IsoHelper; |
6 |
import flash.display.MovieClip; |
7 |
import flash.geom.Point; |
8 |
import flash.filters.GlowFilter; |
9 |
import flash.events.Event; |
10 |
import com.senocular.utils.KeyObject; |
11 |
import flash.ui.Keyboard; |
12 |
import flash.display.Bitmap; |
13 |
import flash.display.BitmapData; |
14 |
import flash.geom.Matrix; |
15 |
import flash.geom.Rectangle; |
16 |
|
17 |
var levelData=[[1,1,1,1,1,1], |
18 |
[1,0,0,2,0,1], |
19 |
[1,0,1,0,0,1], |
20 |
[1,0,0,0,0,1], |
21 |
[1,0,0,0,0,1], |
22 |
[1,1,1,1,1,1]]; |
23 |
|
24 |
var tileWidth:uint = 50; |
25 |
var borderOffsetY:uint = 70; |
26 |
var borderOffsetX:uint = 275; |
27 |
|
28 |
var facing:String = "south"; |
29 |
var currentFacing:String = "south"; |
30 |
var hero:MovieClip=new herotile(); |
31 |
hero.clip.gotoAndStop(facing); |
32 |
var heroPointer:Sprite; |
33 |
var key:KeyObject = new KeyObject(stage);//Senocular KeyObject Class |
34 |
var heroHalfSize:uint=20; |
35 |
|
36 |
//the tiles
|
37 |
var grassTile:MovieClip=new TileMc(); |
38 |
grassTile.gotoAndStop(1); |
39 |
var wallTile:MovieClip=new TileMc(); |
40 |
wallTile.gotoAndStop(2); |
41 |
|
42 |
//the canvas
|
43 |
var bg:Bitmap = new Bitmap(new BitmapData(650,450)); |
44 |
addChild(bg); |
45 |
var rect:Rectangle=bg.bitmapData.rect; |
46 |
|
47 |
//to handle depth
|
48 |
var overlayContainer:Sprite=new Sprite(); |
49 |
addChild(overlayContainer); |
50 |
|
51 |
//to handle direction movement
|
52 |
var dX:Number = 0; |
53 |
var dY:Number = 0; |
54 |
var idle:Boolean = true; |
55 |
var speed:uint = 5; |
56 |
var heroCartPos:Point=new Point(); |
57 |
var heroTile:Point=new Point(); |
58 |
|
59 |
//add items to start level, add game loop
|
60 |
function createLevel() |
61 |
{
|
62 |
var tileType:uint; |
63 |
for (var i:uint=0; i<levelData.length; i++) |
64 |
{
|
65 |
for (var j:uint=0; j<levelData[0].length; j++) |
66 |
{
|
67 |
tileType = levelData[i][j]; |
68 |
placeTile(tileType,i,j); |
69 |
if (tileType == 2) |
70 |
{
|
71 |
levelData[i][j] = 0; |
72 |
}
|
73 |
}
|
74 |
}
|
75 |
overlayContainer.addChild(heroPointer); |
76 |
overlayContainer.alpha=0.5; |
77 |
overlayContainer.scaleX=overlayContainer.scaleY=0.5; |
78 |
overlayContainer.y=290; |
79 |
overlayContainer.x=10; |
80 |
depthSort(); |
81 |
addEventListener(Event.ENTER_FRAME,loop); |
82 |
}
|
83 |
|
84 |
//place the tile based on coordinates
|
85 |
function placeTile(id:uint,i:uint,j:uint) |
86 |
{
|
87 |
var pos:Point=new Point(); |
88 |
if (id == 2) |
89 |
{
|
90 |
|
91 |
id = 0; |
92 |
pos.x = j * tileWidth; |
93 |
pos.y = i * tileWidth; |
94 |
pos = IsoHelper.twoDToIso(pos); |
95 |
hero.x = borderOffsetX + pos.x; |
96 |
hero.y = borderOffsetY + pos.y; |
97 |
//overlayContainer.addChild(hero);
|
98 |
heroCartPos.x = j * tileWidth; |
99 |
heroCartPos.y = i * tileWidth; |
100 |
heroTile.x=j; |
101 |
heroTile.y=i; |
102 |
heroPointer=new herodot(); |
103 |
heroPointer.x=heroCartPos.x; |
104 |
heroPointer.y=heroCartPos.y; |
105 |
|
106 |
}
|
107 |
var tile:MovieClip=new cartTile(); |
108 |
tile.gotoAndStop(id+1); |
109 |
tile.x = j * tileWidth; |
110 |
tile.y = i * tileWidth; |
111 |
overlayContainer.addChild(tile); |
112 |
}
|
113 |
|
114 |
//the game loop
|
115 |
function loop(e:Event) |
116 |
{
|
117 |
if (key.isDown(Keyboard.UP)) |
118 |
{
|
119 |
dY = -1; |
120 |
}
|
121 |
else if (key.isDown(Keyboard.DOWN)) |
122 |
{
|
123 |
dY = 1; |
124 |
}
|
125 |
else
|
126 |
{
|
127 |
dY = 0; |
128 |
}
|
129 |
if (key.isDown(Keyboard.RIGHT)) |
130 |
{
|
131 |
dX = 1; |
132 |
if (dY == 0) |
133 |
{
|
134 |
facing = "east"; |
135 |
}
|
136 |
else if (dY==1) |
137 |
{
|
138 |
facing = "southeast"; |
139 |
dX = dY=0.5; |
140 |
}
|
141 |
else
|
142 |
{
|
143 |
facing = "northeast"; |
144 |
dX=0.5; |
145 |
dY=-0.5; |
146 |
}
|
147 |
}
|
148 |
else if (key.isDown(Keyboard.LEFT)) |
149 |
{
|
150 |
dX = -1; |
151 |
if (dY == 0) |
152 |
{
|
153 |
facing = "west"; |
154 |
}
|
155 |
else if (dY==1) |
156 |
{
|
157 |
facing = "southwest"; |
158 |
dY=0.5; |
159 |
dX=-0.5; |
160 |
}
|
161 |
else
|
162 |
{
|
163 |
facing = "northwest"; |
164 |
dX = dY=-0.5; |
165 |
}
|
166 |
}
|
167 |
else
|
168 |
{
|
169 |
dX = 0; |
170 |
if (dY == 0) |
171 |
{
|
172 |
//facing="west";
|
173 |
}
|
174 |
else if (dY==1) |
175 |
{
|
176 |
facing = "south"; |
177 |
}
|
178 |
else
|
179 |
{
|
180 |
facing = "north"; |
181 |
}
|
182 |
}
|
183 |
if (dY == 0 && dX == 0) |
184 |
{
|
185 |
hero.clip.gotoAndStop(facing); |
186 |
idle = true; |
187 |
}
|
188 |
else if (idle||currentFacing!=facing) |
189 |
{
|
190 |
idle = false; |
191 |
currentFacing = facing; |
192 |
hero.clip.gotoAndPlay(facing); |
193 |
}
|
194 |
if (! idle && isWalkable()) |
195 |
{
|
196 |
heroCartPos.x += speed * dX; |
197 |
heroCartPos.y += speed * dY; |
198 |
heroPointer.x=heroCartPos.x; |
199 |
heroPointer.y=heroCartPos.y; |
200 |
|
201 |
var newPos:Point = IsoHelper.twoDToIso(heroCartPos); |
202 |
//collision check
|
203 |
hero.x = borderOffsetX + newPos.x; |
204 |
hero.y = borderOffsetY + newPos.y; |
205 |
heroTile=IsoHelper.getTileCoordinates(heroCartPos,tileWidth); |
206 |
depthSort(); |
207 |
//trace(heroTile);
|
208 |
}
|
209 |
tileTxt.text="Hero is on x: "+heroTile.x +" & y: "+heroTile.y; |
210 |
}
|
211 |
|
212 |
//check for collision tile
|
213 |
function isWalkable():Boolean{ |
214 |
var able:Boolean=true; |
215 |
var newPos:Point =new Point(); |
216 |
newPos.x=heroCartPos.x + (speed * dX); |
217 |
newPos.y=heroCartPos.y + (speed * dY); |
218 |
switch (facing){ |
219 |
case "north": |
220 |
newPos.y-=heroHalfSize; |
221 |
break; |
222 |
case "south": |
223 |
newPos.y+=heroHalfSize; |
224 |
break; |
225 |
case "east": |
226 |
newPos.x+=heroHalfSize; |
227 |
break; |
228 |
case "west": |
229 |
newPos.x-=heroHalfSize; |
230 |
break; |
231 |
case "northeast": |
232 |
newPos.y-=heroHalfSize; |
233 |
newPos.x+=heroHalfSize; |
234 |
break; |
235 |
case "southeast": |
236 |
newPos.y+=heroHalfSize; |
237 |
newPos.x+=heroHalfSize; |
238 |
break; |
239 |
case "northwest": |
240 |
newPos.y-=heroHalfSize; |
241 |
newPos.x-=heroHalfSize; |
242 |
break; |
243 |
case "southwest": |
244 |
newPos.y+=heroHalfSize; |
245 |
newPos.x-=heroHalfSize; |
246 |
break; |
247 |
}
|
248 |
newPos=IsoHelper.getTileCoordinates(newPos,tileWidth); |
249 |
if(levelData[newPos.y][newPos.x]==1){ |
250 |
able=false; |
251 |
}else{ |
252 |
//trace("new",newPos);
|
253 |
}
|
254 |
return able; |
255 |
}
|
256 |
|
257 |
//sort depth & draw to canvas
|
258 |
function depthSort() |
259 |
{
|
260 |
bg.bitmapData.lock(); |
261 |
bg.bitmapData.fillRect(rect,0xffffff); |
262 |
var tileType:uint; |
263 |
var mat:Matrix=new Matrix(); |
264 |
var pos:Point=new Point(); |
265 |
for (var i:uint=0; i<levelData.length; i++) |
266 |
{
|
267 |
for (var j:uint=0; j<levelData[0].length; j++) |
268 |
{
|
269 |
tileType = levelData[i][j]; |
270 |
//placeTile(tileType,i,j);
|
271 |
|
272 |
pos.x = j * tileWidth; |
273 |
pos.y = i * tileWidth; |
274 |
pos = IsoHelper.twoDToIso(pos); |
275 |
mat.tx = borderOffsetX + pos.x; |
276 |
mat.ty = borderOffsetY + pos.y; |
277 |
if(tileType==0){ |
278 |
bg.bitmapData.draw(grassTile,mat); |
279 |
}else{ |
280 |
bg.bitmapData.draw(wallTile,mat); |
281 |
}
|
282 |
if(heroTile.x==j&&heroTile.y==i){ |
283 |
mat.tx=hero.x; |
284 |
mat.ty=hero.y; |
285 |
bg.bitmapData.draw(hero,mat); |
286 |
}
|
287 |
|
288 |
}
|
289 |
}
|
290 |
bg.bitmapData.unlock(); |
291 |
//add character rectangle
|
292 |
}
|
293 |
createLevel(); |
Registrering Point
Tage særlige hensyn til punkterne registrering af fliser og helten. (Registrering punkter kan opfattes som oprindelse point for hver særlig sprite.) Disse generelt ikke vil falde inde i billedet, men snarere vil være øverst venstre hjørne af den sprite afgrænsningsrammen.
Vi bliver nødt til at ændre vores tegning kode for at lave registrering point korrekt, primært til helten.
Sammenstød Afsløring
Et andet interessant punkt at bemærke er, at vi beregne kollisions baseret på det punkt, hvor helten er.
Men helten har volumen, og ikke kan præcist repræsenteres af et enkelt punkt, så vi skal repræsentere helten som et rektangel og check for kollisioner mod hvert hjørne af dette rektangel, således at der er ingen overlapning med andre fliser og dermed ingen dybde artefakter.
Genveje
I demoen gentegne jeg simpelthen scene igen hver ramme baseret på den nye placering af helten. Vi finde den flise, som helten indtager og tegne helt på toppen af jorden flise, når rendering sløjfer når fliserne.
Men hvis vi ser nærmere, vil vi finde, at der er ingen grund til at sløjfe gennem alle brikkerne i dette tilfælde. Græs fliser og de øverste og venstre vægfliser er altid tegnet før helten er trukket, så vi ikke behøver at gentegne dem på alle. Også, de nederste og højre vægfliser er altid foran helten og dermed trukket efter helten er trukket.
Det væsentlige, så vi kun skal udføre dybde sortering mellem væg inde i det aktive område og hero - det vil sige to fliser. At mærke disse typer af genveje vil hjælpe dig med at spare en masse behandlingstid, som kan være afgørende for ydeevne.
Konklusion
Ved nu, bør du have en stor grundlag for bygning isometrisk spil på din egen: du kan gøre verden og objekter i det, repræsenterer niveau data i simple 2D arrays, konvertere mellem kartesiske og isometrisk coordiates og beskæftige sig med begreber som dybde sortering og karakteranimation. Nyd skaber isometrisk verdener!