# Creating Isometric Worlds: A Primer for Game Developers, Continued

In this tutorial, we'll build on the original Creating Isometric Worlds primer, and learn about implementing pickups, trigger tiles, level swapping, path finding and following, level scrolling, isometric height and isometric projectiles.

## 1. Pickups

Pickups are items that can be collected within the level, normally by simply walking over them - for example, coins, gems, cash, and ammo.

Pickup data can also be accommodated right into our level data as below:

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

In this level data, we use 8 to denote a pickup (1 and 0 represent walls and walkable tiles respectively, as before).

It's important to understand that 8 actually denotes two tiles, not just one: it means we need to first place a walkable grass tile and then place a pickup on top. This means that every pickup will always be on a grass tile. If we want it to be on a walkable brick tile, then we'll need another tile denoted by another number, say 9, that represents "pickup on brick tile".

Typical isometric art will have multiple walkable tiles - suppose we have 30. The above approach means that if we have N pickups we will need (N * 30) tiles in addition to the 30 original tiles, as each tile will need to have one version with pickups and one without. This is not very efficient; instead, we should try to dynamically create these combinations.

To do this, we can use another array with the pickup data alone, and use this to place pickup tiles atop the level layout data:

 1 // Level layout data  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]] 

...plus:

 1 // Pickup layout data  2 [[0,0,0,0,0,0],  3  [0,0,0,0,0,0],  4  [0,0,8,0,0,0],  5  [0,0,0,8,0,0],  6  [0,0,0,0,0,0],  7  [0,0,0,0,0,0]] 

...results in:

This approach ensures that we need only the 30 original tiles in addition to N pickup tiles, as we can create any combination by blending both pieces of art when rendering the level.

### Picking Up Pickups

Detecting pickups is done in the same way as detecting collision tiles, but after moving the character.

 1 tile coordinate= getTileCoordinates (isoTo2D (Iso point) , tile height);  2 if(isPickup(tile coordinate)) {  3  pickupItem(tile coordinate);  4 } 

In the function isPickup(tile coordinate), we check whether the pickup data array value at the given coordinate is a pickup tile or not. The number in the pickup array at that tile coordinate denotes the type of pickup.

We check for collisions before moving the character but check for pickups afterwards, because in the case of collisions the character should not occupy the spot if it is already occupied by the collision tile, but in case of pickups the character is free to move over it.

Another thing to note is that the collision data usually never changes, but the pickup data changes whenever we pick up an item. (This usually just involves changing the value in the pickup array from, say, 8 to 0.)

This leads to a problem: what happens when we need to restart the level, and thus reset all pickups back to their original positions? We do not have the information to do this, as the pickup array has been changed as the player picked up items. The solution is to use a duplicate array for pickups while in play and to keep the original pickup array intact - for instance, we use pickupsArray[] and pickupsLive[], clone the latter from the former at the start of the level, and only change pickupsLive[] during play.

You should notice that we check for pickups whenever the character is on that tile. This can happen multiple times within a second (we check only when the user moves, but we may go round and round within a tile) but the above logic won't fail; since we set the pickup array data to 0 the first time we detect a pickup, all subsequent isPickup(tile) checks will returns false for that tile.

## 2. Trigger Tiles

As the name suggests, trigger tiles cause something to happen when the player steps on them or presses a key while stepping on them. They might teleport the player to a different location, open a gate, or spawn an enemy, to give a few examples. In a sense, pickups are just a special form of trigger tiles: when the player steps on a tile containing a coin, the coin disappears and their coin counter increases.

Let's look at how we could implement a door that takes the player to a different level. The tile next to the door will be a trigger tile; when the player presses the Space bar, they'll proceed to the next level.

To change levels, all we need to do is swap the current level data array with that of the new level, and set the new tile position and direction for the hero character.

Suppose there are two levels with doors to allow passing between them. Since the ground tile next to the door will be the trigger tile in both levels, we can use this as the new position for the character when they appear in the level.

