Dan Dan - 3 years ago 63
Javascript Question

How can I reduce lag in the following HTML 5 canvas game?

I am experimenting with game coding using HTML 5 canvas and JavaScript. My game isn't nearly complete, but I am already experience some prominent lag issues. The game can be found here: http://survival.sthost.net/gunsvsships.html
Also, any other advice on improving functionality would be helpful. Here is the js:

$(document).ready(function(){

var canvas = $("#canvas")[0];
var ctx = canvas.getContext("2d");
var w = $("#canvas").width();
var h = $("#canvas").height();
var cw = 10;

var title = new Image();
var startButton = new Image();
var startButtonSelected1 = new Image();
var startButtonSelected2 = new Image();
var startButtonSelected3 = new Image();
var background = new Image();
var ship = new Image();
var dead_ship = new Image();
var retical = new Image();
var bullet = new Image();
var slug = new Image();
var gunLabel1 = new Image();
var gunLabel2 = new Image();
var gunLabel3 = new Image();
var gunLabel4 = new Image();
var gunLabel5 = new Image();
var gunLabel1Trans = new Image();
var gunLabel2Trans = new Image();
var gunLabel3Trans = new Image();
var currentStartButton = startButton;
var startButtonIncrementor = 0;
var startButtonTimeNow = new Date().getTime();
var startButtonTimeThen = new Date().getTime();
var drawStart = true;
var gun = "Sniper"
var credits = 0;
var dead = 0;
var dead2 = 0;
var dead3 = 0;
var newSpeed = 5;
var bulletSpeed = 8;
var shipX = 0;
var ship2X = 1000;
var ship3X = 0;
var shipY = 250;
var ship2Y = 300;
var ship3Y = 250;
var bulletX = 500;
var bulletY = 550;
var slugAX = 500;
var slugBX = 500;
var slugCX = 500;
var slugAY = 550;
var slugBY = 550;
var slugCY = 550;
var gunType = -1;
var reticalX = 0;
var reticalY = 0;
var reticalX2 = -1;
var reticalY2 = -1;
var drawShipA = true;
var drawShipB = true;
var drawShipC = true;
var drawBullet = false;
var drawSlugs = false;
var now, then = new Date().getTime(), delta;
var sniperSound = new Audio("sniper.wav");
var shotgunSound = new Audio("shotgun.wav");

title.src = "images/title1.png";
startButton.src = "images/start_button.png";
startButtonSelected1.src = "images/start_button_selected1.png";
startButtonSelected2.src = "images/start_button_selected2.png";
startButtonSelected3.src = "images/start_button_selected3.png";
background.src = "images/background.png";
ship.src = "images/ship.png";
dead_ship.src = "images/crashed_ship.png";
retical.src = "images/retical.png";
bullet.src = "images/bullet.png";
slug.src = "images/slug.png";
gunLabel1.src = "images/1.png";
gunLabel2.src = "images/2.png";
gunLabel3.src = "images/3.png";
gunLabel4.src = "images/4.png";
gunLabel5.src = "images/5.png";
gunLabel1Trans.src = "images/1_trans.png";
gunLabel2Trans.src = "images/2_trans.png";
gunLabel3Trans.src = "images/3_trans.png";

sniperSound.volume = 0.1;

function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}

function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}


$('#canvas').mousemove(function(e) {
var pos = findPos(this);
reticalX = e.pageX - pos.x;
reticalY = e.pageY - pos.y;
});

$('#canvas').click(function(e) {
var pos = findPos(this);
reticalX2 = e.pageX - pos.x - 40;
reticalY2 = e.pageY - pos.y - 40;
if (gunType == 0){
if ((Math.abs(reticalX2 - (shipX - 35)) <= 50) && (Math.abs(reticalY2 - (shipY + 35)) <= 50)){
dead = 1;
credits += 100}
if ((Math.abs(reticalX2 - (ship2X - 10)) <= 50) && (Math.abs(reticalY2 - (ship2Y + 10)) <= 50)){
dead2 = 1;
credits += 100}
if ((Math.abs(reticalX2 - (ship3X - 10)) <= 50) && (Math.abs(reticalY2 - (ship3Y + 10)) <= 50)){
dead3 = 1;
credits += 100}}
else if (gunType == 1 && drawBullet == false){
drawBullet = true;
bulletX = reticalX2;
sniperSound.play();}
else if (gunType == 2 && drawSlugs == false){
drawSlugs = true;
slugAX = reticalX2;
slugBX = reticalX2;
slugCX = reticalX2;
shotgunSound.play();}
else if (gunType == -1)
if ((reticalX >= 370 && reticalY >= 220) && (reticalX < 520 && reticalY < 300)) {
drawStart = false;
gunType = 0;}
});

