user11031046
user11031046

Reputation:

Optimizing HTML5 canvas game loop

I'm currently making an HTML5 game, and I'm trying to draw various things onto the canvas. My game is basically just where you move around in an infinite area, but I can't figure out how to optimize my code to draw bushes onto the screen. It works properly, but It lags a lot and I know there's ways to optimize it. Here's my current code:

    for(var x=offset[0];x<(offset[0]+canvas.width)+300;x++) {
        for(var y=offset[1];y<(offset[1]+canvas.height)+300;y++) {
            if(x % 85 == 0 && y % 85 == 0 && noise.simplex2(x, y) == 0) {
                ctx.drawImage(treeimage, ((x-offset[0])*-1)+canvas.width, ((y-offset[1])*-1)+canvas.height);
            }
        }
    }

treeimage is defined as so:

var treeimage = new Image(); treeimage.src = 'images/mapobjects/tree2.png';

offset[] is an array with the values being the offset of the objects relative to the player (So when the player moves left, it goes up) horizontally and vertically respectively. I use simplex noise to generate the bushes because I like them to be in small clumps. The problem that makes the FPS so low is that at the resolution of my screen, I'm running 2 modulo functions 2137104 per frame, and that gets even worse at higher resolutions. I tried to make it faster by looping through every tile of my game instead of every pixel(each tile is 85x85, so incrementing y and x by 85 instead of 1) and then adding the player offset % 85, but I had issues with that jumping around because the offset % 85 didn't go to 0 right when it jumped to the next tile, and I tried and tried to get that working in many different ways, but this is the only way I could get it to work. This is how it looks, and everything works fine besides the code being super slow. image Is there something I was missing when I was trying to optimize it, or is there a completely different way that would fix it as well. I've never really had to optimize code, so this is a new thing for me. I can tell all the lag is coming from this code because without it and just incrementing by 85 it works perfectly fine. Thank you!

Upvotes: 1

Views: 683

Answers (2)

Blindman67
Blindman67

Reputation: 54026

7225 pointless operations per image

Conditions slow code down. When ever possible you should try to avoid them.

eg the line...

if(x % 85 == 0 && y % 85 == 0 && noise.simplex2(x, y) == 0) {

... means that you are evaluating the if statement 85 * 85 (7225) times for every less than one tree this is a massive amount of unneeded overhead.

  • Remove those 7224 useless iterations.

  • Avoid indexing arrays when possible by storing repeated array lookups in a variable.

  • Simplify your math. eg ((x-offset[0])*-1)+canvas.width can be simplified to canvas.width - x + offset[0].

  • Offload as much as you can to the GPU. By default all position calculations are via the transform done on the GPU so that above math can be done once before the loop.

  • General rule for performance, reduce the amount of code inside a loop by moving what you can to outside the loop.

The snippet below implements the above points.

As you have not provided details as to the ranges of offset and canvas size the code below could be further optimized

    var x, y;
    const STEP = 85;
    const offsetX = offset[0];
    const offsetY = offset[1];
    const startX =  Math.floor(offsetX / STEP) * STEP;
    const startY = Math.floor(offsetY / STEP) * STEP;
    const endX = startX + canvas.width;
    const endY = startY + canvas.height;
    ctx.setTransform(1, 0, 0, 1, canvas.width - offsetX, canvas.height - offsetY);
    
    for (x = startX; x < endX; x += STEP) {
        for (y = startY; y < endY; y += STEP) {
            if (noise.simplex2(x, y) == 0) {
                ctx.drawImage(treeimage, x, y);
            }
        }
    }
    // reset transform
    ctx.setTransform(1, 0, 0, 1, 0, 0);

Consider a quad tree

The call to simplex2 I suspect will be very slow. All the implementations I have seen are done very poorly. As the result of simplex is constant for any coordinate it should only be done once per coordinate before the game starts (outside the code in production).

As you want an infinite (like) playfield (infinite is impossible) the RAM requirement way too large. There is not much I can suggest, well apart from... Drop the infinite and set a practical limit to the playfield size which will allow you to create a quad tree map that will make it fly.

Upvotes: 2

obscure
obscure

Reputation: 12891

Many many years ago, as computers weren't as fast as today and you had to do some hefty mathematical operations like computing the sine or cosine - or even the modulo - there was just one option:

instead of calculating it everytime you need it, calculate it once and store it in a huge look-up table. Looking up a value is of course way faster than computation.

So in your case I'd recommend generating two arrays for the modulo of x and y

let xModulo = [];
let yModulo = [];
for (let a = 0; a < canvas.width; a++) {
  xModulo.push(a % 85);
}
for (let a = 0; a < canvas.height; a++) {
  yModulo.push(a % 85);
}

and in your render loop look up the values from the arrays like:

if (xModulo[x] == 0 && yModulo[y] == 0 && noise.simplex2(x, y) == 0) {
  ctx.drawImage(treeimage, ((x - offset[0]) * -1) + canvas.width, ((y - offset[1]) * -1) + canvas.height);
}

That should give a noticeable performance boost. Depending on your needs you might need to change canvas.width / canvas.height to some higher value. You might even consider generating a look-up table for the simplex noise.

Upvotes: 0

Related Questions