This is the second day of my participation in Gwen Challenge

Introduction to the

After the previous study, we have basically mastered the common API and painting skills. Now let’s take a closer look at how canvas can be used in a project by implementing a mini-game.

Introduction to the Game

The user has a spaceship that can be moved left and right with the arrow keys and fired with the space bar. Enemy ships at the top of the screen move back and forth, firing missiles randomly at the same time. And then according to the missile and the collision of the ship, to determine when the user’s ship or the person’s ship, was killed.

Draw the game background

For the sake of the game, the experience goes up. We first draw the background of the game, moving the image objects on the background from bottom to top over time.

Add compatibility animation function first to improve animation efficiency.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Document</title>
  </head>
  <style type="text/css">
    .canvasDiv {
      margin: 0 auto;
      width: 500px;
    }
  </style>
  <body>
    <div class="canvasDiv">
      <canvas width="650" height="500" id="canvas"></canvas>
    </div>
    <script type="text/javascript">
      // Compatibility processing
      window.requestAnimFrame = (function () {
        return (
          window.requestAnimationFrame ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame ||
          window.oRequestAnimationFrame ||
          window.msRequestAnimationFrame ||
          function (callback, element) {
            window.setTimeout(callback, 1000 / 60)})}) ()</script>
  </body>
</html>
Copy the code

Initialize the canvas and game resources

      / * *@type {HTMLCanvasElement} * /
      var canvas = document.getElementById('canvas')
      var c = canvas.getContext('2d')

      // =========== Resource loading ============
      // Sprite -- your own ship, game background objects
      var sprite_image = new Image()
      sprite_image.src = './sprite.png'

      // =========== game state ============
      var game = {
        state: 'start' // Start: start playing: won: win over: die
      }
Copy the code

It is important to note that image resources are loaded asynchronously and can be loaded in the onLoad event if the image is operated separately.

Initialize the background map based on the game state.

      // =========== background animation ============
      // Background map object
      var backImgs = []
      // Load the number of background images
      function updatebackground() {
        if (game.state === 'start') {
          // Start state resets the background image
          backImgs = []
          for (var i = 0; i < 4; i++) {
            // Picture Sprite position
            var imagePos =
              i % 2
                ? { x: 270.y: 0.imgX: 150.imgY: 150.canX: 150.canY: 150}, {x: 0.y: 0.imgX: 200.imgY: 200.canX: 200.canY: 200
                  }
            backImgs.push({
              x: Math.round(Math.random() * 500), // the X-axis position
              y: 200 * i, // Y position
              speed: 1.// Move speed
              // The Sprite position in the image
              imagePos: imagePos
            })
          }

          game.state = 'playing'
        }

        // Move the position
        for (var key in backImgs) {
          var backImg = backImgs[key]
          if (backImg.y + 200= = =0) {
            // Go to the top and return to the bottom
            backImg.y = 650
            backImg.x = Math.round(Math.random() * 500) // the X-axis position
          }
          backImg.y = backImg.y - backImg.speed
        }
      }

      function drawBackground(c) {
        c.fillStyle = 'black'
        c.fillRect(0.0, canvas.width, canvas.height)

        for (var key in backImgs) {
          var backImg = backImgs[key]
          var imPos = backImg.imagePos
          // Draw a graph
          c.drawImage(sprite_image, imPos.x, imPos.y, imPos.imgX, imPos.imgY, backImg.x, backImg.y, imPos.canX, imPos.canY)
        }
      }
Copy the code
  1. Initialization data is separated from drawing images to better maintain the state data of image objects.
  2. Initialize the picture object by judging the state of the game. Because the canvas is high650pxManually set to load 4 picture objects. After the data is initialized, modify the game state so that the picture object is not initialized again.
  3. usedrawImage()According to the drawing data set in the picture object, get the corresponding Sprite in the snowflake map.
  4. Every frame goes inupdatebackground()Function to modify the corresponding Y-axis data of the object, the image object will be moved up in the canvas.
  5. The function determines the position of the object in the canvas, and when it exceeds the canvas, resets the object data to move to the bottom again.

To draw the enemy

In the game, enemies move left and right repeatedly at the top of the canvas, firing bullets at random times.

Loading enemy resources

      // =========== Resource loading ============.// Sprite map enemy ship
      var hunter1_image = new Image()
      hunter1_image.src = './Hunter.png'

      // Bomb enemy bullets
      var bomb_image = new Image()
      bomb_image.src = './bomb.png'
Copy the code

