# 开发者初级教程：制作等距游戏世界

1、等距的游戏世界

2、贴图游戏

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

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

1for (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)

3、等距投影

“我们沿着两个轴取摄像机角度（游戏邦注：将摄像机调成侧边45度后再下向30度）。这就产生了各个网格宽度为高度2倍的菱形（斜方形）网格。这种方法广泛运用于策略游戏和动作RPG。如果我们以这种角度看方块，就能看到方块的三个面（上面和两个侧面）。”

（我们不考虑六边形贴图技术，那是另一种制作等距游戏世界的方法。）

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

1for (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)

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;

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 }

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)))

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

1function 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}

（这里，我们假设贴图高度和宽度是相等的）

1 getTileCoordinates(isoTo2D(screen point), tile height);

1 y = y + speed;
2 placetile(twoDToIso(new Point(x, y)))

4、制作美术

1)从空白的等距网格开始，保证象素精确度。

2)将画面分成单个等距贴图图像。

3)确保各个贴图可通行或不可通行。如果一个贴图中既包含可通行区域又包含不可通行区域，那么就比较复杂了。

4)大部分贴图都必须在一个方向或多个方向上无缝平铺。

5)阴影可能比较难实现，除非使用图层法，也就是在底层上绘制阴影，然后在顶层上绘制角色（或树木等其他对象）。如果你使用的不是多图层法，那么就要保证阴影落在前面，这样当角色站在树后面时才不会产生错乱。

6)如果你不得不使用比标准等距贴图大的图像，那么就要保证图像的大小为等距贴图的倍数。在这种情况下，最好使用图层法，这样就可以根据高度把图像分成不同的小块。这使得深度排序更容易，因为我们可以在符合其高度的相应图层上绘制小块。

5、等距角色

01   Key       Pos
02 U D R L    dX dY
03 ================
04 0 0 0 0     0  0
05 1 0 0 0     0  1
06 0 1 0 0     0 -1
07 0 0 1 0     1  0
08 0 0 0 1    -1  0
09 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

1 newX = currentX + (dX * speed);
2 newY = currentY + (dY * speed);

1 Iso = twoDToIso(new Point(newX, newY))

1 tile coordinate = getTileCoordinates(isoTo2D(iso point), tile height);
2 if (isWalkable(tile coordinate)) {
3   moveCharacter();
4 } else {
5   //do nothing;
6 }

6、实验

package com.csharks.juwalbose
{
import flash.display.Sprite;
import flash.geom.Point;

public class IsoHelper
{
/**
* convert an isometric point to 2D
* */
public static function isoTo2D(pt:Point):Point{
//gx=(2*isoy+isox)/2;
//gy=(2*isoy-isox)/2
var tempPt:Point=new Point(0,0);
tempPt.x=(2*pt.y+pt.x)/2;
tempPt.y=(2*pt.y-pt.x)/2;
return(tempPt);
}
/**
* convert a 2d point to isometric
* */
public static function twoDToIso(pt:Point):Point{
//gx=(isox-isoxy;
//gy=(isoy+isox)/2
var tempPt:Point=new Point(0,0);
tempPt.x=pt.x-pt.y;
tempPt.y=(pt.x+pt.y)/2;
return(tempPt);
}

/**
* convert a 2d point to specific tile row/column
* */
public static function getTileCoordinates(pt:Point, tileHeight:Number):Point{
var tempPt:Point=new Point(0,0);
tempPt.x=Math.floor(pt.x/tileHeight);
tempPt.y=Math.floor(pt.y/tileHeight);

return(tempPt);
}

/**
* convert specific tile row/column to 2d point
* */
public static function get2dFromTileCoordinates(pt:Point, tileHeight:Number):Point{
var tempPt:Point=new Point(0,0);
tempPt.x=pt.x*tileHeight;
tempPt.y=pt.y*tileHeight;

return(tempPt);
}

}
}

// Uses senocular’s KeyObject class
// http://www.senocular.com/flash/actionscript/?file=ActionScript_3.0/com/senocular/utils/KeyObject.as

import flash.display.Sprite;
import com.csharks.juwalbose.IsoHelper;
import flash.display.MovieClip;
import flash.geom.Point;
import flash.filters.GlowFilter;
import flash.events.Event;
import com.senocular.utils.KeyObject;
import flash.ui.Keyboard;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Rectangle;

