Maken Isometrische Worlds: Een Primer voor Game Ontwikkelaars
() translation by (you can also view the original English article)
In deze tutorial, geef ik je een overzicht van wat je moet weten om te isometrische ervaringswerelden creëren. Je leert wat de isometrische projectie is, en hoe om te isometrische niveaus als 2D arrays te vertegenwoordigen. We zullen formuleren relaties tussen de weergave en de logica, zodat wij kunnen gemakkelijk het manipuleren van objecten op het scherm en handvat dakpan botsingdetectie. We zullen ook kijken diepte sorteren en karakter animatie.
Wil je nog meer tips voor het maken van isometrische werelden? Check out de follow-up post, isometrische werelden maken: A Primer voor Gamedevs, vervolg en Juwal het boek, Starling spel ontwikkeling Essentials.
1. De Isometrische Wereld
Isometrische weergave is een display methode gebruikt voor het maken van een illusie van 3D voor een anders 2D game - soms aangeduid als pseudo 3D of 2.5 D. Deze beelden (overgenomen uit Diablo 2 en Age of Empires) illustreren wat ik bedoel:






Uitvoering van een isometrische weergave kan worden gedaan op vele manieren, maar omwille van de eenvoud zal ik me concentreren op een tegel-gebaseerde benadering, die de meest efficiënte en meest gebruikte methode is. Ik heb elke screenshot hierboven bedekt met een diamant raster tonen hoe het terrein is opgedeeld in tegels.
2. Tegel Gebaseerde Spellen
Elk visueel element is in de tegel-gebaseerde benadering opgesplitst in kleinere stukken, zogenaamde tegels, van een standaard formaat. Deze tegels worden gerangschikt om te vormen van de game wereld volgens vooraf bepaalde niveau gegevens - meestal een 2D matrix.
Zo laten we eens kijken een topdown standaard 2D weergave met twee tegels - een gras tegel en de tegel van een muur - zoals hier wordt weergegeven:

Deze tegels zijn elk dezelfde grootte als elke andere, en vierkant, zodat de hoogte van de tegel en de tegel breedte hetzelfde zijn.
Voor een niveau met grasland aan alle kanten omsloten door muren, zal het niveau gegevens 2D matrix als volgt uitzien:
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]] |
Hier 0
duidt een gras tegel en 1
duidt op een tegel van de muur. Regelen van de tegels volgens de niveau gegevens zal produceren de onder niveau afbeelding:

Wij kan dit verbeteren door de toevoegen hoekelementen en aparte verticale en horizontale wandtegels, waarvoor vijf extra tegels:
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]] |

Ik hoop dat het concept van de tegel gebaseerde benadering is nu duidelijk. Dit is een eenvoudig 2D raster implementatie, die we kunnen code als volgt:
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) |
Hier veronderstellen wij dat tegel breedte en hoogte van de tegel zijn gelijke (en hetzelfde geldt voor alle tegels), en komen overeen met de tegel beelden afmetingen. Dus, de tegel breedte en hoogte van de tegel voor dit voorbeeld zijn zowel 50px, waardoor de totale niveau grootte van 300x300px - dat wil zeggen zes rijen en zes kolommen van tegels meten van elke 50x50px.
In een normale tegel gebaseerde benadering, voeren we ofwel een top-down weergave of een zijaanzicht; we moeten de isometrische projectie implementeren voor een isometrische weergave.
3. Isometrische Projectie
De beste technische uitleg van wat "isometrische projectie" betekent, voor zover ik weet, is uit dit artikel door Clint Bellanger:
We hoek onze camera langs twee assen (swing de camera 45 graden aan de ene kant, vervolgens 30 graden omlaag). Hiermee maakt je een raster van de ruitvormige (ruit) waar de raster ruimten twee keer zo groot zijn als ze hoog zijn. Deze stijl werd gepopulariseerd door Strategiespellen en actie RPG's. Als we kijken naar een kubus in deze weergave, drie zijden zijn zichtbaar (boven en twee tegenoverliggende zijden).
Hoewel het klinkt een beetje ingewikkeld, is eigenlijk de uitvoering van deze weergave eenvoudig. Wat we nodig hebben om te begrijpen is de relatie tussen 2D en de isometrische ruimte - dat wil zeggen, de relatie tussen het niveau gegevens en de beeld; de transformatie van top-down "Cartesiaanse" coördinaten naar isometrische coördinaten.



