1

I'm writing one of those simple games to learn JS and I'm learning HTML5 in the process so I need to draw things on canvas.

Here's the code:

 let paddle = new Paddle(GAME_WIDTH,GAME_HEIGHT);

 new InputHandler(paddle);

 let lastTime = 0;

 const ball = new Image();
 ball.src = 'assets/ball.png';

 function gameLoop(timeStamp){
   let dt = timeStamp - lastTime;
   lastTime = timeStamp;

   ctx.clearRect(0,0,600,600);
   paddle.update(dt);
   paddle.draw(ctx);

   ball.onload = () => {
    ctx.drawImage(ball,20,20);
  }

  window.requestAnimationFrame(gameLoop);
 }

gameLoop();

screenshot: no ball before comment

now I comment out the clearRect():

after comment

hello ball.

There's also a paddle at the bottom of the canvas that doesn't seem to be affected by the clearRect() method. It works just fine. What am I missing here?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Amri Ahmad
  • 15
  • 6
  • 1
    "What am I missing here?" at least one `}` to begin with. Also, if your indentation was correct that would help to read your code. `gameLoop` is never called. Depending on the two first points, `ball.onload` may never fire if it is indeed inside `gameLoop`, because it's an event listener, and will fire only once ; You'd be better having `ball.onload = galeLoop;` – Kaiido Apr 21 '20 at 06:07
  • @Kaiido I've included the rest of the code. Thanks for taking the time to look into this. I'll study the event listeners – Amri Ahmad Apr 21 '20 at 06:31

1 Answers1

1

It doesn't make much sense to put the image's onload handler inside the game loop. This means the game has to begin running before the image's onload function is set, leading to a pretty confusing situation.

The correct sequence is to set the onload handlers, then the image sources, then await all of the image onloads firing before running the game loop. Setting the main loop to an onload directly is pretty easy when you only have one image, but for a game with multiple assets, this can get awkward quickly.

Here's a minimal example of how you might load many game assets using Promise.all. Very likely, you'll want to unpack the loaded images into more descriptive objects rather than an array, but this is a start.

const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = 400;
canvas.height = 250;
const ctx = canvas.getContext("2d");

const assets = [
  "http://placekitten.com/120/100",
  "http://placekitten.com/120/120",
  "http://placekitten.com/120/140",
];
const assetsLoaded = assets.map(url =>
  new Promise(resolve => {
    const img = new Image();
    img.onerror = e => reject(`${url} failed to load`);
    img.onload = e => resolve(img);
    img.src = url;
  })
);

Promise
  .all(assetsLoaded)
  .then(images => {
    (function gameLoop() {
      requestAnimationFrame(gameLoop);
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      images.forEach((e, i) =>
        ctx.drawImage(
          e, 
          i * 120, // x
          Math.sin(Date.now() * 0.005) * 20 + 40 // y
        )
      );
    })();
  })
  .catch(err => console.error(err))
;
ggorlen
  • 44,755
  • 7
  • 76
  • 106