Jose A. Ayllón
Jose A. Ayllón

Reputation: 886

Html5 canvas animation leaves a trail

I'm trying to make a little game to play around with canvas. I've decided to go for Flappy Bird as there are plenty of examples online. My goal was to make the game "responsive" for all devices. If you open it in a mobile or whichever screen, you should be able to play it normally.

For this purpose I drew a background image within the canvas that will adapt the height to the screen's height and repeat it through the x-axis.

Flappy Bird

The issue I'm having is that, when I animate the bird, this one leaves a trail behind it.

As like so:

                                           https://i.sstatic.net/xu0Vzm.png

An easy fix would be clearing the canvas as explained in this post:

Canvas image leaves weird trail when moving left

But it isn't possible because of the way I'm painting my background (I paint it once and repaint it if it resizes).

Question

How do I avoid this image trail with a static background? Is it maybe possible to have like a false static background that doesn't need to be repainted?

I obviously want to do it in the canvas, without needing to have an image in the html.

This is the code with the main functionality so that you can take a look:

const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");

cvs.style.height = "100vh";
cvs.style.width = "100vw";

var bird = new Image();
var bg = new Image();

bird.src = "https://lh3.googleusercontent.com/prntPuix7QxlhKXx6IBtTxv7PrdEJtmzFJ4mopScNS1_klze86BVNy3PqHbQn2UQ4JyJdix3XQ=w128-h128-e365";
bg.src = "https://opengameart.org/sites/default/files/bg_5.png";

let gravity = 1;
birdPositionY = 10;
birdPositionX = 50;

const draw = () => {
  ctx.drawImage(bird, birdPositionX, birdPositionY);

  birdPositionY += gravity;
  birdPositionX += 3;

  requestAnimationFrame(draw);
};

const resizeCanvas = () => {
  const {
    width,
    height
  } = cvs.getBoundingClientRect();
  cvs.height = height;
  cvs.width = width;

  let x = 0,
    y = 0;
  while (x < cvs.width && y < cvs.height) {
    ctx.drawImage(bg, x, y, 360, cvs.height);
    x += bg.width;
  }
};

bg.onload = () => {
  resizeCanvas();
};

window.addEventListener("resize", resizeCanvas, false);

draw();
<!DOCTYPE html>
<html>

<head>
  <title>Flappy Bird</title>
  <meta name="viewport" content="width=device-width, user-scalable=no" />
  <style>
    html {
      overflow: hidden;
    }
    
    body {
      margin: 0;
    }
    
    canvas {
      width: 100vw;
      height: 100vh;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script src="index.js"></script>
</body>

</html>

Hope it did make sense!

Thanks in advance!

Upvotes: 0

Views: 1395

Answers (2)

Kaiido
Kaiido

Reputation: 136608

Draw your background on a canvas that you will keep off-screen, then draw this canvas every frame on your visible one before you draw your moving parts.

const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");
// our backround canvas that we won't append in the doc ("off-screen")
const background = cvs.cloneNode();
const bg_ctx = background.getContext('2d');

cvs.style.height = "100vh";
cvs.style.width = "100vw";

var bird = new Image();
var bg = new Image();

bird.src = "https://lh3.googleusercontent.com/prntPuix7QxlhKXx6IBtTxv7PrdEJtmzFJ4mopScNS1_klze86BVNy3PqHbQn2UQ4JyJdix3XQ=w128-h128-e365";
bg.src = "https://opengameart.org/sites/default/files/bg_5.png";

let gravity = 1;
birdPositionY = 10;
birdPositionX = 50;

const draw = () => {
  // first draw the background canvas
  ctx.drawImage(background, 0,0);
  // then your persona
  ctx.drawImage(bird, birdPositionX, birdPositionY);

  birdPositionY += gravity;
  birdPositionX += 3;

  requestAnimationFrame(draw);
};

const resizeCanvas = () => {
  const {
    width,
    height
  } = cvs.getBoundingClientRect();
  // resize both canvases
  cvs.height = background.height = height;
  cvs.width = background.width = width;

  let x = 0,
    y = 0;
  while (x < cvs.width && y < cvs.height) {
    // draw on the off-screen canvas
    bg_ctx.drawImage(bg, x, y, 360, cvs.height);
    x += bg.width;
  }
};

bg.onload = () => {
  resizeCanvas();
  draw();
};

window.addEventListener("resize", resizeCanvas, false);
<!DOCTYPE html>
<html>

<head>
  <title>Flappy Bird</title>
  <meta name="viewport" content="width=device-width, user-scalable=no" />
  <style>
    html {
      overflow: hidden;
    }
    
    body {
      margin: 0;
    }
    
    canvas {
      width: 100vw;
      height: 100vh;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script src="index.js"></script>
</body>

</html>

Upvotes: 1

anbcodes
anbcodes

Reputation: 849

I think you could just call resizeCanvas in your draw loop to redraw the background.

EDIT: Apparently that does not work. Another option is just drawing it in the draw loop like this:

const draw = () => {
  ctx.clearRect(0, 0, cvs.width, cvs.height)
  let x = 0, y = 0;
  while (x < cvs.width && y < cvs.height) {
    ctx.drawImage(bg, x, y, 360, cvs.height);
    x += bg.width;
  }

  ctx.drawImage(bird, birdPositionX, birdPositionY);

  birdPositionY += gravity;
  birdPositionX += 3;

  requestAnimationFrame(draw);
};

Edit: That did not work either. The other option would be to make two canvases and overlap them with one drawing the background and one drawing the bird.

Upvotes: 0

Related Questions