const ctx = document.querySelector('canvas').getContext('2d');
const mapImage = new Image();
mapImage.onload = start;
mapImage.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Map_of_the_Battle_of_the_Somme%2C_1916.svg/1568px-Map_of_the_Battle_of_the_Somme%2C_1916.svg.png';
// wedge pointing left
const playerPath = new Path2D();
playerPath.lineTo(15, 0);
playerPath.lineTo(-15, 10);
playerPath.lineTo(-15, -10);
playerPath.closePath();
function start() {
const keys = {};
const player = {
x: mapImage.width / 2,
y: mapImage.height / 2,
turnVel: Math.PI / 2, // 1/4 turn per second
dir: -Math.PI / 2,
vel: 10, // 10 units per second
};
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
resizeCanvasToDisplaySize(ctx.canvas);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
let turnDir = 0;
if (keys[37]) {
turnDir = -1;
} else if (keys[39]) {
turnDir = 1;
}
player.dir += player.turnVel * turnDir * deltaTime;
player.x += Math.cos(player.dir) * player.vel * deltaTime;
player.y += Math.sin(player.dir) * player.vel * deltaTime;
ctx.save();
{
// move origin to bottom center of canvas
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height * 0.9);
ctx.save();
{
// rotate origin opposite of player
// the -Math.PI/2 is because the code above adding the direction
// to the velocity has dir = 0 meaning going east.
// We want to face up the map. Up is -Math.PI/2
// so when going up we need the rotation here to be 0. (not rotated)
ctx.rotate(-player.dir - Math.PI/ 2);
// move origin based on player's position
ctx.translate(-player.x, -player.y);
ctx.drawImage(mapImage, 0, 0);
// draw enemies or targets here. They are in map coordinates
// example
ctx.fillStyle = 'red';
for (let y = 0; y < mapImage.height; y += 200) {
for (let x = 0; x < mapImage.width; x += 200) {
ctx.save();
{
ctx.translate(x, y);
ctx.rotate(Math.atan2(player.y - y, player.x - x));
ctx.fill(playerPath);
}
ctx.restore();
}
}
}
ctx.restore(); // origin back at bottom center of canvas
// draw Player
ctx.save();
{
ctx.rotate(-Math.PI / 2); // because the player's shape points left
ctx.fillStyle = 'black';
ctx.fill(playerPath);
}
ctx.restore();
}
ctx.restore(); // origin back at top left corner of canvas
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('keydown', (e) => {
keys[e.keyCode] = true;
});
window.addEventListener('keyup', (e) => {
keys[e.keyCode] = false;
});
function resizeCanvasToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
canvas.width = width;
canvas.height = height;
}
return needResize;
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
#info {
position: absolute;
left: 1em;
top: 1em;
color: white;
background: rgba(0, 0, 0, 0.5);
padding: 0.5em;
}
<canvas></canvas>
<div id="info">use cursor left/right</div>