The implementation logic here is the same as for pickups, and again we use an array to store trigger values. This is inefficient and you should consider other data structures for this purpose, but let's keep this simple for the sake of the tutorial. Let the new level arrays be as below (7 denotes a door):

 1 // Level 1  2 [[1,1,1,1,1,1],  3  [1,0,0,0,0,1],  4  [1,0,0,0,0,7],  5  [1,0,0,0,0,1],  6  [1,0,0,0,1,1],  7  [1,1,1,1,1,1]];  8 9 // Level 2  10 [[1,1,1,1,1,1],  11  [1,0,0,0,1,1],  12  [7,0,0,0,0,1],  13  [1,0,0,0,0,1],  14  [1,1,0,0,0,1],  15  [1,1,1,1,1,1]]; 

Let levels have a few pickups, as detailed by the below pickup arrays:

 1 [[0,0,0,0,0,0],  2  [0,0,0,0,0,0],  3  [0,0,8,0,0,0],  4  [0,0,0,8,0,0],  5  [0,0,0,0,0,0],  6  [0,0,0,0,0,0]];  7 8 [[0,0,0,0,0,0],  9  [0,0,0,0,0,0],  10  [0,0,0,0,0,0],  11  [0,0,0,0,8,0],  12  [0,0,8,0,0,0],  13  [0,0,0,0,0,0]]; 

Let the corresponding trigger tile arrays for each level be as below:

 1 [[0,0,0,0,0,0],  2  [0,0,0,0,0,0],  3  [0,0,0,0,2,0],  4  [0,0,0,0,0,0],  5  [0,0,0,0,0,0],  6  [0,0,0,0,0,0]];  7 8 [[0,0,0,0,0,0],  9  [0,0,0,0,0,0],  10  [0,1,0,0,0,0],  11  [0,0,0,0,0,0],  12  [0,0,0,0,0,0],  13  [0,0,0,0,0,0]]; 

The values (1 and 2) denote the level which will be loaded when the player presses Space.

Here's the code that runs when the player hits that key:

 1 // Pseudocode  2 tile coordinate = getTileCoordinates(isoTo2D(Iso point), tile height);  3 if (isTrigger(tile coordinate)) {  4   doRelevantAction(tile coordinate);  5 } 