var levelData=[[1,1,1,1,1,1],
[1,0,0,2,0,1],
[1,0,1,0,0,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,1,1,1,1,1]];

var tileWidth:uint = 50;
var borderOffsetY:uint = 70;
var borderOffsetX:uint = 275;

var facing:String = “south”;
var currentFacing:String = “south”;
var hero:MovieClip=new herotile();
hero.clip.gotoAndStop(facing);
var heroPointer:Sprite;
var key:KeyObject = new KeyObject(stage);//Senocular KeyObject Class
var heroHalfSize:uint=20;

//the tiles
var grassTile:MovieClip=new TileMc();
grassTile.gotoAndStop(1);
var wallTile:MovieClip=new TileMc();
wallTile.gotoAndStop(2);

//the canvas
var bg:Bitmap = new Bitmap(new BitmapData(650,450));
var rect:Rectangle=bg.bitmapData.rect;

//to handle depth
var overlayContainer:Sprite=new Sprite();

//to handle direction movement
var dX:Number = 0;
var dY:Number = 0;
var idle:Boolean = true;
var speed:uint = 5;
var heroCartPos:Point=new Point();
var heroTile:Point=new Point();

function createLevel()
{
var tileType:uint;
for (var i:uint=0; i<levelData.length; i++)
{
for (var j:uint=0; j<levelData.length; j++)
{
tileType = levelData[i][j];
placeTile(tileType,i,j);
if (tileType == 2)
{
levelData[i][j] = 0;
}
}
}
overlayContainer.alpha=0.5;
overlayContainer.scaleX=overlayContainer.scaleY=0.5;
overlayContainer.y=290;
overlayContainer.x=10;
depthSort();
}

//place the tile based on coordinates
function placeTile(id:uint,i:uint,j:uint)
{
var pos:Point=new Point();
if (id == 2)
{

id = 0;
pos.x = j * tileWidth;
pos.y = i * tileWidth;
pos = IsoHelper.twoDToIso(pos);
hero.x = borderOffsetX + pos.x;
hero.y = borderOffsetY + pos.y;
heroCartPos.x = j * tileWidth;
heroCartPos.y = i * tileWidth;
heroTile.x=j;
heroTile.y=i;
heroPointer=new herodot();
heroPointer.x=heroCartPos.x;
heroPointer.y=heroCartPos.y;

}
var tile:MovieClip=new cartTile();
tile.gotoAndStop(id+1);
tile.x = j * tileWidth;
tile.y = i * tileWidth;
}

//the game loop
function loop(e:Event)
{
if (key.isDown(Keyboard.UP))
{
dY = -1;
}
else if (key.isDown(Keyboard.DOWN))
{
dY = 1;
}
else
{
dY = 0;
}
if (key.isDown(Keyboard.RIGHT))
{
dX = 1;
if (dY == 0)
{
facing = “east”;
}
else if (dY==1)
{
facing = “southeast”;
dX = dY=0.5;
}
else
{
facing = “northeast”;
dX=0.5;
dY=-0.5;
}
}
else if (key.isDown(Keyboard.LEFT))
{
dX = -1;
if (dY == 0)
{
facing = “west”;
}
else if (dY==1)
{
facing = “southwest”;
dY=0.5;
dX=-0.5;
}
else
{
facing = “northwest”;
dX = dY=-0.5;
}
}
else
{
dX = 0;
if (dY == 0)
{
//facing=”west”;
}
else if (dY==1)
{
facing = “south”;
}
else
{
facing = “north”;
}
}
if (dY == 0 && dX == 0)
{
hero.clip.gotoAndStop(facing);
idle = true;
}
else if (idle||currentFacing!=facing)
{
idle = false;
currentFacing = facing;
hero.clip.gotoAndPlay(facing);
}
if (! idle && isWalkable())
{
heroCartPos.x +=  speed * dX;
heroCartPos.y +=  speed * dY;
heroPointer.x=heroCartPos.x;
heroPointer.y=heroCartPos.y;

var newPos:Point = IsoHelper.twoDToIso(heroCartPos);
//collision check
hero.x = borderOffsetX + newPos.x;
hero.y = borderOffsetY + newPos.y;
heroTile=IsoHelper.getTileCoordinates(heroCartPos,tileWidth);
depthSort();
//trace(heroTile);
}
tileTxt.text=”Hero is on x: “+heroTile.x +” & y: “+heroTile.y;
}