Create a function that modifies an enemy object

 // =========== enemy ============
 / / the enemy
      var enemies = []
      // Enemy bullets
      var enemyBullets = []

      var num = 10 // Number of enemies
      // Modify the enemy object
      function updateEnemies() {
        if (game.state === 'start') {
          // Initialize enemies every time you restart
          enemies = [] // Clear out enemies
          enemyBullets = [] // Empty the bullets
          for (var i = 0; i < num; i++) {
            enemies.push({
              x: 50 + i * 50.// the X-axis position
              y: 10.width: 40.height: 40.state: 'alive'.// He was alive when he hit his dead body
              counter: 0./ / measurement
              phase: Math.floor(Math.random() * 100) // Add bullet time
            })
          }
          game.state = 'playing'
        }

        // Add bullets
        for (var i = 0; i < num; i++) {
          var enemy = enemies[i]
          if(! enemy)continue

          if (enemy && enemy.state == 'alive') {
            // The survival state continues
            enemy.counter++
            // The enemy moves left and right
            enemy.x += Math.sin((enemy.counter * Math.PI * 2) / 100) * 2
            // Add bullet time
            if ((enemy.counter + enemy.phase) % 200= =0) {
              enemyBullets.push({
                x: enemy.x,
                y: enemy.y + enemy.height,
                width: 20.height: 20.counter: 0}}})// Hit state
          if (enemy && enemy.state == 'hit') {
            enemy.counter++

            // Change the state to dead after a period of time
            if (enemy.counter >= 20) {
              enemy.state = 'dead'
              enemy.counter = 0}}}// Clear dead enemies
        enemies = enemies.filter(function (e) {
          if(e && e.state ! ='dead') {
            return true
          }
          return false
        })

        // Change bullet position
        if (enemyBullets.length) {
          for (var i in enemyBullets) {
            var bullet = enemyBullets[i]
            bullet.y += 1.2
            bullet.counter++
          }

          // Remove bullets off screen
          enemyBullets = enemyBullets.filter(function (bullet) {
            return bullet.y < 600}}})Copy the code
  1. 10 enemy objects are calculated based on the width of the canvas and the distance the enemy moves from left to right.
  2. Same as before depending on the game stategame.stateDetermine whether to initialize the enemy object and modify the game state after it is created.It is important to note at this point that the code that changes the state of the game needs to be commented out after initialization in the background object// game.state = 'playing'Because the initialization data will be affected after the game state changes, the function call location cannot be modified.
  3. Once the enemy object is initialized, it is executed on a per-frame basisupdateEnemies()Delta function, byMath.sin()To compute the y displacement so that it can move left or right.
  4. The position is modified according to the number of times the function is executedenemy.counterAnd random numbers to determine when enemy bullets are generated.
  5. Determine enemy status (status will be modified after hit). After being hit, enter the hit state, do not generate bullets, and enter the countdown end, enter the death state. Clear an object that has entered the dead state.
  6. Loop bullet object changes position, when outside the canvas, clear bullet object.

Draw enemies and bullets