The function isTrigger() checks whether the trigger data array value at the given coordinate is greater than zero. If so, our code passes that value to doRelevantAction(), which decides which function to call next. For our purposes, we'll use the simple rule that if the value lies between 1 and 10, it's a door, and so this function will be called:

 1 function swapLevel(level) {  2  // swap level arrays, pickup arrays, and trigger arrays with new level's data  3  // move player's starting position to tile next to door that leads to previous level  4  // set character's direction  5 } 

Since the value of the tile in the trigger array also denotes the level which needs to be loaded, we can simply pass it to swapLevel(). This implies, in turn, that our game has ten levels.

Here is a working demo. Try picking up items by walking over them and swapping levels by standing next to doors and hitting Space.

I have made the trigger be activated when Space is released; if we just listen for the key being pressed then we end up in a loop where we swap between levels as long as the key is held down, since the character always spawns in the new level on top of a trigger tile.

Here is the full code (in AS3):

 1 import flash.display.Sprite;  2 import com.csharks.juwalbose.IsoHelper;  3 import flash.display.MovieClip;  4 import flash.geom.Point;  5 import flash.filters.GlowFilter;  6 import flash.events.Event;  7 import com.senocular.utils.KeyObject;  8 import flash.ui.Keyboard;  9 import flash.display.Bitmap;  10 import flash.display.BitmapData;  11 import flash.geom.Matrix;  12 import flash.geom.Rectangle;  13 import flash.events.KeyboardEvent;  14 15 var level1Data=[[1,1,1,1,1,1],  16 [1,0,0,0,0,1],  17 [1,0,0,0,0,7],  18 [1,0,0,0,0,1],  19 [1,0,0,0,1,1],  20 [1,1,1,1,1,1]];  21 22 var pickup1Array=[[0,0,0,0,0,0],  23 [0,0,0,0,0,0],  24 [0,0,8,0,0,0],  25 [0,0,0,8,0,0],  26 [0,0,0,0,0,0],  27 [0,0,0,0,0,0]];  28 29 var level2Data=[[1,1,1,1,1,1],  30 [1,0,0,0,1,1],  31 [7,0,0,0,0,1],  32 [1,0,0,0,0,1],  33 [1,1,0,0,0,1],  34 [1,1,1,1,1,1]];  35 36 var pickup2Array=[[0,0,0,0,0,0],  37 [0,0,0,0,0,0],  38 [0,0,0,0,0,0],  39 [0,0,0,0,8,0],  40 [0,0,8,0,0,0],  41 [0,0,0,0,0,0]];  42 43 var trigger1Array=[[0,0,0,0,0,0],  44 [0,0,0,0,0,0],  45 [0,0,0,0,2,0],  46 [0,0,0,0,0,0],  47 [0,0,0,0,0,0],  48 [0,0,0,0,0,0]];  49 50 var trigger2Array=[[0,0,0,0,0,0],  51 [0,0,0,0,0,0],  52 [0,1,0,0,0,0],  53 [0,0,0,0,0,0],  54 [0,0,0,0,0,0],  55 [0,0,0,0,0,0]];  56 57 58 var levelData:Array=new Array();  59 var pickupArray:Array=new Array();  60 var triggerArray:Array=new Array();  61 var currentLevel:uint=1;  62 var newLevel:uint=1;  63 64 var tileWidth:uint = 50;  65 var borderOffsetY:uint = 70;  66 var borderOffsetX:uint = 275;  67 68 var facing:String = "south";  69 var currentFacing:String = "south";  70 var hero:MovieClip=new herotile();  71 hero.clip.gotoAndStop(facing);  72 var heroPointer:Sprite;  73 var key:KeyObject = new KeyObject(stage);//Senocular KeyObject Class  74 var heroHalfSize:uint=20;  75 var pickupCount:uint=0;  76 77 //the tiles  78 var grassTile:MovieClip=new TileMc();  79 grassTile.gotoAndStop(1);  80 var wallTile:MovieClip=new TileMc();  81 wallTile.gotoAndStop(2);  82 var pickupTile:MovieClip=new TileMc();  83 pickupTile.gotoAndStop(3);  84 var doorTile:MovieClip=new TileMc();  85 doorTile.gotoAndStop(4);  86 87 //the canvas  88 var bg:Bitmap = new Bitmap(new BitmapData(650,450));  89 addChild(bg);  90 var rect:Rectangle=bg.bitmapData.rect;  91 92 //to handle depth  93 var overlayContainer:Sprite=new Sprite();  94 addChild(overlayContainer);  95 96 //to handle direction movement  97 var dX:Number = 0;  98 var dY:Number = 0;  99 var idle:Boolean = true;  100 var speed:uint = 5;  101 var heroCartPos:Point=new Point();  102 var heroTile:Point=new Point();  103 104 var spaceKeyUp:Boolean=false;  105 //initial hero position  106 var spawnPt:Point=new Point(1,3);  107 108 //add items to start level, add game loop  109 function createLevel()  110 {  111  if(currentLevel==1){  112  levelData=level1Data;  113  pickupArray=pickup1Array;  114  triggerArray=trigger1Array;  115  }else{  116  levelData=level2Data;  117  pickupArray=pickup2Array;  118  triggerArray=trigger2Array;  119  }  120  levelData[spawnPt.x][spawnPt.y]=2;  121   122  var tileType:uint;  123  for (var i:uint=0; i0);  315 }  316 function pickupItem(tilePt:Point):void{  317  pickupCount++;  318  pickupArray[tilePt.y][tilePt.x]=0;  319 }  320 //check for collision tile  321 function isWalkable():Boolean{  322  var able:Boolean=true;  323  var newPos:Point =new Point();  324  newPos.x=heroCartPos.x + (speed * dX);  325  newPos.y=heroCartPos.y + (speed * dY);  326  switch (facing){  327  case "north":  328  newPos.y-=heroHalfSize;  329  break;  330  case "south":  331  newPos.y+=heroHalfSize;  332  break;  333  case "east":  334  newPos.x+=heroHalfSize;  335  break;  336  case "west":  337  newPos.x-=heroHalfSize;  338  break;  339  case "northeast":  340  newPos.y-=heroHalfSize;  341  newPos.x+=heroHalfSize;  342  break;  343  case "southeast":  344  newPos.y+=heroHalfSize;  345  newPos.x+=heroHalfSize;  346  break;  347  case "northwest":  348  newPos.y-=heroHalfSize;  349  newPos.x-=heroHalfSize;  350  break;  351  case "southwest":  352  newPos.y+=heroHalfSize;  353  newPos.x-=heroHalfSize;  354  break;  355  }  356  newPos=IsoHelper.getTileCoordinates(newPos,tileWidth);  357  if(levelData[newPos.y][newPos.x]>=1){  358  able=false;  359  }else{  360  //trace("new",newPos);  361  }  362  return able;  363 }  364 365 //sort depth & draw to canvas  366 function depthSort()  367 {  368  bg.bitmapData.lock();  369  bg.bitmapData.fillRect(rect,0xffffff);  370  var tileType:uint;  371  var mat:Matrix=new Matrix();  372  var pos:Point=new Point();  373  for (var i:uint=0; i

## 3. Path Finding

Path finding and path following is a fairly complicated process. There are various approaches using different algorithms for finding the path between two points, but our level data is a 2D array things are easier than they might otherwise be - we have well defined and unique nodes which the player can occupy and we can easily check whether they are walkable.

A detailed overview of pathfinding algorithms is outside of the scope of this article but I will try to explain the most common way it works: the shortest path algorithm, of which A* and Dijkstra's algorithms are famous implementations.

We aim to find nodes connecting a starting node and an ending node. From the starting node we visit all eight neighboring nodes and mark them all as visited; this core process is repeated for each newly visited node, recursively. Each thread tracks the nodes visited. When jumping to neighboring nodes, nodes that have already been visited nodes are skipped (the recursion stops); otherwise, the process continues until we reach the ending node, where the recursion ends and the full path followed is returned as a node array. Sometimes the end node is never reached, in which case the path finding fails. We usually end up finding multiple paths between the two nodes, in which case we take the one with the least number of nodes.

There are many standard solutions available for path finding based on 2D arrays, so we'll skip reinventing that wheel. Let's use this AS3 solution (I recommend you check out this great explanatory demo)).