(We zijn niet een zeshoekige tegel gebaseerd techniek, dat een andere manier is van de isometrische werelden gezien.)
Isometrische Tegels Plaatsen
Verhuren mij uitproberen voor het vereenvoudigen van de relatie tussen niveau gegevens opgeslagen als een 2D matrix en de isometrische weergave - dat wil zeggen, hoe we transformeren Cartesiaanse coördinaten naar isometrische coördinaten.
We zullen proberen om de isometrische weergave maken voor onze muur-omsloten grasland niveau gegevens:
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]] |
In dit scenario kunnen wij een walkable gebied vaststellen door te controleren of het element van de matrix 0
op die coördinaat is, waardoor die aangeeft gras. De uitvoering van de 2D weergave van het bovenstaande niveau was een simpele iteratie met twee lussen, vierkante tegels compensatie elk met de vaste tegel hoogte en breedte van de tegel te plaatsen.
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) |
Voor de isometrische weergave, blijft de code alleen hetzelfde, maar de wijzigingen van de functie placeTile()
.
Voor een isometrisch aanzicht moeten we de bijbehorende isometrische coördinaten binnen de lussen berekenen.
De vergelijkingen om dit te doen zijn als volgt, waar isoX
en isoY
isometrische x - en y-coördinaten vormen, en cartX
en cartY
Cartesiaanse x - en y-coördinaten vertegenwoordigen:
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; |
Deze functies laten zien hoe je kunt converteren van het ene systeem naar het andere:
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 |
}
|
De pseudocode voor de lus ziet er dan als volgt:
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))) |



Als voorbeeld, laten we eens kijken hoe een typische 2D positie wordt omgezet naar een isometrische positie:
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]; |
Ook een input van [0, 0]
zal resulteren in [0, 0]
, en [10, 5]
zal [5, 7.5]
.
De bovenstaande methode ons in staat stelt om te maken een directe correlatie tussen de 2D niveau gegevens en de isometrische coördinaten. We kunnen van de tegel coördinaten vinden in de niveau gegevens uit de cartesiaanse coördinaten met behulp van deze functie:
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 |
}
|
(Hier, wij in wezen veronderstellen dat tegel hoogte en tegel breedte zijn gelijk, net als in de meeste gevallen.)
Vandaar, van een paar schermcoördinaten (isometrische), kan vinden we tegel coördinaten door te bellen naar:
1 |
getTileCoordinates(isoTo2D(screen point), tile height); |
Dit scherm punt zou kunnen zijn, bijvoorbeeld een muis klik op positie of de positie van een pick-up.
Tip: Een andere methode van plaatsing is de Zigzag model, waarin een andere aanpak helemaal.
Verplaatsen in Isometrische Coördinaten
Beweging is heel eenvoudig: je manipuleren je gegevens van de game wereld in Cartesiaanse coördinaten en gewoon gebruik maken van de bovenstaande functies voor het bijwerken van het op het scherm. Bijvoorbeeld, als je een karakter voorwaarts in de positieve y-richting te verplaatsen wilt, kunt je gewoon de eigenschap y
stapsgewijs wijzigen en haar positie vervolgens omzetten in isometrische coördinaten:
1 |
y = y + speed; |
2 |
placetile(twoDToIso(new Point(x, y))) |
Diepte Sorteren
Naast normale plaatsing, zullen we moeten zorgen voor diepte sorteren voor het tekenen van de isometrische wereld. Dit zorgt ervoor dat onderdelen dichter naar de speler objecten verder weg zijn getekend.
De eenvoudigste diepte sorteermethode is gewoon het gebruik van de Cartesische waarde van de y-coördinaat, zoals vermeld in deze Quick Tip: verdere omhoog het scherm het object is, hoe eerder het moet worden getrokken. Dit werkt goed, zolang we hebben niet alle sprites die bezetten meer dan een ruimte van één tegel.
De meest efficiënte manier van diepte sorteren voor isometrische werelden is te breken alle tegels in één-tegel standaardafmetingen en niet toe te staan van grotere afbeeldingen. Bijvoorbeeld, hier is een tegel die niet passen in de standaard blokgrootte - Zie hoe kunnen we opsplitsen in meerdere tegels die elke aan de tegel afmetingen:



