Im experimenting with ES6 & Canvas for game development. In this example I have an image of a spaceship and I rotate it with right and left arrow keys, and move it forward with upper key. If you look at the rotation and the moving forward you see that it's laggy. Is there a way to make it smoother?
The code for 'animation' and movement: (found in plunker as well)
from engine.js
:
let mainLoop = function() {
clrscr();
draw();
requestAnimationFrame(mainLoop);
}
let draw = function() {
spaceship.draw(ctx);
}
let keyDownListener = function(e) {
if(e.keyCode == 37)
spaceship.rotateLeft();
if(e.keyCode == 38)
spaceship.moveForward(ctx);
if(e.keyCode == 39)
spaceship.rotateRight();
if(e.keyCode == 32)
createExplosion();
};
let clrscr = function() {
ctx.fillStyle="#415575";
ctx.fillRect(0,0,w,h);
}
from spaceship.js
:
let width = image.width * resizeMultiplier;
let height = image.height * resizeMultiplier;
const rotateDelta = 0.37;
const forwardDelta = 0.77;
let draw = function(context) {
//Save context
context.save();
//Translate before rotate
context.translate(x,y);
//Rotate on translated 0,0
context.rotate((angle) * Math.PI/180);
//Draw rotated image
context.drawImage(image, -(width/2), -(height/2), width, height);
//Restore the translated & rotated coords to where we began
context.restore();
}
let rotateRight = function() {
console.log(angle);
angle = (angle === 360) ? 0 : angle + (rotateDelta *(1000/60));
}
let rotateLeft = function() {
console.log(angle);
angle = (angle === -360) ? 0 : angle - (rotateDelta *(1000/60));
}
let moveForward = function() {
let dx = Math.sin((angle) * Math.PI/180);
let dy = - Math.cos((angle) * Math.PI/180);
x += dx * forwardDelta * (1000/60);
y += dy * forwardDelta * (1000/60);
console.log('dx: ',dx,' dy: ',dy);
//x += forwardDelta * (1000/20);
}
Thank you for your time.
The main reason for that is that you use dedicated functions for each input:
spaceship.rotateLeft()
, spaceship.moveForward()
, spaceship.rotateRight()
While this looks very good OOP wise, the result is that every time the window.onkeydown
handler is invoked, it will interrupt/reset the current execution because the key-handler is not in sync with the frame-function.
The frame-function runs (in theory) at a fixed interval. But the key-handler doesn't follow the same pattern, because that get fired when you press a key. So the two kinda work against each other.
(Anyone please correct me if I'm wrong, not 100% sure about this.)
In any case, you can resolve it by moving the key-handler to spaceship.js
, and in the key-handler set a boolean
for every key to true
when you press it, and also add a onkeyup
handler to set them back to false again.
And in your draw()
function you call the function that calculates all the movement, right before you draw the new values:
//move
const rotateDelta = 7; //degrees
const forwardDelta = 10;
let key = {up:false, left:false, right:false, fire:false};
//DRAW--------------------
let draw = function(ctx) {
move();
ctx.save();
...
};
//MOVE--------------------
let move = function() {
if (key.left) {angle = (angle <= -360)?0: angle-rotateDelta;}
if (key.right) {angle = (angle >= 360)?0: angle+rotateDelta;}
if (key.up) {
x += Math.sin(angle*Math.PI/180)*forwardDelta;
y += -Math.cos(angle*Math.PI/180)*forwardDelta;
}
if (key.fire) {}
};
//KEY-HANDLER--------------------
window.onkeydown = function(e) {
if (e.keyCode == 37) {key.left=true;} //LEFT
if (e.keyCode == 38) {key.up=true;} //UP
if (e.keyCode == 39) {key.right=true;} //RIGHT
if (e.keyCode == 32) {key.fire=true;} //SPACEBAR
};
window.onkeyup = function(e) {
if (e.keyCode == 37) {key.left=false;} //LEFT
if (e.keyCode == 38) {key.up=false;} //UP
if (e.keyCode == 39) {key.right=false;} //RIGHT
if (e.keyCode == 32) {key.fire=false;} //SPACEBAR
};
*(1000/60)
and unnecessary brackets etc). If you do need them you can use them of course, but I wanted to keep the calculations as light as possible to remove any cause for stutter. And rotateDelta
and forwardDelta
now have nice round values.7
and 10
, in the SO Code Snippet I had to lower them a bit because the ship had to be smaller due to the available space.angle === -360
and angle === 360
to respectively angle <= -360
and angle >= 360
, because your angle could skip over 360
and then the value wouldn't be reset. This way is more secure.width:100%;
and height:100%
to the <html>
, so the <canvas>
properly covers the whole page.And the result is a smooth-sailing space machine:
Engine = window.Engine || {};
Engine = function() {
let canvas,ctx, w,h;
//player
let ssImgPath = "http://i68.tinypic.com/2q87s0i.png";
let ssSizeRatio = 0.1; //multiplier for original image dimensions
let spaceship; //player object
let ssImage = new Image();
ssImage.src = ssImgPath;
//INIT--------------------
let initModule = function() {
canvas = document.getElementById("canvas");
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
ctx = canvas.getContext("2d");
w=canvas.width, h=canvas.height;
spaceship = new Spaceship({x:w/2, y:h*0.7, angle:0, canvasW:w, canvasH:h, resizeMultiplier:ssSizeRatio, image:ssImage});
mainLoop();
};
//FRAME-LOOP--------------------
let mainLoop = function() {
clrscr();
draw();
requestAnimationFrame(mainLoop);
};
let clrscr = function() {
ctx.fillStyle = "rgb(65,85,117)";
ctx.fillRect(0,0,w,h);
};
let draw = function() {
spaceship.draw(ctx);
};
//RETURN--------------------
return {initModule};
}(); window.onload=Engine.initModule;
/*==============================================================*/
/****************************************************************/
/*==============================================================*/
Spaceship = window.Spaceship || {};
Spaceship = function(options) {
let {x,y,angle, canvasW,canvasH, resizeMultiplier, image} = options;
let width = image.width*resizeMultiplier;
let height = image.height*resizeMultiplier;
//move
const rotateDelta = 7; //degrees
const forwardDelta = 5;
let key = {up:false, left:false, right:false, fire:false};
//DRAW--------------------
let draw = function(ctx) {
move();
ctx.save();
ctx.translate(x,y);
ctx.rotate((angle) * Math.PI / 180); //player rotation
ctx.drawImage(image, -width/2, -height/2, width,height);
ctx.restore();
};
//MOVE--------------------
let move = function() {
if (key.left) {angle = (angle <= -360)?0: angle-rotateDelta;}
if (key.right) {angle = (angle >= 360)?0: angle+rotateDelta;}
if (key.up) {
x += Math.sin(angle*Math.PI/180)*forwardDelta;
y += -Math.cos(angle*Math.PI/180)*forwardDelta;
}
if (key.fire) {console.log("pew");}
};
//KEY-HANDLER--------------------
window.onkeydown = function(e) {
if (e.keyCode == 37) {key.left=true;} //LEFT
if (e.keyCode == 38) {key.up=true;} //UP
if (e.keyCode == 39) {key.right=true;} //RIGHT
if (e.keyCode == 32) {key.fire=true;} //SPACEBAR
};
window.onkeyup = function(e) {
if (e.keyCode == 37) {key.left=false;} //LEFT
if (e.keyCode == 38) {key.up=false;} //UP
if (e.keyCode == 39) {key.right=false;} //RIGHT
if (e.keyCode == 32) {key.fire=false;} //SPACEBAR
};
//RETURN--------------------
return {draw};
};
html, body {width:100%; height:99%; margin:0; padding:0;}
<!DOCTYPE html>
<html>
<head>
<title>Asteroids</title>
<link rel=stylesheet href="asteroids.css">
<script src="engine.js"></script>
<script src="spaceship.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>