The solution returns an array with points forming the path from the starting point to the end point:

 1 path = PathFinder.go(start x, start y, end x, end y, levelData); 

### Path Following

Once we have the path as a node array, we need to make the character follow it.

Say we want to make the character walk to a tile that we click on. We first need to look for a path between the node that the character currently occupies and the node where we clicked. If a successful path is found, then we need to move the character to the first node in the node array by setting is as the destination. Once we get to the destination node, we check where there are any more nodes in the node array and, if so, set the next node as destination - and so on until we reach the final node.

We will also change the direction of the player based on the current node and new destination node each time we reach a node. Between nodes, we just walk in the required direction until we reach the destination node. This is a very simple AI.

Check out this working example:

Here's the full source:

 1 import flash.display.Sprite;  2 import com.csharks.juwalbose.IsoHelper;  3 import flash.display.MovieClip;  4 import flash.geom.Point;  5 import flash.filters.GlowFilter;  6 import flash.events.Event;  7 import com.senocular.utils.KeyObject;  8 import flash.ui.Keyboard;  9 import flash.display.Bitmap;  10 import flash.display.BitmapData;  11 import flash.geom.Matrix;  12 import flash.geom.Rectangle;  13 import flash.events.MouseEvent;  14 15 import libs.PathFinder.*;  16 17 var levelData=[[1,1,1,1,1,1,1,1],  18 [1,0,0,0,0,2,0,1],  19 [1,0,0,1,0,0,0,1],  20 [1,1,0,0,0,0,0,1],  21 [1,0,0,0,0,1,1,1],  22 [1,0,0,1,0,0,0,1],  23 [1,0,0,1,0,0,0,1],  24 [1,1,1,1,1,1,1,1]];  25 26 var tileWidth:uint = 50;  27 var borderOffsetY:uint = 70;  28 var borderOffsetX:uint = 275;  29 30 var facing:String = "south";  31 var currentFacing:String = "south";  32 var hero:MovieClip=new herotile();  33 hero.clip.gotoAndStop(facing);  34 var heroPointer:Sprite;  35 var heroHalfSize:uint=20;  36 37 //the tiles  38 var grassTile:MovieClip=new TileMc();  39 grassTile.gotoAndStop(1);  40 var wallTile:MovieClip=new TileMc();  41 wallTile.gotoAndStop(2);  42 43 //the canvas  44 var bg:Bitmap = new Bitmap(new BitmapData(650,450));  45 addChild(bg);  46 var rect:Rectangle=bg.bitmapData.rect;  47 48 //to handle depth  49 var overlayContainer:Sprite=new Sprite();  50 addChild(overlayContainer);  51 52 //to handle direction movement  53 var dX:Number = 0;  54 var dY:Number = 0;  55 var idle:Boolean = true;  56 var speed:uint = 5;  57 var heroCartPos:Point=new Point();  58 var heroTile:Point=new Point();  59 60 var path:Array=new Array();  61 var destination:Point=new Point();  62 var stepsTillTurn:uint=5;  63 var stepsTaken:uint;  64 65 var glowFilter:GlowFilter=new GlowFilter(0x00ffff);  66 67 //add items to start level, add game loop  68 function createLevel()  69 {  70  var tileType:uint;  71  for (var i:uint=0; idestination.x){  190  dX = -1;  191  }else {  192  dX=0;  193  }  194  if(heroTile.ydestination.y){  197  dY = -1;  198  }else {  199  dY=0;  200  }  201  if(heroTile.x==destination.x){//top or bottom  202  dX=0;  203  }else if(heroTile.y==destination.y){//left or right  204  dY=0;  205  }  206   207  if (dX == 1)  208  {  209   210  if (dY == 0)  211  {  212  facing = "east";  213  }  214  else if (dY==1)  215  {  216  facing = "southeast";  217  dX = dY=0.5;  218  }  219  else  220  {  221  facing = "northeast";  222  dX=0.5;  223  dY=-0.5;  224  }  225  }  226  else if (dX==-1)  227  {  228   229  if (dY == 0)  230  {  231  facing = "west";  232  }  233  else if (dY==1)  234  {  235  facing = "southwest";  236  dY=0.5;  237  dX=-0.5;  238  }  239  else  240  {  241  facing = "northwest";  242  dX = dY=-0.5;  243  }  244  }  245  else  246  {  247   248  if (dY == 0)  249  {  250  facing=currentFacing;  251  }  252  else if (dY==1)  253  {  254  facing = "south";  255  }  256  else  257  {  258  facing = "north";  259  }  260  }  261  }  262   263   264   265 }  266 //sort depth & draw to canvas  267 function depthSort()  268 {  269  bg.bitmapData.lock();  270  bg.bitmapData.fillRect(rect,0xffffff);  271  var tileType:uint;  272  var mat:Matrix=new Matrix();  273  var pos:Point=new Point();  274  for (var i:uint=0; ilevelData.length-1||clickPt.x>levelData[0].length-1){  312  //trace("invalid");  313  //we have clicked outside  314  return;  315  }  316  if(levelData[clickPt.y][clickPt.x]==1){  317  //trace("wall");  318  //we clicked on a wall  319  return;  320  }  321  trace("find ",heroTile, clickPt);  322  destination=heroTile;  323  path= PathFinder.go(heroTile.x, heroTile.y, clickPt.x, clickPt.y, levelData);  324  path.reverse();  325  path.push(clickPt);  326  path.reverse();  327  paintPath();  328 }  329 function paintPath():void{//show hihglighted path in minimap  330  var sp:Sprite;  331  for (var i:uint=0; i