4. Het Maken van de Kunst
Isometrische kunst pixel kunst kan zijn, maar het niet hoeft te worden. Bij de behandeling met isometrische pixelart, vertelt RhysD de gids je bijna alles wat die je moet weten. Enkele theorie kan worden teruggevonden op Wikipedia.
Bij het maken van isometrische art, zijn de algemene regels
- Beginnen met een lege isometrische raster en voldoen aan de pixel perfecte precisie.
- Proberen te breken van kunst in één isometrische tegel beelden.
- Probeer ervoor te zorgen dat elke tegel een van beide is beloopbaar of niet-beloopbaar. Het zou ingewikkeld zijn als wij nodig hebt aan één tegel die zowel walkable en niet-walkable gebieden bevat.
- De meeste tegels moet naadloos naast elkaar in een of meer richtingen.
- Schaduwen kunnen lastig uit te voeren, tenzij we gebruik maken van een gelaagde benadering waar we schaduwen op de grond laag tekenen en tekent je de held (of bomen of andere objecten) op de bovenste laag. Als de aanpak die je niet multi-gelaagde, zorg ervoor dat schaduwen vallen aan de voorzijde, zodat ze niet zal op, zeg, de held vallen wanneer hij achter een boom staat.
- In het geval dat je nodig hebt om een groter is dan de standaard isometrische tegel tegel-afbeelding te gebruiken, probeer te gebruiken van een dimensie die een veelvoud van de grootte van de tegel iso is. Het is beter om een gelaagde benadering in dergelijke gevallen, waar we de kunst in verschillende stukken die zijn gebaseerd op de hoogte opsplitsen kunt. Bijvoorbeeld, een boom kan worden gesplitst in drie stukken: de wortel, de romp en het gebladerte. Dit maakt het gemakkelijker om te sorteren diepten als we stukken in de bijbehorende lagen die overeenkomt met hun hoogten kunnen trekken.
Isometrische tegels die groter dan de één tegel afmetingen zijn zal kwesties maken met diepte sorteren. Enkele van de kwesties worden besproken in deze links:
5. Isometrische Tekens
Uitvoering van tekens in isometrische weergave is niet ingewikkeld als het klinkt. Kunst karakter dient te worden gemaakt volgens bepaalde normen. Eerst moeten we bepalen hoeveel bewegingsrichtingen zijn toegestaan in onze game - meestal bieden games four-way beweging of beweging met acht richtingen



Voor een weergave van bovenaf, kunnen we maken van een set van teken animaties in één richting, en draait je hen eenvoudig voor alle anderen. Voor isometrische tekens moeten we elke animatie opnieuw renderen in elk van de toegestane richtingen - dus voor beweging met acht richtingen moeten we acht animaties maken voor elke actie. Voor het gemak van begrip duiden we meestal de richtingen als Noord, noordwesten, West, Zuidwest, Zuid, South-East, East, en Noord-Oosten, tegen de klok, in die volgorde.