// Draw enemies
      function drawEnemies(cxt) {
        // Enemy draw
        for (var key in enemies) {
          var enemy = enemies[key]

          if (enemy.state === 'alive') {
            / / live
            // c.fillStyle = 'green'
            cxt.drawImage(hunter1_image, 25.50.22.22, enemy.x, enemy.y, enemy.width, enemy.height)
          }

          if (enemy.state === 'hit') {
            // Hit -- change to black
            cxt.fillStyle = 'black'
            cxt.fillRect(enemy.x, enemy.y, enemy.width, enemy.height)
          }

          if (enemy.state === 'dead') {
            // Death -- not drawing}}// Bullet -- draw
        for (var i in enemyBullets) {
          var bullet = enemyBullets[i]
          // Switch Sprite position to achieve animation
          var xoff = (bullet.counter % 9) * 12 + 2
          var yoff = 1
          cxt.drawImage(bomb_image, xoff, yoff, 9.10, bullet.x, bullet.y, bullet.width, bullet.height)
        }
      }
Copy the code
  1. Enter different drawing states depending on the enemy state
  2. Bullet animation is loaded via Sprite, throughbullet.counterFunction to calculate the number of times to execute, interval after how many frames into Sprite figure next Sprite, to achieve tween animation.

Add to loop

      // =========== initialize ============
      function mainLoop() {
        // Empty the canvas
        cxt.clearRect(0.0.650.500)
        // Modify the image Sprite
        updatebackground()
        // Modify enemy object enemy bullet object
        updateEnemies()

        // Draw the background image
        drawBackground(cxt)
        // Draw -- enemy -- enemy bullets
        drawEnemies(cxt)
        window.requestAnimFrame(mainLoop)
      }
      window.requestAnimFrame(mainLoop)
Copy the code

Draw the user

The user plane in the game can, through the left and right direction keys to control the plane movement, through the space bar to shoot bullets.

Load resources

      // User bullets
      var bullets_image = new Image()
      bullets_image.src = './bullets.png'
Copy the code

Initialize user objects and keyboard events

// User object
var II = {
  x: 300.y: 400.width: 50.height: 50.state: 'alive' // He was alive when he hit his dead body
}
// The user's bullet
var IIBullets = []

// Keyboard listener
var keyboard = []

// Initial user
function updatePlayer() {
  // Death is not drawn
  if (II.state == 'dead') return

  / / left?
  if (keyboard[37]) {
    II.x -= 10
    if (II.x < 0) II.x = 0
  }
  / / right
  if (keyboard[39]) {
    II.x += 10
    var right = canvas.width - II.width
    if (II.x > right) II.x = right
  }

  / / space
  if (keyboard[32]) {
    if(! keyboard.fired) {// Add bullets
      IIBullets.push({
        x: II.x + 17.5.y: II.y - 7.width: 15.height: 15.counter: 0
      })
      keyboard.fired = true}}else {
    keyboard.fired = false
  }

  // The hit state waits 40 frames to change to the dead state
  if (II.state == 'hit') {
    II.counter++
    if (II.counter >= 40) {
      II.counter = 0
      II.state = 'dead'
      game.state = 'over'}}// Change bullet position
  if (IIBullets) {
    for (i in IIBullets) {
      var bullet = IIBullets[i]
      bullet.y -= 8
      bullet.counter++
    }
    // Remove bullets off screen
    IIBullets = IIBullets.filter(function (bullet) {
      return bullet.y > 0})}Copy the code
  1. Define the user object at each executionupdatePlayer(), first judge the keyboard listening objectkeyboard, whether the value corresponding to the keyboard is in hold state (keyboardTo modify and add bullets to the user object.
  2. Hit state, increased wait time used to add death animation when drawing.
  3. Change bullet position beyond canvas clearance

Add keyboard events

// =========== keyboard events ============
 function doSetup() {
   / / press
   attachEvent(document.'keydown'.function (e) {
     keyboard[e.keyCode] = true
     console.log(keyboard)
   })
   / / to loosen
   attachEvent(document.'keyup'.function (e) {
     keyboard[e.keyCode] = false
     console.log(keyboard)
   })
 }
 function attachEvent(node, name, func) {
   if (node.addEventListener) {
     node.addEventListener(name, func, false)}else if (node.attachEvent) {
     node.attachEvent(name, func)
   }
 }
 // Execute on entry
 doSetup()
Copy the code
  • Monitor whether the keyboard is pressed, such as pressed to modify the corresponding value in the keyboard value object istrueRelease and change the value to false. Used forupdatePlayer()Function controls the modification of airplane objects.

Draw customer aircraft

// Draw yourself
function drawII(cxt) {
  if (II.state === 'alive') {
    cxt.drawImage(sprite_image, 201.0.70.80, II.x, II.y, II.width, II.height)
  }

  if (II.state === 'hit') {
    c.fillStyle = 'black'
    c.fillRect(II.x, II.y, II.width, II.height)
    // drawExplosion(cxt)
  }

  if (II.state === 'dead') {
    return
  }

  // Draw bullets
  for (i in IIBullets) {
    var bullet = IIBullets[i]
    var count = Math.floor(bullet.counter / 4)
    var xoff = (count % 4) * 24
    cxt.drawImage(
      bullets_image,
      xoff + 10.0 + 7.5.8.13,
      bullet.x,
      bullet.y,
      bullet.width,
      bullet.height //dst)}}Copy the code
  • According to the state of the aircraft, draw different images.

Added collision detection and death animation

// =========== collision detection ============
function checkCollisions() {
  // Get hit yourself
  for (var key in IIBullets) {
    var bullet = IIBullets[key];
    for (var j in enemies) {
      var enemy = enemies[j];
      if (collided(bullet, enemy)) {
        bullet.state = "hit";
        enemy.state = "hit";
        enemy.counter = 0; }}}if (II.state == "hit" || II.state == "dead") return;

  // The enemy is hit
  for (var i in enemyBullets) {
    var bullet = enemyBullets[i];
    if (collided(bullet, II)) {
      bullet.state = "hit";
      II.state = "hit";
      II.counter = 0; }}}/** * Judgment of two objects on canvas * A: the line to be judged * b: the line to be judged */
function collided(a, b) {
  // Check for horizontal collisions
  // Object B's far right is larger than object A's far left
  // The leftmost part of b is less than the rightmost part of A
  if (b.x + b.width > a.x && b.x < a.x + a.width) {
    // Check for vertical collisions
    if (b.y + b.height >= a.y && b.y < a.y + a.height) {
      return true; }}// A is in B
  if (b.x <= a.x && b.x + b.width >= a.x + a.width) {
    if (b.y <= a.y && b.y + b.height >= a.y + a.height) {
      return true; }}// B is in a
  if (a.x <= b.x && a.x + a.width >= b.x + b.width) {
    if (a.y <= b.y && a.y + a.height >= b.y + b.height) {
      return true; }}return false;
}
Copy the code
  • Check whether the plane and the enemy are not hit by the bullets of the other side, and change the status tohitAnd modifycounterTo zero.

Added death animation

/ / modify
if (II.state === 'hit') {
   // c.fillStyle = 'black'
   // c.fillRect(II.x, II.y, II.width, II.height)
   drawExplosion(cxt)
 }


// =========== Death animation ===========
var particles = []
function drawExplosion(cxt) {
  // Count starts at 0 after being hit
  if (II.counter == 0) {
    // Generate particles
    particles = []
    for (var i = 0; i < 50; i++) {
      particles.push({
        x: II.x + II.width / 2.y: II.y + II.height / 2.xv: (Math.random() - 0.5) * 2.0 * 5.0.// x velocity
        yv: (Math.random() - 0.5) * 2.0 * 5.0.// y velocity
        age: 0 // v\\\ exist time}}})if (II.counter > 0) {
    for (var i = 0; i < particles.length; i++) {
      var p = particles[i]
      p.x += p.xv
      p.y += p.yv
      var v = 255 - p.age * 3
      cxt.fillStyle = 'rgb(' + v + ', ' + v + ', ' + v + ') '
      cxt.fillRect(p.x, p.y, 3.3)
      p.age++
    }
  }
}
Copy the code
  • After the plane enters the hit state, it begins to draw the death animation. Particle explosions are where we update the list of particle objects on every frame. Calculate the middle of the plane explosion, and then expand at random speed in random directions.

Join the game state


      // =========== game state ============
      var overlay = {}
      function updateGame() {
        if (game.state == 'playing' && enemies.length == 0) {
          / / in the game
          game.state = 'won' / / victory
          overlay.title = 'All enemies destroyed'
          overlay.subtitle = 'Press the space bar to start again'
          overlay.counter = 0
        }
        if (game.state == 'over' && keyboard[32]) {
          // Game over
          game.state = 'start' / /
          II.state = 'alive'
          overlay.counter = -1
        } else if (game.state == 'over') {
          overlay.title = 'Game over'
          overlay.subtitle = 'Press the space bar to start again'
        }

        if (game.state == 'won' && keyboard[32]) {
          // Game win
          game.state = 'start' / /
          II.state = 'alive'
          overlay.counter = -1
        } else if (game.state == 'won') {
          overlay.title = 'Game win'
          overlay.subtitle = 'Press the space bar to start again'
        }

        if (overlay.counter >= 0) {
          overlay.counter++
        }
      }

      // Draw hints for different states of the game
      function drawOverlay(cxt) {
        if (game.state == 'over' || game.state == 'won') {
          cxt.fillStyle = 'white'
          cxt.font = 'Bold 40pt Arial'
          cxt.fillText(overlay.title, 225.200)
          cxt.font = '14pt Arial'
          cxt.fillText(overlay.subtitle, 250.250)}}...// =========== initialize ============
      function mainLoop() {
        // Empty the canvas
        cxt.clearRect(0.0.650.500)
        // Modify the image Sprite
        updatebackground()
        // Modify enemy object enemy bullet object
        updateEnemies()
        // Modify the user object bullet object
        updatePlayer()

        // Detect collisions
        checkCollisions()

        // Game state
        updateGame()

        // Draw the background image
        drawBackground(cxt)
        // Draw -- enemy -- enemy bullets
        drawEnemies(cxt)
        // Draw -- user -- user bullets
        drawII(cxt)
        // Game hints
        drawOverlay(cxt)
        window.requestAnimFrame(mainLoop)
      }
Copy the code
  • Add prompts according to different states.

The code address