You may have noticed that I removed the collision check logic; it's no longer needed as we cannot manually move our character using the keyboard. However, we do need to filter out valid click points by determining whether we've clicked within the walkable area, rather than a wall tile or other non-walkable tile.

Another interesting point for coding the AI: we do not want the character to turn to face the next tile in the node array as soon as he has arrived in the current one, as such an immediate turn results in our character walking on the borders of tiles. Instead, we should wait until the character is a few steps inside the tile before we look for the next destination. It is also better to manually place the hero in the middle of the current tile just before we turn, to make it all feel perfect.

Also, if you explore the above demo, you may notice that our draw logic gets disrupted when the hero is moving diagonally close to a wall tile. This is an extreme case where, for one frame, our hero seems to be inside the wall tile. This happens because we have disabled the collision check. One workaround is to use a pathfinding algorithm that ignores the diagonal solutions. (Almost all path finding algorithms have options to enable or disable diagonal walk solutions.)

## 4. Projectiles

A projectile is something that moves in a particular direction with a particular speed, like a bullet, a magic spell, a ball, and so on.

Everything about the projectile is same as the hero character, apart from the height: rather than rolling along the ground, projectiles often float above it at a certain height. A bullet will travel above the waist level of the character, and even a ball may need to bounce around.

One interesting thing to note is that isometric height is the same as height in a 2D side view. There are no complicated conversions involved. If a ball is 10 pixels above ground in Cartesian coordinates, it is 10 pixels above the ground in isometric coordinates. (In our case, the relevant axis is the y-axis.)