//check for collision tile
function isWalkable():Boolean{
var able:Boolean=true;
var newPos:Point =new Point();
newPos.x=heroCartPos.x +  (speed * dX);
newPos.y=heroCartPos.y +  (speed * dY);
switch (facing){
case “north”:
newPos.y-=heroHalfSize;
break;
case “south”:
newPos.y+=heroHalfSize;
break;
case “east”:
newPos.x+=heroHalfSize;
break;
case “west”:
newPos.x-=heroHalfSize;
break;
case “northeast”:
newPos.y-=heroHalfSize;
newPos.x+=heroHalfSize;
break;
case “southeast”:
newPos.y+=heroHalfSize;
newPos.x+=heroHalfSize;
break;
case “northwest”:
newPos.y-=heroHalfSize;
newPos.x-=heroHalfSize;
break;
case “southwest”:
newPos.y+=heroHalfSize;
newPos.x-=heroHalfSize;
break;
}
newPos=IsoHelper.getTileCoordinates(newPos,tileWidth);
if(levelData[newPos.y][newPos.x]==1){
able=false;
}else{
//trace(“new”,newPos);
}
return able;
}

//sort depth & draw to canvas
function depthSort()
{
bg.bitmapData.lock();
bg.bitmapData.fillRect(rect,0xffffff);
var tileType:uint;
var mat:Matrix=new Matrix();
var pos:Point=new Point();
for (var i:uint=0; i<levelData.length; i++)
{
for (var j:uint=0; j<levelData.length; j++)
{
tileType = levelData[i][j];
//placeTile(tileType,i,j);

pos.x = j * tileWidth;
pos.y = i * tileWidth;
pos = IsoHelper.twoDToIso(pos);
mat.tx = borderOffsetX + pos.x;
mat.ty = borderOffsetY + pos.y;
if(tileType==0){
bg.bitmapData.draw(grassTile,mat);
}else{
bg.bitmapData.draw(wallTile,mat);
}
if(heroTile.x==j&&heroTile.y==i){
mat.tx=hero.x;
mat.ty=hero.y;
bg.bitmapData.draw(hero,mat);
}

}
}
bg.bitmapData.unlock();
}
createLevel();

Creating Isometric Worlds: A Primer for Game Developers

by Juwal Bose

In this tutorial, I’ll give you a broad overview of what you need to know to create isometric worlds. You’ll learn what the isometric projection is, and how to represent isometric levels as 2D arrays. We’ll formulate relationships between the view and the logic, so that we can easily manipulate objects on screen and handle tile-based collision detection. We’ll also look at depth sorting and character animation.

1. The Isometric World

Isometric view is a display method used to create an illusion of 3D for an otherwise 2D game – sometimes referred to as pseudo 3D or 2.5D. These images (taken from the original Diablo and Age of Empires games) illustrate what I mean:

Classic Diablo

Age of Empires

Implementing an isometric view can be done in many ways, but for the sake of simplicity I’ll focus on a tile-based approach, which is the most efficient and widely used method. I’ve overlaid each screenshot above with a diamond grid showing how the terrain is split up into tiles.

2. Tile-Based Games

In the tile-based approach, each visual element is broken down into smaller pieces, called tiles, of a standard size. These tiles will be arranged to form the game world according to pre-determined level data – usually a 2D array.

Related Posts

Tony Pa’s tile-based tutorials.

For example let us consider a standard top-down 2D view with two tiles – a grass tile and a wall tile – as shown here:

Some simple tiles

These tiles are each the same size as each other, and are each square, so the tile height and tile width are the same.

For a level with grassland enclosed on all sides by walls, the level data’s 2D array will look like this:

[[1,1,1,1,1,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,1,1,1,1,1]]

Here, 0 denotes a grass tile and 1 denotes a wall tile. Arranging the tiles according to the level data will produce the below level image:

A simple level, displayed in a top-down view.

We can enhance this by adding corner tiles and separate vertical and horizontal wall tiles, requiring five additional tiles:

[[3,1,1,1,1,4],
[2,0,0,0,0,2],
[2,0,0,0,0,2],
[2,0,0,0,0,2],
[2,0,0,0,0,2],
[6,1,1,1,1,5]]

Enhanced level with tile numbers

I hope the concept of the tile-based approach is now clear. This is a straightforward 2D grid implementation, which we could code like so:

for (i, loop through rows)
for (j, loop through columns)
x = j * tile width
y = i * tile height
tileType = levelData[i][j]
placetile(tileType, x, y)

Here we assume that tile width and tile height are equal (and the same for all tiles), and match the tile images’ dimensions. So, the tile width and tile height for this example are both 50px, which makes up the total level size of 300x300px – that is, six rows and six columns of tiles measuring 50x50px each.

In a normal tile-based approach, we either implement a top-down view or a side view; for an isometric view we need to implement the isometric projection.

3. Isometric Projection

The best technical explanation of what “isometric projection” means, as far as I’m aware, is from this article by Clint Bellanger:

We angle our camera along two axes (swing the camera 45 degrees to one side, then 30 degrees down). This creates a diamond (rhombus) shaped grid where the grid spaces are twice as wide as they are tall. This style was popularized by strategy games and action RPGs. If we look at a cube in this view, three sides are visible (top and two facing sides).

Although it sounds a bit complicated, actually implementing this view is straightforward. What we need to understand is the relation between 2D space and the isometric space – that is, the relation between the level data and view; the transformation from top-down “Cartesian” coordinates to isometric coordinates.

Cartesian grid vs. isometric grid.

(We are not considering a hexagonal tile based technique, which is another way of implementing isometric worlds.)

Placing Isometric Tiles

Let me try to simplify the relationship between level data stored as a 2D array and the isometric view – that is, how we transform Cartesian coordinates to isometric coordinates.

We will try to create the isometric view for our wall-enclosed grassland level data:

[[1,1,1,1,1,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,1,1,1,1,1]]

In this scenario we can determine a walkable area by checking whether the array element is 0 at that coordinate, thereby indicating grass. The 2D view implementation of the above level was a straightforward iteration with two loops, placing square tiles offsetting each with the fixed tile height and tile width values.

for (i, loop through rows)
for (j, loop through columns)
x = j * tile width
y = i * tile height
tileType = levelData[i][j]
placetile(tileType, x, y)

For the isometric view, the code remains the same, but the placeTile() function changes.

For an isometric view we need to calculate the corresponding isometric coordinates inside the loops.

The equations to do this are as follows, where isoX and isoY represent isometric x- and y-coordinates, and cartX and cartY represent Cartesian x- and y-coordinates:

//Cartesian to isometric:

isoX = cartX – cartY;
isoY = (cartX + cartY) / 2;

//Isometric to Cartesian:

cartX = (2 * isoY + isoX) / 2;
cartY = (2 * isoY – isoX) / 2;

These functions show how you can convert from one system to another:

function isoTo2D(pt:Point):Point{
var tempPt:Point = new Point(0, 0);
tempPt.x = (2 * pt.y + pt.x) / 2;
tempPt.y = (2 * pt.y – pt.x) / 2;
return(tempPt);
}

function twoDToIso(pt:Point):Point{
var tempPt:Point = new Point(0,0);
tempPt.x = pt.x – pt.y;
tempPt.y = (pt.x + pt.y) / 2;
return(tempPt);
}
The pseudocode for the loop then looks like this:

for(i, loop through rows)
for(j, loop through columns)
x = j * tile width
y = i * tile height
tileType = levelData[i][j]
placetile(tileType, twoDToIso(new Point(x, y)))

Our wall-enclosed grassland in an isometric view.

As an example, let’s see how a typical 2D position gets converted to an isometric position:

2D point = [100, 100];
// twoDToIso(2D point) will be calculated as below
isoX = 100 – 100; // = 0
isoY = (100 + 100) / 2;  // = 100
Iso point == [0, 100];
Similarly, an input of [0, 0] will result in [0, 0], and [10, 5] will give [5, 7.5].

The above method enables us to create a direct correlation between the 2D level data and the isometric coordinates. We can find the tile’s coordinates in the level data from its Cartesian coordinates using this function:

