Saturday, June 16, 2018

javascript - Adapting tilemap algorithm to support isometric tilemap


I'm using Phaser to build an isometric game. The framework doesn't have support for isometric tilemaps yet, so I'm starting to write a PR for it to support.


What I currently have, loading an isometric tilemap on the current Phaser.Tilemap object, is this:



enter image description here


As you can see, the tiles are wrongly positioned because of the simple 2D tile positioning approach the framework currently uses.


The class that actually makes the parsing of the JSON map and converts it into a tilemap is Phaser.TilemapParser, specifically at line 185.


What I need is some help on where to start adapting this parser – or any other part of the code – in order for it to support isometric tilemaps.


I don't know exactly where to start extending this parser – or even writing a new parser just for isometric tilemaps. Also, I know the calculations are different for positioning isometric tiles, and I want to know too where that change should go here, as I didn't find that either.



Answer



You don't need to change the parser, just the renderer.
The tiles will be at the 'same' place, except the projection is different.
The good news is that the good context 2d can do isometric just by setting the right isometric transform.
Once you set it, just draw in a regular way (including drawImage), and all will be drawn in the isometric way !!! magic !!!



So what i suggest you do with phaser to draw the tiles :
-save context.
-set transform to an isometric one.
-draw tiles with regular phaser code.
-restore context.


This image was made just with setTransform + drawImage of random small tile bitmap on a context2D :


enter image description here


How does isometric coordinates work ?
The 'regular' coordinate system is the cartesian coordinates.
It needs a center O, and two unit perpendicular vectors Ux and Uy.

The fact that they are unit vectors means they have a length of 1. It allows such a system not to change distances after translation.
The fact that they are perpendicular allows such a system not to change distances after a rotation.
If you have x and y of a point, you get the points with


P(x,y) = x * Ux + y * Uy.  
with Ux = (1,0) and Uy = (0,1).

Now for isometric projection we just release one constraint : Ux and Uy do not have to be perpendicular. So distances will change after a rotation.
But since they are still unit vectors, distances are kept after a translation ( iso - metric == same - distances ).
Since Ux and Uy, are unit vectors, we can write them as


 Ux = (cos A1, sin A1) 

Uy = (cos A2, sin A2).

A1 and A2 will be the relative angle between Ux / Uy and the horizontal line :


enter image description here


By changing A1 and A2, you change the axes directions, and you can give the look that you want to your projection.
Obviously, if A1 == A2, both axes are the same and you'll see nothing.
A1 = PI / 6 and A2 = 9 * PI / 10 seems (subjectively) a good starting point for the angles.


to compute where is a (x, y) point, the formula is the same as cartesian : P = xUx + yUy.


To convert from cartesian to isometric, the transform matrix is :


  (in regular mathematical notation)

M = [ cosA1 sinA1 translateX
cosA2 sinA2 translateY
0 0 1 ] ;

or (in webgl column-by-column notation )
Mgl = [ [ cosA1 cosA2 0 ]
[ sinA1 sinA2 0 ]
[ translateX translateY 1 ] ]

below i also compute the reverse of the transform matrix to be able to get world coordinates from mouse coordinates.



Notice that i introduced a scale, and also that i allow to change the x aspect ratio if you want to have non-square tiles.


if you want to play with the fiddle it's here (move by pressing mouse).


(version corresponding to the code below) http://jsfiddle.net/gamealchemist/zF2w8/4/


(latest version : http://jsfiddle.net/gamealchemist/zF2w8/ )


//  Parameters
//

// center of the display on screen
var displayCenterX = 1*canvas.width / 3;
var displayCenterY = 2 * canvas.height / 3;


// angle of the x axis. Should be in [0, PI/2]
var angleX = Math.PI / 6;
// angle of the y axis. Should be in [PI/2, PI[
var angleY = 2.8;

// scale for the tiles
var scale = 120.0;
// relative scale for the x of the tile. use it to stretch tiles.
var relScaleX = 1;


// ----------------------------------------
// Transforms
// ----------------------------------------
var transfMatrix = [Math.cos(angleX), Math.sin(angleX),
Math.cos(angleY), Math.sin(angleY)];
var _norm = relScaleX + 1;
relScaleX /= _norm;
transfMatrix[0] *= scale * relScaleX;
transfMatrix[1] *= scale * relScaleX;

transfMatrix[2] *= scale / _norm;
transfMatrix[3] *= scale / _norm;
// matrix reverse
var determinant = transfMatrix[0] * transfMatrix[3] - transfMatrix[2] * transfMatrix[1];
var transfMatrixRev = [transfMatrix[3], -transfMatrix[1], -transfMatrix[2], transfMatrix[0]];
transfMatrixRev[0] /= determinant;
transfMatrixRev[1] /= determinant;
transfMatrixRev[2] /= determinant;
transfMatrixRev[3] /= determinant;


// use with :
c.setTransform(transfMatrix[0],transfMatrix[1],
transfMatrix[2],transfMatrix[3],
displayCenterX, displayCenterY);

.


 // regular 3x3 transform matrix in webGL format :
var mat3 = [ transfMatrix[0],transfMatrix[2], 0,
transfMatrix[1],transfMatrix[3], 0,
displayCenterX, displayCenterY, 1

] ;

( for the fun, the pseudo-3D version : http://jsfiddle.net/gamealchemist/zF2w8/5/ )


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...