SKero2004
SKero2004

Reputation: 143

Why does my canvas decrease in performance after a while?

I have the following code that makes the player jump when you press the up arrow key (sorry it's long):

class Vec {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}

class Rect {
  constructor(w, h) {
    this.pos = new Vec;
    this.size = new Vec(w, h);
    this.vel = new Vec;
    this.last = new Vec;
  }
}

class Player extends Rect {
  constructor() {
    super(40, 40);
  }
}

// setup
const backgroundContext = document.getElementById("backgroundCanvas").getContext("2d");
const groundContext = document.getElementById("groundCanvas").getContext("2d");
const objectContext = document.getElementById("objectCanvas").getContext("2d");

const WIDTH = 600;
const HEIGHT = 400;
const GROUND_Y = 50;

const player = new Player;
player.pos.x = 100;
player.pos.y = 390;
player.last.x = player.pos.x;
player.last.y = player.pos.y;
player.vel.x = 0;
let isJumping = true;
const JUMP_STRENGTH = -300;
const GRAVITY = 10;

function update(dt) {
  // update player
  player.last.x = player.pos.x;
  player.last.y = player.pos.y;

  player.vel.y += GRAVITY;
  player.pos.y += player.vel.y * dt;
  player.pos.y = Math.round(player.pos.y);

  document.addEventListener("keydown", (e) => {
    if (e.keyCode === 38 && isJumping === false) {
      isJumping = true;
      player.vel.y = JUMP_STRENGTH;
    }
  }, false);
  if (player.pos.y > HEIGHT - GROUND_Y - player.size.y) {
    isJumping = false;
    player.pos.y = HEIGHT - GROUND_Y - player.size.y;
    player.vel.y = 0;
  }
}

function draw() {
  // draw background
  backgroundContext.fillStyle = "#000";
  backgroundContext.fillRect(0, 0, WIDTH, HEIGHT);

  // draw ground
  objectContext.clearRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);
  groundContext.fillStyle = "#00ff00";
  groundContext.fillRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);

  // draw player
  objectContext.clearRect(player.last.x, player.last.y, player.size.x, player.size.y);
  objectContext.fillStyle = "#fff";
  objectContext.fillRect(player.pos.x, player.pos.y, player.size.x, player.size.y);
}

// game loop
const TIMESTEP = 1 / 60;
let accumulator = 0;
let lastRender = 0;

function loop(timestamp) {
  accumulator += (timestamp - lastRender) / 1000;
  lastRender = timestamp;
  while (accumulator >= TIMESTEP) {
    update(TIMESTEP);
    draw();
    accumulator -= TIMESTEP;
  }

  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);
canvas {
  position: absolute;
  left: 0;
  top: 0;
}
<!DOCTYPE html>
<html>

<head>
  <title>Jump_Over_It</title>
  <link href="css/default.css" rel="stylesheet" />
</head>

<body>
  <canvas style="z-index: 0;" id="backgroundCanvas" width="600" height="400"></canvas>
  <canvas style="z-index: 1;" id="groundCanvas" width="600" height="400"></canvas>
  <canvas style="z-index: 2;" id="objectCanvas" width="600" height="400"></canvas>
  <script src="js/main.js"></script>
</body>

</html>

I have three questions:

1) If you wait for about a minute, you start to notice that the performance starts to decrease. Why is this happening?

2) Why does the performance significantly drop when you hold the up arrow key?

3) In the game loop, I did:

while (accumulator >= TIMESTEP) {
  update(TIMESTEP);
  draw();
  accumulator -= TIMESTEP;
}

Is it OK to put the draw() function in the same while loop as the update() function?

If you know what to do, please let me know.

Upvotes: 3

Views: 52

Answers (1)

Kaiido
Kaiido

Reputation: 136598

For questions 1 and 2:

This is Because you are adding a new EventListener in a while loop, itself in a requestAnimationFrame loop.

I won't even calculate the number of event handlers that are attached, nor run this snippet, but do not touch your keyboard because there could be Zillions handlers executing serially there.

To fix this properly, move your addEventListener call out of these loops, you only need to call it once.

For question 3:

It is not clear why you even need this while loop at all. Would be better to actually calculate the new position directly rather than updating in a loop like that.
But at least, no. You should not call draw inside this while loop since every call will negate previous ones. So call it only once at the end of your rAF handler.

Also, note that instead of clearing on the part where your object is, you'd be better clearing the whole canvas everytime, and you might even consider moving all your drawings on a single canvas, since the compositing you thought you'd win by having three canvases actually still happens when painting the 3 DOM Elements on screen.

class Vec {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}

class Rect {
  constructor(w, h) {
    this.pos = new Vec;
    this.size = new Vec(w, h);
    this.vel = new Vec;
    this.last = new Vec;
  }
}

class Player extends Rect {
  constructor() {
    super(40, 40);
  }
}

// setup
const backgroundContext = document.getElementById("backgroundCanvas").getContext("2d");
const groundContext = document.getElementById("groundCanvas").getContext("2d");
const objectContext = document.getElementById("objectCanvas").getContext("2d");

const WIDTH = 600;
const HEIGHT = 400;
const GROUND_Y = 50;

const player = new Player;
player.pos.x = 100;
player.pos.y = 390;
player.last.x = player.pos.x;
player.last.y = player.pos.y;
player.vel.x = 0;
let isJumping = true;
const JUMP_STRENGTH = -300;
const GRAVITY = 10;

function update(dt) {
  // update player
  player.last.x = player.pos.x;
  player.last.y = player.pos.y;

  player.vel.y += GRAVITY;
  player.pos.y += player.vel.y * dt;
  player.pos.y = Math.round(player.pos.y);

  if (player.pos.y > HEIGHT - GROUND_Y - player.size.y) {
    isJumping = false;
    player.pos.y = HEIGHT - GROUND_Y - player.size.y;
    player.vel.y = 0;
  }
}
document.addEventListener("keydown", (e) => {
  if (e.keyCode === 38 && isJumping === false) {
    e.preventDefault();
    isJumping = true;
    player.vel.y = JUMP_STRENGTH;
  }
}, false);

function draw() {
  // draw background
  backgroundContext.fillStyle = "#000";
  backgroundContext.fillRect(0, 0, WIDTH, HEIGHT);

  // draw ground
  groundContext.clearRect(0, 0, WIDTH, HEIGHT);
  groundContext.fillStyle = "#00ff00";
  groundContext.fillRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);

  // draw player
  objectContext.clearRect(0, 0, WIDTH, HEIGHT);  objectContext.fillStyle = "#fff";
  objectContext.fillRect(player.pos.x, player.pos.y, player.size.x, player.size.y);
}

// game loop
const TIMESTEP = 1 / 60;
let accumulator = 0;
let lastRender = 0;

function loop(timestamp) {
  accumulator += (timestamp - lastRender) / 1000;
  lastRender = timestamp;
  while (accumulator >= TIMESTEP) {
    accumulator -= TIMESTEP;
    update(TIMESTEP);
  }
  draw();

  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);
canvas {
  position: absolute;
  left: 0;
  top: 0;
}
<!DOCTYPE html>
<html>

<head>
  <title>Jump_Over_It</title>
  <link href="css/default.css" rel="stylesheet" />
</head>

<body>
  <canvas style="z-index: 0;" id="backgroundCanvas" width="600" height="400"></canvas>
  <canvas style="z-index: 1;" id="groundCanvas" width="600" height="400"></canvas>
  <canvas style="z-index: 2;" id="objectCanvas" width="600" height="400"></canvas>
  <script src="js/main.js"></script>
</body>

</html>

Upvotes: 2

Related Questions