Isometric View

Tilt your world 45 degrees and you get the iconic look behind PokΓ©mon, Age of Empires, and Diablo. Isometric view makes a flat tile grid feel like a real 3D space. Best of all, it’s just two lines of math on top of everything you’ve already built:

Loading editor…

Walk north of the pillar - it covers you. Walk south - you cover it. All game logic (movement, collision) runs in the flat world grid. Only the final render step converts to the diamond view.

THE ISOMETRIC TRICK πŸ”·

Isometric view is a two-step visual transformation applied to a normal square tile:

  1. Rotate 45 degrees - the square becomes a diamond
  2. Squash height by half - the diamond is compressed to a 2:1 width-to-height ratio
Normal tile:     Rotated 45Β°:     Squashed (2:1):
β”Œβ”€β”€β”€β”€β”€β”€β”             /\               /\
β”‚      β”‚            /  \            /    \
β”‚      β”‚            \  /            \    /
β””β”€β”€β”€β”€β”€β”€β”˜             \/               \/
  30Γ—30              42Γ—42           60Γ—30

The result is the classic isometric tile used in countless games. Every grid square maps to one of these diamonds on screen.

THE TRANSFORMATION πŸ“

Two lines convert any world coordinate to its isometric screen position:

const OFFSET_X = 150; // horizontal center of the canvas
const OFFSET_Y = 20;  // vertical margin from top

function isoToScreen(worldX, worldY) {
    return {
        x: (worldX - worldY) + OFFSET_X,
        y: (worldX + worldY) / 2 + OFFSET_Y
    };
}

Why does this work? Moving right in the world (increasing worldX) shifts the screen position right and down - that’s the southeast diamond direction. Moving down in the world (increasing worldY) shifts the screen position left and down - southwest. The diamond grid emerges naturally from the subtraction and addition of those two axes.

For tile at (col, row):

const worldX = col * TILE_SIZE;
const worldY = row * TILE_SIZE;
const screen = isoToScreen(worldX, worldY);

TWO COORDINATE SYSTEMS

The most important architectural rule in isometric games: keep all game logic in world space.

Collision detection, movement, pathfinding, enemy AI - all of it uses the flat worldX/worldY grid. The isometric transform only runs at the very end, when positioning sprites for rendering:

function gameLoop() {
    // All logic runs in flat world space - unchanged from every previous tutorial
    resolveCollisions();  // uses worldX, worldY, map[row][col]
    handleInput();        // modifies worldX, worldY

    // Only the final render step converts to isometric screen coords
    const screen = isoToScreen(player.worldX, player.worldY);
    player.sprite.x = screen.x - halfSpriteWidth;
    player.sprite.y = screen.y - halfSpriteHeight;
}

Your isSolid() function doesn’t change at all - it still divides by TILE_SIZE to find which grid cell the player is in. The diamond view is purely a visual layer on top.

DRAWING DIAMOND TILES

Graphics.poly() draws any polygon from a flat array of [x1, y1, x2, y2, ...] vertices. A flat ground diamond:

// TILE_SIZE = 30: diamond is 60px wide (2 Γ— TILE_SIZE) and 30px tall
function makeGroundTile(color) {
    return new PIXI.Graphics()
        .poly([30, 0,   // top vertex
               60, 15,  // right vertex
               30, 30,  // bottom vertex
               0,  15]) // left vertex
        .fill(color);
}

// Position so diamond center aligns with the screen coords
tile.x = screen.x - TILE_SIZE;      // screen.x - 30 = left edge of bounding box
tile.y = screen.y - TILE_SIZE / 2;  // screen.y - 15 = top edge of bounding box

For wall tiles that look like 3D boxes, draw three faces with different shades:

function makeWallTile() {
    const WH = 20; // visual height of the wall in pixels
    const g = new PIXI.Graphics();

    // Top face: diamond shifted up by WH pixels
    g.poly([30, -WH,    60, 15-WH, 30, 30-WH, 0, 15-WH]).fill(0xA07840);
    // Left face: parallelogram going down-left
    g.poly([0,  15-WH,  30, 30-WH, 30, 30,    0, 15   ]).fill(0x5C4020);
    // Right face: parallelogram going down-right
    g.poly([30, 30-WH,  60, 15-WH, 60, 15,    30, 30  ]).fill(0x7A5528);

    return g;
}

Three shades of the same color (light top, dark left, medium right) create the 3D illusion.

DEPTH SORTING ↕️

From tutorial 19 you know the foot-point rule: objects further south render in front. In isometric, the sort key is worldX + worldY - objects further along both world axes are closer to the viewer:

app.stage.sortableChildren = true;

// When building tiles - set once
tile.zIndex = worldX + worldY;   // = (col + row) * TILE_SIZE

// In the game loop - update player each frame
player.sprite.zIndex = player.worldX + player.worldY + TILE_SIZE / 2;
// The + TILE_SIZE/2 keeps the player in front of floor tiles at the same depth

worldX + worldY is proportional to screenY (since screenY = (worldX + worldY) / 2 + offset) - so this is the same Y-sort from tutorial 19, just expressed in world coordinates.

What you’ve built:

  • βœ… Two-line isoToScreen() transform applied at render time only
  • βœ… World-space game logic: collision and movement code unchanged
  • βœ… Diamond tiles drawn with Graphics.poly()
  • βœ… 3-face wall boxes with light/dark/medium shading
  • βœ… Depth sorting using worldX + worldY

Next up: The mouse clicks in screen space. How do you find which tile it actually hit? Next: Isometric Mouse