$(document).keydown(function(e) {
if (drawStart == false) {
if (drawBullet == false && drawSlugs == false) {
switch(e.keyCode) {
case 49:
gunType = 0;
gun = "Sniper";
break;
case 50:
gunType = 1;
gun = "Revolver";
break;
case 51:
gunType = 2;
gun = "Shotgun";
break;}
}}

});


function update() {

if ((reticalX >= 370 && reticalY >= 220) && (reticalX < 520 && reticalY < 280)) {
if ((startButtonTimeNow - startButtonTimeThen) < 500) {
currentStartButton = startButtonSelected1;
startButtonTimeNow = new Date().getTime()}
else if ((startButtonTimeNow - startButtonTimeThen) < 1000) {
currentStartButton = startButtonSelected2;
startButtonTimeNow = new Date().getTime();}
else if ((startButtonTimeNow - startButtonTimeThen) < 1500) {
currentStartButton = startButtonSelected3;
startButtonTimeNow = new Date().getTime();}
else if ((startButtonTimeNow - startButtonTimeThen) < 2000) {
startButtonTimeNow, startButtonTimeThen = new Date().getTime(), new Date().getTime();}
else {startButtonTimeNow, startButtonTimeThen = new Date().getTime(), new Date().getTime();}
}

else {currentStartButton = startButton;}

if (shipY >= -70 && dead == 0){
drawShipA = true;
shipY -= newSpeed;
shipX += newSpeed;
}
else if (dead == 1 && shipY < 600){
shipY += newSpeed;
drawShipA = false;
}
else {
shipY = getRandomInt(400, 425);
shipX = getRandomInt(100, 900);
dead = 0;
drawShipA = true;
}

if (ship2Y >= -70 && dead2 == 0){
drawShipB = true;
ship2Y -= newSpeed;
ship2X -= newSpeed;
}
else if (dead2 == 1 && ship2Y < 600){
ship2Y += newSpeed;
drawShipB = false;
}
else {
ship2Y = getRandomInt(400, 425);
ship2X = getRandomInt(100, 900);
dead2 = 0;
drawShipB = true;;
}

if (ship3Y >= -70 && dead3 == 0){
drawShipC = true;
ship3Y -= newSpeed;
ship3X -= newSpeed;
}
else if (dead3 == 1 && ship3Y < 600){
ship3Y += newSpeed;
drawShipC = false;
}
else {
ship3Y = getRandomInt(400, 425);
ship3X = getRandomInt(100, 900);
dead3 = 0;
drawShipC = true;
}

if ((dead + dead2 + dead3 >= 2) && (((Math.abs(shipX - ship2X) + Math.abs(shipY - ship2Y)) + (Math.abs(shipX - ship3X) + Math.abs(shipY - ship3Y)) + (Math.abs(ship3X - ship2X) + Math.abs(ship3Y - ship2Y))) < 1500)) {credits += 50}

if (drawBullet == true){
bulletY -= bulletSpeed;
if ((Math.abs(bulletX - (shipX + 20)) <= 40) && (Math.abs(bulletY - (shipY + 10)) <= 50)){
dead = 1;
credits += 100}
if ((Math.abs(bulletX - (ship2X + 20)) <= 40) && (Math.abs(bulletY - (ship2Y + 10)) <= 50)){
dead2 = 1;
credits += 100}
if ((Math.abs(bulletX - (ship3X + 20)) <= 40) && (Math.abs(bulletY - (ship3Y + 10)) <= 50)){
dead3 = 1;
credits += 100}
if (bulletY <= -130){
drawBullet = false;
bulletY = 550;
} }

if (drawSlugs == true){
slugAY -= bulletSpeed;
bulletSpeed = bulletSpeed * 2;
bulletSpeed = (bulletSpeed - (bulletSpeed % 3))/3
slugBX -= bulletSpeed;
slugBY -= bulletSpeed * 2;
slugCX += bulletSpeed;
slugCY -= bulletSpeed * 2;
if ((Math.abs(slugAX - (shipX + 10)) <= 40) && (Math.abs(slugAY - (shipY + 10)) <= 50)){
dead = 1;
credits += 100}
if ((Math.abs(slugAX - (ship2X + 10)) <= 40) && (Math.abs(slugAY - (ship2Y + 10)) <= 50)){
dead2 = 1;
credits += 100}
if ((Math.abs(slugAX - (ship3X + 10)) <= 40) && (Math.abs(slugAY - (ship3Y + 10)) <= 50)){
dead3 = 1;
credits += 100}
if ((Math.abs(slugBX - (shipX + 10)) <= 40) && (Math.abs(slugBY - (shipY + 10)) <= 50)){
dead = 1;
credits += 100}
if ((Math.abs(slugBX - (ship2X + 10)) <= 40) && (Math.abs(slugBY - (ship2Y + 10)) <= 50)){
dead2 = 1;
credits += 100}
if ((Math.abs(slugBX - (ship3X + 10)) <= 40) && (Math.abs(slugBY - (ship3Y + 10)) <= 50)){
dead3 = 1;
credits += 100}
if ((Math.abs(slugCX - (shipX + 10)) <= 40) && (Math.abs(slugCY - (shipY + 10)) <= 50)){
dead = 1;
credits += 100}
if ((Math.abs(slugCX - (ship2X + 10)) <= 40) && (Math.abs(slugCY - (ship2Y + 10)) <= 50)){
dead2 = 1;
credits += 100}
if ((Math.abs(slugCX - (ship3X + 10)) <= 40) && (Math.abs(slugCY - (ship3Y + 10)) <= 50)){
dead3 = 1;
credits += 100}
if (slugAY <= -150){
drawSlugs = false;
slugAY = 550;
slugBY = 550;
slugCY = 550;}}

}