Let's try to implement a ball bouncing around in our walled grassland. We'll ignore damping effects (and so make the bouncing continue endlessly), and for a touch of realism we'll add a shadow to the ball. We move the shadow just like we move the hero character (i.e. without using a height value), but for the ball we must add the height value to the isometric Y value. The height value will change from frame to frame depending on the gravity, and once the ball hits the ground we'll flip the current velocity along the y-axis.

Before we tackle bouncing in an isometric system, we'll see how we can implement it in a 2D Cartesian system. Let us represent the height of the ball by a variable zValue. Imagine that, to begin with, the ball is ten pixels high, so zValue = 10. We'll use two more variables: incrementValue, which starts at 0, and gravity, which has a value of 1.

Each frame, we add incrementValue to zValue, and subtract gravity from incrementValue. When zValue reaches 0, it means the ball has reached the ground; at this point, we flip the sign of incrementValue by multiplying it by -1 turning it into a positive number. This means that the ball will move upwards from the next frame, thus bouncing.

Here's how that looks in code:

 1 zValue=10;  2 gravity=1;  3 incrementValue=0;  4 5 gameLoop(){  6  incrementValue-=gravity;  7  zValue-=incrementValue;  8  if(zValue<=0){  9  zValue=0;  10  incrementValue*=-1;  11  }  12 } 

We're actually going to use a slightly modified version of that:

 1 zValue=10;  2 gravity=1;  3 incrementValue=0;  4 incrementReset=-12  5 6 gameLoop(){  7  incrementValue-=gravity;  8  zValue-=incrementValue;  9  if(zValue<=0){  10  zValue=0;  11  incrementValue=incrementReset;  12  }  13 } 

This removes the damping effect, and lets the ball bounce forever.

Applying this to our ball, we get the below demo:

