var currentRenderState = getFocus; // current game state
const config = {
player: {
start: {x: 100, y:100},
speed: 2,
imageURL: "https://i.stack.imgur.com/C7qq2.png?s=64&g=1",
},
keys: { // link key code to game action
up: ["ArrowUp", "KeyW"],
down: ["ArrowDown", "KeyS"],
left: ["ArrowLeft", "KeyA"],
right: ["ArrowRight", "KeyD"],
},
touchableTime: 140, // in ms. Set to 0 or remove to deactivate
text: {
focus: "Click canvas to get focus",
loading: "Just a moment still loading media!",
instruct: "Use arrow keys or WASD to move",
}
};
requestAnimationFrame(mainLoop); // request first frame
const ctx = gameCanvas.getContext("2d");
const w = gameCanvas.width, h = gameCanvas.height;
const player = {
image: (()=> {
const img = new Image;
img.src = config.player.imageURL;
img.addEventListener("load", () => player.size = img.width, {once: true});
return img;
})(),
x: config.player.start.x,
y: config.player.start.y,
size: 0,
speed: config.player.speed,
direction: 0,
update() {
var oldX = this.x, oldY = this.y;
if (actions.left) { this.x -= this.speed }
if (actions.right) { this.x += this.speed }
if (actions.up) { this.y -= this.speed }
if (actions.down) { this.y += this.speed }
if (this.x < 0) { this.x = 0 }
else if (this.x > w - this.size) { this.x = w - this.size }
if (this.y < 0) { this.y = 0 }
else if (this.y > h - this.size) { this.y = h - this.size }
const mx = this.x - oldX, my = this.y - oldY;
if (mx !== 0 || my !== 0) { this.direction = Math.atan2(my, mx) }
},
draw(ctx) {
if (ctx) {
ctx.setTransform(1, 0, 0, 1, this.x + this.size / 2, this.y + this.size / 2);
ctx.rotate(this.direction + Math.PI / 2); // rotate 90 deg as image points up
ctx.drawImage(this.image,-this.size / 2, -this.size / 2, this.size, this.size);
}
}
}
function drawText(ctx, text, size, color) {
if (ctx) {
ctx.fillStyle = color;
ctx.font = size + "px Arial";
ctx.textAlign = "center";
ctx.fillText(text, w / 2, h * (1/4));
}
}
function getFocus(ctx) {
drawText(ctx, config.text.focus, 24, "black");
}
function drawScene(ctx) {
if (!player.size === 0) {
drawText(ctx, config.text.loading, 16, "blue")
actions.hasInput = false; // ensure instruction are up when ready
} else {
if (!actions.hasInput) { drawText(ctx, config.text.instruct, 16, "blue") }
player.update();
player.draw(ctx);
}
}
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, w, h);
currentRenderState(ctx);
requestAnimationFrame(mainLoop); // request next frame
}
// keys holds action name for each named key. eg for up action ArrowUp: "up", KeyW: "up",
const keys = Object.entries(config.keys)
.reduce((keys, [action,codes]) =>
codes.reduce((keys, code) => (keys[code] = action, keys), keys), {});
// actions are set true when key down. NOTE first up key for action cancels action
const actions = Object.keys(config.keys)
.reduce((actions,action) => (actions[action] = false, actions),{});
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
function keyEvent(event) {
if (keys[event.code] !== undefined) {
actions[keys[event.code]] = event.type === "keydown";
event.preventDefault();
actions.hasInput = true;
}
}
if (config.touchableTime) {
const actionTimers = {};
touchable.addEventListener("click", (e) => {
if (e.target.dataset.action) {
actions[e.target.dataset.action] = true;
clearTimeout(actionTimers[e.target.dataset.action]);
actionTimers[e.target.dataset.action] = setTimeout(() => actions[e.target.dataset.action] = false, config.touchableTime);
actions.hasInput=true;
if (currentRenderState !== drawScene) {
window.focus();
currentRenderState = drawScene;
}
}
});
} else {
touchable.classList.add("hide");
}
gameCanvas.addEventListener("click", () => currentRenderState = drawScene, {once: true});
canvas {border: 1px solid black}
#game {
width:402px;
height:182px;
font-size: 24px;
user-select: none;
}
.left {
position: absolute;
top: 160px;
left: 10px;
cursor: pointer;
}
.right {
position: absolute;
top: 160px;
left: 355px;
cursor: pointer;
}
#touchable span:hover {color: red}
.hide { display: none }
<div id="game">
<canvas id="gameCanvas" width="400" height="180"></canvas>
<div id="touchable">
<div class="left">
<span data-action="up">▲</span>
<span data-action="down">▼</span>
</div>
<div class="right">
<span data-action="left">◄</span>
<span data-action="right">►</span>
</div>
</div>
</div>