function paint()
{
now = new Date().getTime();
delta = now - then;

ctx.drawImage(background,0,0);

if (drawShipA == true){ctx.drawImage(ship, shipX, shipY, 70, 70);}
else {ctx.drawImage(dead_ship, shipX, shipY, 70, 70);}

if (drawShipB == true){ctx.drawImage(ship, ship2X, ship2Y, 70, 70);}
else {ctx.drawImage(dead_ship, ship2X, ship2Y, 70, 70);}

if (drawShipC == true){ctx.drawImage(ship, ship3X, ship3Y, 70, 70);}
else {ctx.drawImage(dead_ship, ship3X, ship3Y, 70, 70);}

if (drawBullet == true){
ctx.drawImage(bullet, bulletX + 30, bulletY, 10,10);}

if (drawSlugs == true){
ctx.drawImage(slug, slugAX + 20, slugAY, 30, 30);
ctx.drawImage(slug, slugBX + 20, slugBY, 30, 30);
ctx.drawImage(slug, slugCX + 20, slugCY, 30, 30);
}

bulletSpeed = calcSpeed(delta, 6);
newSpeed = calcSpeed(delta, 3);
then = now;
ctx.drawImage(retical, reticalX - 40, reticalY - 40, 80, 80);
ctx.font = "30px sans-serif";
ctx.strokeStyle = "rgba(255, 165, 0, 1)"
ctx.strokeText("$" + credits, 15, 30);
ctx.strokeStyle = "rgba(255, 165, 0, .8)"
ctx.strokeText(gun, 450, 500);
ctx.drawImage(gunLabel1, 445, 510, 35, 35);
ctx.drawImage(gunLabel2, 470, 510, 35, 35);
ctx.drawImage(gunLabel3, 495, 510, 35, 35);
switch(gunType) {
case 0:
ctx.drawImage(gunLabel1Trans, 445, 510, 35, 35);
break;
case 1:
ctx.drawImage(gunLabel2Trans, 470, 510, 35, 35);
break;
case 2:
ctx.drawImage(gunLabel3Trans, 495, 510, 35, 35);
break;
}
if (drawStart == true) {
ctx.drawImage(background, 0, 0);
ctx.drawImage(title, 270, 90, 400, 100);
ctx.drawImage(currentStartButton, 370, 220, 150, 60);
ctx.drawImage(retical, reticalX - 40, reticalY - 40, 80, 80);}
ctx.strokeRect(0,0,w,background.height);
}

var init = function()

{
requestAnimationFrame(init);
update();
paint();
}

var calcSpeed = function(del, speed) {
return (speed * 60 * del) / 1000;
}

init();})

Answer Source

The problem is that your game speed is tied to your framerate:

var init = function()

{
    requestAnimationFrame(init);
    update();
    paint();
}   

When the number of frames per second goes down, so does the number of updates per second. As a result the game slows down when the framerate goes down and speeds up when the framerate goes up. To solve this problem, execute the update-loop in fixed intervals:

 window.setInterval(update, 10);

That way the game logic will always be updated 100 times per second, no matter how quick or slowly the rendering works right now.

When you do this, you might notice an issue though: You have no control when the drawing happens. It is possible that a call to paint happens during a call to update. As a result it will draw a game-state which is half logic-frame n and half logic-frame n+1. This could cause some weird graphic glitches. I usually structure my game loops like this (psuedocode):

 calculated_time_in_ms = getCurrentTimeInMs();

 while(game_is_running) {
     paint();
     while(calculated_time_in_ms < getCurrentTimeInMs()) {
          update();
          calculated_time_in_ms += 10;
     }
 }

This way maintaining the game logic framerate always has priority over rendering, but when the graphic engine is fast enough, it is able to draw more often than the game logics update.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download