Here is the full AS3 code:

 1 import flash.display.Sprite;  2 import com.csharks.juwalbose.IsoHelper;  3 import flash.display.MovieClip;  4 import flash.geom.Point;  5 import flash.events.Event;  6 import flash.display.Bitmap;  7 import flash.display.BitmapData;  8 import flash.geom.Matrix;  9 import flash.geom.Rectangle;  10 import com.senocular.utils.KeyObject;  11 12 var levelData=[[1,1,1,1,1,1],  13 [1,0,0,0,0,1],  14 [1,0,0,0,0,1],  15 [1,0,0,2,0,1],  16 [1,0,0,0,0,1],  17 [1,1,1,1,1,1]];  18 19 var tileWidth:uint = 50;  20 var borderOffsetY:uint = 70;  21 var borderOffsetX:uint = 275;  22 23 var key:KeyObject = new KeyObject(stage);  24 25 var facing:String = "south";  26 var currentFacing:String = "south";  27 var heroHalfSize:uint = 20;  28 29 //the tiles  30 var grassTile:MovieClip=new TileMc();  31 grassTile.gotoAndStop(1);  32 var wallTile:MovieClip=new TileMc();  33 wallTile.gotoAndStop(2);  34 var pickupTile:MovieClip=new TileMc();  35 pickupTile.gotoAndStop(3);  36 var ball:Sprite=new Ball();  37 var shadow_S:Sprite=new Shadow();  38 39 //the canvas  40 var bg:Bitmap = new Bitmap(new BitmapData(650,450));  41 addChild(bg);  42 var rect:Rectangle = bg.bitmapData.rect;  43 44 //to handle depth  45 var heroPointer:Sprite;  46 var overlayContainer:Sprite=new Sprite();  47 addChild(overlayContainer);  48 49 //to handle direction movement  50 var dX:Number = 0;  51 var dY:Number = 0;  52 var speed:uint = 5;  53 var ballCartPos:Point=new Point();  54 var ballTile:Point=new Point();  55 56 var zValue:int = 50;  57 var gravity:int = -1;  58 var incrementValue:Number = 0;  59 60 //add items to start level, add game loop  61 function createLevel()  62 {  63  var tileType:uint;  64  for (var i:uint=0; i

Do understand that the role played by the shadow is a very important one which adds to the realism of this illusion. In the above example, I have added half the ball's height to the ball's y-position, so that it bounces at the right position with respect to the shadow.

Also, note that we're now using the two screen coordinates (x and y) to represent three dimensions in isometric coordinates - the y-axis in screen coordinates is also the z-axis in isometric coordinates. This can be confusing!

## 5. Isometric Scrolling

When the level area is much larger than the visible screen area, we will need to make it scroll.

The visible screen area can be considered as a smaller rectangle within the larger rectangle of the complete level area. Scrolling is, essentially, just moving the inner rectangle inside the larger one:

Usually, when such scrolling happens, the position of the player remains the same with respect to the screen rectangle, commonly at the screen center. All we need, to implement scrolling, is to track the corner point of the inner rectangle:

This corner point, which is in Cartesian coordinates (in the image we can only show the isometric values), will fall within a tile in the level data. For scrolling, we increment the x- and y-position of the corner point in Cartesian coordinates. Now we can convert this point to isometric coordinates and use it to draw the screen.

The newly converted values, in isometric space, need to be the corner of our screen too, which means they are the new (0, 0). So, while parsing and drawing the level data, we subtract this value from the isometric position of each tile, and only draw it if the tile's new position falls within the screen. We can express this in steps as so:

• Update Cartesian corner point's x- and y-coordinates.
• Convert this to isometric space.
• Subtract this value from the isometric draw position of each tile.
• Draw the tile only if the new isometric draw position falls within the screen.

Check out this example (use arrows to scroll):

