Vanilla JS Tutorials

JsIso Tutorials

RSS feed

02 - Creating a simple isometric Tile Engine

This tutorial covers switching from a tile map to an isometric tile map.

In this sample we make use of all the previous tutorial logic however we expand on it for images and isometric shaped tiles. We also tidy up our code by adding scope, getting rid of the onLoad <body> attribute and switch out fillRect for drawImage.

Isometric tiles are just as easy to achieve as a standard tile map. With a bit of shape alteration and minor positioning adjustment, you can change your flat 2D game concept into a user grasping “2D.5″ isometric engine, capable of graphical destruction and full user awe.

But where do you begin in creating such an experience?

To the drawing board!

So you start off with a 52×52 pixel square which represents part of the fields of Felderon, which gives your hero enlightenment and a sanithy increase while walked on, at the risk of a slight game ending seizure. Such a tile may look like this:



However this hero isn’t walking on any standard field of Felderon, he is standing on an isometric field of Felderon. So how do we change our tile to meet these requirements?

Basically we rotate it and half the height so our tile is now 52px wide and 26px tall. For drawing purposes it's best to keep both dimension values as even numbers. Instantly our field tile looks like it has some slight depth to it with the grass blades of pixel beauty growing out. For image format, pngs are used allowing alpha and the image overlap. This tile when placed into our soon to be coded engine would look like this:



So we have our tile and now we need to alter our previous example on creating a tile engine to match the isometric view.
Although our Felderon field tile is of a graphically inspiring status, we will need some other tiles to go along with it. Feel free to use this tile which represents the sea of despair, that when drank, generously reduces charisma with a 30% chance of sporadic teeth implosion.



Like the previous tutorial we have a map with each number representing a tile. We also have an array which will be used to store the loaded tile graphics.


var map = [
  [1,0,0,0],
  [1,0,0,1],
  [0,0,1,1],
  [1,1,1,1]
];

var tileGraphics = [];

Loading the desired images can be achieved with the following loop contained in our loadImg() function.

In this simple function for loading multiple tile images, tileGraphicsToLoad will hold all our tile images to be loaded, and our global variable tileImages array will contain the graphics for accessing.


function loadImg() {
    
  var tileGraphicsToLoad = ["/tutorials/images/water.png","/tutorials/images/land.png"],
      tileGraphicsLoaded = 0;

  for (var i = 0; i < tileGraphicsToLoad.length; i++) {
  
    tileGraphics[i] = new Image();
    tileGraphics[i].src = tileGraphicsToLoad[i];
    
    tileGraphics[i].onload = function() {
      tileGraphicsLoaded++;
      if (tileGraphicsLoaded === tileGraphicsToLoad.length) {
          drawMap();
      }
    }
  
  }

}

Pretty simple code really, it runs through the length of your tileGraphicsToLoad and as each image is successfully loaded it increments tileGraphicsLoaded by 1.

Next we adjust our drawMap to be specific for an isometric view and to make use of our loaded images.


function drawMap() {
    
  var ctx = document.getElementById('main').getContext('2d');

  var tileH = 25;
  var tileW = 52;
  
  var mapX = 76;
  var mapY = 52;

  var drawTile;

  for (var i = 0; i < map.length; i++) {
    for (var j = 0; j < map[i].length; j++) {
      drawTile = map[i][j];
      ctx.drawImage(tileGraphics[drawTile], (i - j) * tileH + mapX, (i + j) * tileH / 2 + mapY);
    }
  }
}

Exactly the same as the first tutorial we loop through the array lengths however the tile positioning code is slightly different.
Taking the changes in size and the fact that we are now positioning for an isometric view into account we calculate the position accordingly.
drawTile is set as the value of the current map array tile, and used for getting the matching tile image from the tileGraphics array.

Next we add an init function that will be called once the page has finished loading. This function removes the Event Listner and calls for the engine tile images to be loaded via loadImg();.


function init() {
    isometric.removeEventListener('load', init);
    loadImg();
};

And behold the fields of Felderon with the sea of despair flowing powerfully through it. That really is all there is to it as seen in the following Canvas element.


// Create the isometric scope.
// Tutorial Note: Wrapping all our code within a function this way means all 
// our variables and functions don't become globals. This prevents conflicts if you're using other scripts.
(function(isometric) {

  // Two Dimensional Array storing our isometric map layout. Each number represents a tile.
  var map = [
    [1,0,0,0],
    [1,0,0,1],
    [0,0,1,1],
    [1,1,1,1]
  ];

  var tileGraphics = [];

  function loadImg() {
      
    // Images to be loaded and used.
    // Tutorial Note: As water is loaded first it will be represented by a 0 on the map and land will be a 1.
    var tileGraphicsToLoad = ["/tutorials/images/water.png","/tutorials/images/land.png"],
    tileGraphicsLoaded = 0;

    for (var i = 0; i < tileGraphicsToLoad.length; i++) {
      tileGraphics[i] = new Image();
      tileGraphics[i].src = tileGraphicsToLoad[i];
      tileGraphics[i].onload = function() {
        // Once the image is loaded increment the loaded graphics count and check if all images are ready.
        tileGraphicsLoaded++;
        if (tileGraphicsLoaded === tileGraphicsToLoad.length) {
            drawMap();
        }
      }
    }

  }


  function drawMap() {
      
    // create the canvas context
    var ctx = document.getElementById('main').getContext('2d');

    // Set as your tile pixel sizes, alter if you are using larger tiles.
    var tileH = 25;
    var tileW = 52;
    
    // mapX and mapY are offsets to make sure we can position the map as we want.
    var mapX = 76;
    var mapY = 52;

    var drawTile;

    // loop through our map and draw out the image represented by the number.
    for (var i = 0; i < map.length; i++) {
      for (var j = 0; j < map[i].length; j++) {
        drawTile = map[i][j];
        // Draw the represented image number, at the desired X & Y coordinates followed by the graphic width and height.
        ctx.drawImage(tileGraphics[drawTile], (i - j) * tileH + mapX, (i + j) * tileH / 2 + mapY);
      }
    }
  }

  function init() {
    // Remove Event Listener and load images.
    isometric.removeEventListener('load', init);
    loadImg();
  };

  // Add Event Listener to dectect when page has fully loaded.
  isometric.addEventListener('load', init, false);

})(this);


Tutorial Outcome (View & Run Source)

3 comments

  1. Thank you so much for these tutorials. I am currently working on a isometric html 5 game and these tutorials are really helping me to understand the basics of how the isometric view is done. Thanks again!

    — Kenny Fri, 30 Jan 2015

  2. You are more than welcome Kenny, I'm glad to hear they help a little! Thanks :).

    — Iain Sun, 1 Mar 2015

  3. Thank you for these well made tutorials. Thanks to them I can actually understand how this stuff works.

    — Alan Fri, 11 Dec 2015

Please insert the result of the arithmetical operation from the following image:

Please insert the result of the arithmetical operation from this image. =