Search code examples
javascriptanimationhtml5-canvas

What is the right way to reset and restart the animation loop for a game built with JS and HTML canvas?


I have built a simple 2D game that makes use of the CanvasRenderingContext2D interface. There is a 'reset' method on the Game class that is called once the game has ended and the user clicked the 'r' key.

As far as I can tell the method resets the game object correctly but there is an issue with calling the animation function a second time because the deltaTime variable is calculated incorrectly. When the animation is first run deltaTime is around 16/ 17. When the animation is run again from the reset method, deltaTime is equal to the gameTime variable or more.

How can I factor in the difference in time when resetting the game?

Here is the animate function:

  function animate(timeStamp) {
    const deltaTime = timeStamp - lastTime
    lastTime = timeStamp
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    game.draw(ctx)
    game.update(deltaTime)
    if (game.gameOver) game.draw(ctx) // draw gameover text on top
    if (!game.gameOver) requestAnimationFrame(animate)
  }

The rest of the code can be found here. A live version of the game is deployed here.

I have tried adding another parameter to the animation function, to differentiate the initial animation from the reset animation. Then changing timeStamp or lastTime variable to gameTime or 0 or a new time with the built-in new Date() method, but none of those worked.

  function animate(timeStamp, restart) {
    const deltaTime = timeStamp - lastTime
    if (restart) timeStamp = 0
    lastTime = timeStamp
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    game.draw(ctx)
    game.update(deltaTime)
    if (game.gameOver) game.draw(ctx) // draw gameover text on top
    if (!game.gameOver) requestAnimationFrame(animate)
  }

Solution

  • I found the fix. I assigned requestAnimationFrame to a variable, then used that to properly cancel the previous animation before starting the next one. Then declared a helper variable (restart) with its initial value set to false.

    The reset method on my main Game class cancels the animation frame and sets the restart variable to true.

    The animation function checks if restart is true and if it is, it calls the update method on the game with 0 as the argument, instead of the default deltaTime variable.

    This is what the animation function looks like now:

    function animate(timeStamp) {
      const deltaTime = timeStamp - lastTime
      lastTime = timeStamp
      if (restart) {
        game.update(0)
        restart = false
      } else game.update(deltaTime)
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      game.draw(ctx)
      if (game.gameOver) game.draw(ctx) // draw gameover text on top
      if (!game.gameOver) requestID = requestAnimationFrame(animate)
    }
    

    And the reset method:

    reset() {
      if (this.gameOver) {
        cancelAnimationFrame(requestID)
        restart = true
        // reset game objects
        this.keys = []
        this.enemies = []
        this.particles = []
        this.explosions = []
        this.enemyTimer = 0
        this.enemyInterval = 2000
        this.ammo = 20
        this.ammoTimer = 0
        this.ammoInterval = 350
        this.gameOver = false
        this.score = 0
        this.gameTime = 0
        this.speed = 1
        requestID = requestAnimationFrame(animate)
      }
    }
    

    If you're interested you can check the rest of the code here.