function getTileCoordinates(pt:Point, tileHeight:Number):Point{
var tempPt:Point = new Point(0, 0);
tempPt.x = Math.floor(pt.x / tileHeight);
tempPt.y = Math.floor(pt.y / tileHeight);
return(tempPt);
}

(Here, we essentially assume that tile height and tile width are equal, as in most cases.)

Hence, from a pair of screen (isometric) coordinates, we can find tile coordinates by calling:

getTileCoordinates(isoTo2D(screen point), tile height);
This screen point could be, say, a mouse click position or a pick-up position.
Tip: Another method of placement is the Zigzag model, which takes a different approach altogether.

Moving in Isometric Coordinates

Movement is very easy: you manipulate your game world data in Cartesian coordinates and just use the above functions for updating it on the screen. For example, if you want to move a character forward in the positive y-direction, you can simply increment its y property and then convert its position to isometric coordinates:

y = y + speed;
placetile(twoDToIso(new Point(x, y)))
Depth Sorting

In addition to normal placement, we will need to take care of depth sorting for drawing the isometric world. This makes sure that items closer to the player are drawn on top of items farther away.

The simplest depth sorting method is simply to use the Cartesian y-coordinate value, as mentioned in this Quick Tip: the further up the screen the object is, the earlier it should be drawn. This work well as long as we do not have any sprites that occupy more than a single tile space.

The most efficient way of depth sorting for isometric worlds is to break all the tiles into standard single-tile dimensions and not to allow larger images. For example, here is a tile which does not fit into the standard tile size – see how we can split it into multiple tiles which each fit the tile dimensions:

A large image is split into multiple tiles of standard isometric dimensions

4. Creating the Art

Isometric art can be pixel art, but it doesn’t have to be. When dealing with isometric pixel art, RhysD’s guide tells you almost everything you need to know. Some theory can be found on Wikipedia as well.

When creating isometric art, the general rules are

Try to break art into single isometric tile images.

Try to make sure that each tile is either walkable or non-walkable. It will be complicated if we need to accommodate a single tile that contains both walkable and non-walkable areas.

Most tiles will need to seamlessly tile in one or more directions.

Shadows can be tricky to implement, unless we use a layered approach where we draw shadows on the ground layer and then draw the hero (or trees, or other objects) on the top layer. If the approach you use is not multi-layered, make sure shadows fall to the front so that they won’t fall on, say, the hero when he stands behind a tree.

In case you need to use a tile image larger than the standard isometric tile size, try to use a dimension which is a multiple of the iso tile size. It is better to have a layered approach in such cases, where we can split the art into different pieces based on its height. For example, a tree can be split into three pieces: the root, the trunk, and the foliage. This makes it easier to sort depths as we can draw pieces in corresponding layers which corresponds with their heights.

Isometric tiles that are larger than the single tile dimensions will create issues with depth sorting. Some of the issues are discussed in these links:

Related Posts

Bigger tiles.

Splitting and Painter’s algorithm.

Openspace’s post on effective ways of splitting up larger tiles.

5. Isometric Characters

Implementing characters in isometric view is not complicated as it may sound. Character art needs to be created according to certain standards. First we will need to fix how many directions of motion are permitted in our game – usually games will provide four-way movement or eight-way movement.

Eight-way navigation directions in top-down and isometric views.

For a top-down view, we could create a set of character animations facing in one direction, and simply rotate them for all the others. For isometric character art, we need to re-render each animation in each of the permitted directions – so for eight-way motion we need to create eight animations for each action. For ease of understanding we usually denote the directions as North, North-West, West, South-West, South, South-East, East, and North-East, anti-clockwise, in that order.

An isometric character facing in different directions.

We place characters in the same way that we place tiles. The movement of a character is accomplished by calculating the movement in Cartesian coordinates and then converting to isometric coordinates. Let’s assume we are using the keyboard to control the character.

We will set two variables, dX and dY, based on the directional keys pressed. By default these variables will be 0, and will be updated as per the chart below, where U, D, R and L denote the Up, Down, Right and Left arrow keys, respectively. A value of 1 under a key represents that key being pressed; 0 implies that the key is not being pressed.

