Isometric Maze on HTML Canvas

Tom Cantwell
4 min readOct 29, 2020

--

Last week I made an isometric maze using CSS elements. This week I’m showing how I did the same using HTML Canvas instead. This has the advantage of being a lot easier to use as a background image.

I’ve gone over the code for a randomly generated maze before, so I’m moving on to the isometric projection. For now, just know that we have a 2D Array called grid that contains all the cells of the maze.

I’m not a fan of the default canvas, since it always looks a bit fuzzy to me. You can sharpen the canvas using the following code. In this case, it’s four times sharper:

//---------Canvas as Background-----------//let bg = document.querySelector(".bg"),
bgCtx = bg.getContext("2d"),
//sharpen * 4
bgw = (bg.width = window.innerWidth * 4),
bgh = (bg.height = window.innerHeight * 4);
bg.style.width = window.innerWidth + "px";
bg.style.height = window.innerHeight + "px";

Next, we need to establish a few key variables such as the origin point of the maze (since using 0, 0 no longer makes sense for an isometric view), the size of the cells, and the perspective to draw at. This isn’t a true perspective, really. More of a stretch. But it works for medium values.

let xO = bgw * 0.5;
let yO = bgh * 0.1;
let cellSize = 50;
let perspective = 0.7;

Drawing the maze

We need a function to draw one cube, which will be repeated for each wall of the maze.

function shadeColor(color, percent) {
color = color.substr(1);
var num = parseInt(color, 16),
amt = Math.round(2.55 * percent),
R = (num >> 16) + amt,
G = ((num >> 8) & 0x00ff) + amt,
B = (num & 0x0000ff) + amt;
return (
"#" +
(
0x1000000 +
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
(B < 255 ? (B < 1 ? 0 : B) : 255)
)
.toString(16)
.slice(1)
);
}
function drawCube(x, y, w, h, color, per) {
//left
bgCtx.beginPath();
bgCtx.moveTo(x, y);
bgCtx.lineTo(x - w, y - w * per);
bgCtx.lineTo(x - w, y - h - w * per);
bgCtx.lineTo(x, y - h * 1);
bgCtx.closePath();
bgCtx.fillStyle = shadeColor(color, 10);
bgCtx.strokeStyle = shadeColor(color, 10);
bgCtx.stroke();
bgCtx.fill();
//right
bgCtx.beginPath();
bgCtx.moveTo(x, y);
bgCtx.lineTo(x + w, y - w * per);
bgCtx.lineTo(x + w, y - h - w * per);
bgCtx.lineTo(x, y - h * 1);
bgCtx.closePath();
bgCtx.fillStyle = shadeColor(color, -10);
bgCtx.strokeStyle = shadeColor(color, -10);
bgCtx.stroke();
bgCtx.fill();
//top
bgCtx.beginPath();
bgCtx.moveTo(x, y - h);
bgCtx.lineTo(x - w, y - h - w * per);
bgCtx.lineTo(x - w + w, y - h - (w * per + w * per));
bgCtx.lineTo(x + w, y - h - w * per);
bgCtx.closePath();
bgCtx.fillStyle = shadeColor(color, 20);
bgCtx.strokeStyle = shadeColor(color, 20);
bgCtx.stroke();
bgCtx.fill();
}

Now draw the whole maze, calling drawCube for each cell that’s a wall. The formula for calculating xPos and yPos is important to use rather than using an incrementing method, because if you want to interact with the maze in any way, you’ll need to inverse those equations.

drawMaze();
function drawMaze() {
for (let y = 0; y < grid.length; y++) {
for (let x = 0; x < grid[y].length; x++) {
let xPos = xO + cellSize * (x - y);
let yPos = yO + perspective * (cellSize * (x + y));
if (grid[y][x].color === "transparent") {
continue;
}
// draw the cube
drawCube(
xPos,
yPos,
cellSize,
cellSize,
grid[y][x].color,
perspective
);
}
}
}

Interacting with the maze

Add an event listener to the canvas, and after scaling the offset coordinates to match the amount we sharpened the canvas by, use the inverse of the equations from before to get coordinates we can match to the maze grid.

//Canvas Events
bg.addEventListener("click", handleCanvasClick);
function handleCanvasClick(e) {
//Get coordinates on canvas, offset by origin
let xCanvas = e.offsetX * 4 - xO;
let yCanvas = e.offsetY * 4 - yO;
//Get inverse of isometric transformation
let invX = 0.7 + (yCanvas / perspective + xCanvas) / (2 * cellSize);
let invY = 0.7 + (yCanvas / perspective - xCanvas) / (2 * cellSize);
let x = Math.ceil(invX);
let y = Math.ceil(invY);
if (grid[y][x].color === "transparent") {
grid[y][x].color = "#b94f4f";
bgCtx.clearRect(0, 0, bgw, bgh);
drawMaze();
}
}

--

--

No responses yet