Sebastian Walker
Sebastian Walker

Reputation: 187

Isometric tilemap using canvas (with click detection)

I am currently developing a game, which requires a map consisting of various tile images. I managed to make them display correctly (see second image) but I am now unsure of how to calculate the clicked tile from the mouse position.

Are there any existing libraries for this purpose?

Please also note, that the tile images aren't drawn perfectly "corner-facing-camera", they are slightly rotated clockwise.

enter image description hereenter image description here

Upvotes: 2

Views: 733

Answers (1)

Blindman67
Blindman67

Reputation: 54049

Isometric Transformations

Define a projection

Isometric display is the same as standard display, the only thing that has changed is the direction of the x and y axis. Normally the x axis is defined as (1,0) one unit across and zero down and the y axis is (0,1) zero units across and one down. For isometric (strictly speaking your image is a dimetric projection) you will have something like x axis (0.5,1) and y axis (-1,0.5)

The Matrix

From this you can create a rendering matrix with 6 values Two each for both axes and two for the origin, which I will ignore for now (the origin) and just use the 4 for the axis and assume that the origin is always at 0,0

var dimetricMatrix = [0.5,1.0,-1,0.5]; // x and y axis

Matrix transformation

From that you can get a point on the display that matches a given isometric coordinate. Lets say the blocks are 200 by 200 pixels and that you address each block by the block x and y. Thus the block in the bottom of your image is at x = 2 and y = 1 (the first top block is x = 0, y = 0)

Using the matrix we can get the pixel location of the block

var blockW = 200;
var blockH = 200;
var locX = 2;
var locY = 1;

function getLoc(x,y){
   var xx,yy; // intermediate results
   var m = dimetricMatrix; // short cut to make code readable
   x *= blockW; // scale up
   y *= blockH;
   // now move along the projection x axis 
   xx = x * m[0];
   yy = x * m[1];   
   // then add the distance along the y axis
   xx += y * m[2];
   yy += y * m[3];

   return {x : xx, y : yy};
}

Befoer I move on you can see that I have scaled the x and y by the block size. We can simplify the above code and include the scale 200,200 in the matrix

var xAxis = [0.5, 1.0];
var yAxis = [-1, 0.5];
var blockW = 200;
var blockH = 200;
// now create the matrix and scale the x and y axis
var dimetricMatrix = [
    xAxis[0] * blockW,
    xAxis[1] * blockW,
    yAxis[0] * blockH,
    yAxis[1] * blockH,

]; // x and y axis   

The matrix holds the scale in the x and y axis so that the two numbers for x axis tell us the direction and length of a transformed unit.

Simplify function

And redo the getLoc function for speed and efficiency

function transformPoint(point,matrix,result){
   if(result === undefined){
       result = {};
   }
   // now move along the projection x axis 
   result.x = point.x * matrix[0] + point.y * matrix[2];
   result.y = point.x * matrix[1] + point.y * matrix[3];
   return result;
}

So pass a point and get a transformed point back. The result argument allows you to pass an existing point and that saves having to allocate a new point if you are doing it often.

var point = {x : 2, y : 1};
var screen = transformPoint(point,dimetricMatrix);
// result is the screen location of the block

// next time
screen = transformPoint(point,dimetricMatrix,screen); // pass the screen obj
                                                      // to avoid those too
                                                      // GC hits that kill
                                                      // game frame rates

Inverting the Matrix

All that is handy but you need the reverse of what we just did. Luckily the way matrices work allows us to reverse the process by inverting the matrix.

function invertMatrix(matrix){
    var m = matrix; // shortcut to make code readable
    var rm = [0,0,0,0]; // resulting matrix
    // get the cross product of the x and y axis. It is the area of the rectangle made by the
    // two axis 
    var cross =  m[0] * m[3] - m[1] * m[2]; // I call it the cross but most will call 
                                                    // it the determinate (I think that cross 
                                                    // product is more suited to geometry while
                                                    // determinate is for maths geeks)
    rm[0] = m[3] / cross;   // invert both axis and unscale (if cross is 1 then nothing)
    rm[1] = -m[1] / cross;
    rm[2] = -m[2] / cross;
    rm[3] = m[0] / cross;    
    return rm;         
}

Now we can invert our matrix

var dimetricMatrixInv = invertMatrix(dimetricMatrix); // get the invers

And now that we have the inverse matrix we can use the transform function to convert from a screen location to a block location

var screen = {x : 100, y : 200};
var blockLoc = transformPoint(screen, dimetricMatrixInv );
// result is the location of the block

The Matrix for rendering

For a bit of magic the transformation matrix dimetricMatrix can also be used by the 2D canvas, but you need to add the origin.

var m = dimetricMatrix;
ctx.setTransform(m[0], m[1], m[2], m[3], 0, 0); // assume origin at 0,0

Now you can draw a box around the block with

ctx.strokeRect(2,1,1,1); // 3rd by 2nd block 1 by 1 block wide.

The origin

I have left out the origin in all the above, I will leave that up to you to find as there is a trillion pages online about matrices as all 2D and 3D rendering use them and getting a good deep knowledge of them is important if you wish to get into computer visualization.

Upvotes: 4

Related Questions