Key       Pos
U D R L    dX dY
================
0 0 0 0     0  0
1 0 0 0     0  1
0 1 0 0     0 -1
0 0 1 0     1  0
0 0 0 1    -1  0
1 0 1 0     1  1
1 0 0 1    -1  1
0 1 1 0     1 -1
0 1 0 1    -1 -1

Now, using the values of dX and dY, we can update the Cartesian coordinates as so:

newX = currentX + (dX * speed);
newY = currentY + (dY * speed);

So dX and dY stand for the change in the x- and y-positions of the character, based on the keys pressed.
We can easily calculate the new isometric coordinates, as we’ve already discussed:

Iso = twoDToIso(new Point(newX, newY))

Once we have the new isometric position, we need to move the character to this position. Based on the values we have for dX and dY, we can decide which direction the character is facing and use the corresponding character art.

Collision Detection

Collision detection is done by checking whether the tile at the calculated new position is a non-walkable tile. So, once we find the new position, we don’t immediately move the character there, but first check to see what tile occupies that space.

tile coordinate = getTileCoordinates(isoTo2D(iso point), tile height);
if (isWalkable(tile coordinate)) {
moveCharacter();
} else {
//do nothing;
}

In the function isWalkable(), we check whether the level data array value at the given coordinate is a walkable tile or not. We must take care to update the direction in which the character is facing – even if he does not move, as in the case of him hitting a non-walkable tile.

Depth Sorting With Characters

Consider a character and a tree tile in the isometric world.

For properly understanding depth sorting, we must understand that whenever the character’s x- and y-coordinates are less than those of the tree, the tree overlaps the character. Whenever the character’s x- and y-coordinates are greater than that of the tree, the character overlaps the tree.

When they have the same x-coordinate, then we decide based on the y-coordinate alone: whichever has the higher y-coordinate overlaps the other. When they have same y-coordinate then we decide based on the x-coordinate alone: whichever has the higher x-coordinate overlaps the other.

A simplified version of this is to just sequentially draw the levels starting from the farthest tile – that is, tile – then draw all the tiles in each row one by one. If a character occupies a tile, we draw the ground tile first and then render the character tile. This will work fine, because the character cannot occupy a wall tile.

Depth sorting must be done every time any tile changes position. For instance, we need to do it whenever characters move. We then update the displayed scene, after performing the depth sort, to reflect the depth changes.

6. Have a Go!

Now, put your new knowledge to good use by creating a working prototype, with keyboard controls and proper depth sorting and collision detection. Here’s my demo:

Click to give the SWF focus, then use the arrow keys. Click here for the full-sized version.

You may find this utility class useful (I’ve written it in AS3, but you should be able to understand it in any other programming language):

+ expand source

If you get really stuck, here’s the full code from my demo (in Flash and AS3 timeline code form):

+ expand source

Registration Points

Give special consideration to the registration points of the tiles and the hero. (Registration points can be thought of as the origin points for each particular sprite.) These generally won’t fall inside the image, but rather will be the top left corner of the sprite’s bounding box.

We will have to alter our drawing code to fix the registration points correctly, mainly for the hero.

Collision Detection

Another interesting point to note is that we calculate collision detection based on the point where the hero is.

But the hero has volume, and cannot be accurately represented by a single point, so we need to represent the hero as a rectangle and check for collisions against each corner of this rectangle so that there are no overlaps with other tiles and hence no depth artifacts.

Shortcuts

In the demo, I simply redraw the scene again each frame based on the new position of the hero. We find the tile which the hero occupies and draw the hero on top of the ground tile when the rendering loops reach those tiles.

But if we look closer, we will find that there is no need to loop through all the tiles in this case. The grass tiles and the top and left wall tiles are always drawn before the hero is drawn, so we don’t ever need to redraw them at all. Also, the bottom and right wall tiles are always in front of the hero and hence drawn after the hero is drawn.

Essentially, then, we only need to perform depth sorting between the wall inside the active area and the hero – that is, two tiles. Noticing these kinds of shortcuts will help you save a lot of processing time, which can be crucial for performance.

Conclusion

By now, you should have a great basis for building isometric games of your own: you can render the world and the objects in it, represent level data in simple 2D arrays, convert between Cartesian and isometric coordiates, and deal with concepts like depth sorting and character animation. Enjoy creating isometric worlds!(source:gamedev.tutsplus)