We plaatst je tekens op dezelfde manier dat we tegels plaatsen. De beweging van een karakter wordt bereikt door berekening van het verkeer in Cartesiaanse coördinaten en vervolgens te converteren naar isometrische coördinaten. Laten we aannemen dat we zijn met behulp van het toetsenbord om te controleren van het teken.
We zullen twee variabelen, dX
en dY
, op basis van de directionele toetsen instellen. Standaard deze variabelen 0
zal worden, en zal worden bijgewerkt volgens de grafiek hieronder, waar U
, D
, R
en L
de omhoog duiden, omlaag, links en rechts pijltoetsen, respectievelijk. Een waarde van 1
onder een sleutel geeft die toets wordt gedrukt; 0
betekent dat de sleutel niet wordt ingedrukt.
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, met behulp van de waarden van dX
en dY
, we kunnen updaten het Cartesiaanse coördinaten zo dus:
1 |
newX = currentX + (dX * speed); |
2 |
newY = currentY + (dY * speed); |
Dus dX
en dY
staan voor de verandering in de x - en y-positie van het teken, gebaseerd op de toetsen die worden ingedrukt.
Zoals we al hebben besproken, kunnen we gemakkelijk de nieuwe isometrische coördinaten, berekenen:
1 |
Iso = twoDToIso(new Point(newX, newY)) |
Zodra we de nieuwe isometrische positie hebben, moeten we dat doen verhuizen teken naar deze positie. Op basis van de waarden die wij voor dX
en dY
hebben, kunnen we beslissen welke richting het teken wordt geconfronteerd met en gebruiken van het bijbehorende teken art.
Botsing Detectie
Botsingdetectie wordt gedaan door te controleren of de tile op de berekende nieuwe positie een niet-walkable tegel is. Dus, zodra we de nieuwe positie vinden, wij onmiddellijk de teken er niet verplaatsen, maar eerst te controleren om te zien welke tegel die ruimte inneemt.
1 |
tile coordinate = getTileCoordinates(isoTo2D(iso point), tile height); |
2 |
if (isWalkable(tile coordinate)) { |
3 |
moveCharacter(); |
4 |
} else { |
5 |
//do nothing; |
6 |
} |
In de functie isWalkable()
controleren we of de waarde van de array niveau gegevens op bepaalde coördinaat een walkable tegel of niet is. We moeten oppassen voor het bijwerken van de richting waarin het teken wordt geconfronteerd - zelfs als hij niet beweegt, zoals in het geval van hem raken van een niet-walkable tegel.
Diepte Sorteren Met Tekens
Overwegen een karakter en een boom tegel in de isometrische wereld.
Voor goed begrip diepte sorteren, moeten we begrijpen dat wanneer het karakter van de x - en y-coördinaten minder dan die van de boom zijn, de boom het teken overlapt. Wanneer het karakter van de x - en y-coördinaten groter dan die van de boom zijn, overlapt het teken de boom.
Als ze de dezelfde x-coördinaat, hebben dan wij beslissen op basis van de y-coördinaat alleen: welke is de hogere y-coördinaat de andere overlapt. Als ze hetzelfde y-coördinaat hebben dan wij beslissen op basis van de x-coördinaat alleen: welke is de hogere x-coördinaat overlapt anderzijds.
Een vereenvoudigde versie van dit is gewoon sequentieel te trekken de niveaus vanaf de verste tegel - dat wil zeggen, tegel[0][0]
- teken vervolgens alle tegels in elke rij één voor één. Als een karakter een tegel neemt, we trekken de tegel van de grond eerst en dan maken de karakter-tegel. Dit werkt prima, omdat het karakter niet kan een tegel van de muur bezetten.
Diepte sorteren moet worden gedaan telkens een tegel wijzigingen positie. Bijvoorbeeld, moeten we om het te doen wanneer tekens verplaatsen. We updaten dan de weergegeven scène, na het uitvoeren van de sorteerbewerking diepte, zodat de wijzigingen van de diepte.
6. Hebben een Go!
Nu, benut je nieuwe kennis door het creëren van een werkend prototype, met opties voor toetsenbordbediening en goede diepte sorteren en botsing detectie. Hier is mijn demo:
Klik hierop om aan te geven van de SWF focus, en vervolgens met de pijltoetsen. Klik hier voor de full-sized versie.
Misschien vindt je deze klasse nut handig (ik heb het geschreven in AS3, maar je moet zitten kundig te begrijpen in elke andere programmeertaal):
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 |
}
|
Als je echt vast zit, is hier de volledige code van mijn demo (in Flash en AS3 tijdlijn code mag wordengegeven):
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(); |
Registratie Punten
Speciale aandacht besteden aan de registratiepunten van de tegels en de held. (Registratiepunten kunnen worden beschouwd als de oorsprong punten voor elke bijzondere sprite.) Deze vallen meestal niet in de afbeelding, maar zijn eerder de linkerbovenhoek van het selectiekader van de sprite.
We zullen onze tekencode moeten wijzigen om de registratiepunten correct te corrigeren, voornamelijk voor de held.
Botsing Detectie
Een ander interessant punt om op te merken is dat we berekenen botsingdetectie op basis van het punt waar de held is.
Maar de held volume, heeft en kan niet nauwkeurig worden vertegenwoordigd door één aanspreekpunt, dus we de held te vertegenwoordigen als een rechthoek en een selectievakje voor botsingen tegen elke hoek van deze rechthoek moeten zodat er geen overlappingen met andere tegels en dus geen diepte artefacten.
Snelkoppelingen
In de demo Ververs ik gewoon de scène opnieuw elk frame op basis van de nieuwe positie van de held. We vinden de tegel die de held inneemt en de held op de top van de tegel van de grond te trekken wanneer de rendering lussen die tegels bereiken.
Maar indien wij dichterbij kijken, zullen we vinden dat er is geen behoefte om te doorlopen alle tegels in dit geval. De gras tegels en de boven en linkerrand wandtegels worden altijd getekend voordat de held wordt getekend, zodat we niet steeds hoeven ze helemaal te vernieuwen. Ook zijn de onderste en rechtse wandtegels altijd voor de held en dus getekend na de held is getekend
In wezen moeten, alleen wij uitvoeren van diepte sorteren tussen de muur binnen het actieve gebied en de held - dat wil zeggen twee tegels. Deze soorten snelkoppelingen te merken zal je helpen besparen een heleboel verwerkingstijd, die kan worden cruciaal voor de prestaties.
Conclusie
Onderhand, hebt je een grote basis voor het bouwen van isometrische spellen van je eigen: kunt je het renderen van de wereld en de objecten daarin, vertegenwoordigen niveau gegevens in eenvoudige 2D arrays, converteren tussen Cartesiaanse en isometrische coordiates, en deal met begrippen zoals diepte sorteren en animatie van het karakter. Geniet van het maken van isometrische werelden!