Here's the full AS3 source code:

 1 import flash.display.Sprite;  2 import com.csharks.juwalbose.IsoHelper;  3 import flash.display.MovieClip;  4 import flash.geom.Point;  5 import flash.filters.GlowFilter;  6 import flash.events.Event;  7 import com.senocular.utils.KeyObject;  8 import flash.ui.Keyboard;  9 import flash.display.Bitmap;  10 import flash.display.BitmapData;  11 import flash.geom.Matrix;  12 import flash.geom.Rectangle;  13 14 var levelData=[[1,1,1,1,1,1,1,1,1,1,1,1],  15 [1,0,0,0,0,0,0,0,0,1,0,1],  16 [1,0,0,0,0,0,0,0,0,1,0,1],  17 [1,0,0,1,0,0,0,0,0,0,0,1],  18 [1,0,0,1,2,0,0,0,0,0,0,1],  19 [1,0,0,1,0,0,0,0,0,0,0,1],  20 [1,0,0,0,0,0,0,0,0,1,0,1],  21 [1,0,0,0,0,0,0,0,0,0,0,1],  22 [1,0,0,0,1,1,1,0,0,0,0,1],  23 [1,0,0,0,0,0,0,0,0,0,0,1],  24 [1,0,0,0,0,0,0,0,1,1,0,1],  25 [1,1,0,0,0,0,0,0,0,0,0,1],  26 [1,1,1,1,1,1,1,1,1,1,1,1]];  27 28 var tileWidth:uint = 50;  29 var borderOffsetY:uint = 70;  30 var borderOffsetX:uint = 275;  31 32 var facing:String = "south";  33 var currentFacing:String = "south";  34 var hero:MovieClip=new herotile();  35 hero.clip.gotoAndStop(facing);  36 var heroPointer:Sprite;  37 var key:KeyObject = new KeyObject(stage);//Senocular KeyObject Class  38 var heroHalfSize:uint=20;  39 40 //the tiles  41 var grassTile:MovieClip=new TileMc();  42 grassTile.gotoAndStop(1);  43 var wallTile:MovieClip=new TileMc();  44 wallTile.gotoAndStop(2);  45 46 //the canvas  47 var bg:Bitmap = new Bitmap(new BitmapData(650,450));  48 addChild(bg);  49 var rect:Rectangle=bg.bitmapData.rect;  50 51 //to handle depth  52 var overlayContainer:Sprite=new Sprite();  53 addChild(overlayContainer);  54 55 //to handle direction movement  56 var dX:Number = 0;  57 var dY:Number = 0;  58 var idle:Boolean = true;  59 var speed:uint = 6;  60 var heroCartPos:Point=new Point();  61 var heroTile:Point=new Point();  62 63 var cornerPoint:Point=new Point();  64 65 //add items to start level, add game loop  66 function createLevel()  67 {  68  var tileType:uint;  69  for (var i:uint=0; i

Please note that the corner point is incremented in the opposite direction to the hero's position update as he moves. This makes sure that hero stays where he is with respect to the screen:

 1 heroCartPos.x += speed * dX;  2 heroCartPos.y += speed * dY;  3 cornerPoint.x -= speed * dX;  4 cornerPoint.y -= speed * dY; 

The draw logic only changes in two lines, where we determine the Cartesian coordinates of each tile. We just pass the corner point to the original point which actually combines points 1, 2 and 3 above:

 1 pos.x = j * tileWidth+cornerPoint.x;  2 pos.y = i * tileWidth+cornerPoint.y; 

A couple of notes:

• While scrolling, we may need to draw additional tiles at the screen borders, or else we may see tiles disappearing and appearing at the screen extremes.
• If you have tiles that take up more than one space, then you will need to draw more tiles at the borders. For example, if the largest tile in the whole set measures X by Y, then you will need to draw X more tiles to the left and right and Y more tiles to the top and bottom. This makes sure that the corners of the bigger tile will still be visible when scrolling in or out of the screen.
• We still need to make sure that we don't have blank areas in the screen while we are drawing near the borders of the level.
• The level should only scroll until the most extreme tile gets drawn at the corresponding screen extreme - after this, the character should continue moving in screen space without the level scrolling. For this, we will need to track all four corners of the inner screen rectangle, and throttle the scrolling and player movement logic accordingly. Are you up for the challenge to try implementing that for yourself?

## Conclusion

This series is particularly aimed at beginners trying to explore isometric game worlds. Many of the concepts explained have alternate approaches which are a bit more complicated and I have purposefully chosen the easiest ones. They may not fulfill all scenarios which you may encounter, but the knowledge gained can be used to build upon these concepts to create much complicated solutions.