【默默努力】h5-game-blockBreaker

先放下游戏的效果,我不太会玩游戏

然后放下无私开源的作者大大的地址:https://github.com/yangyunhe369/h5-game-blockBreaker
这个游戏的话,我觉得应该是如果如果球跟砖碰到了,那么这个砖就消失,然后得一分,然后这个球就会以竖直的相同的角度返回,
如果球到了发射台,就会以在发射台的角度返回去,如果球没有碰到发射台,那么球就沿着坠落的方向消失,游戏结束。
接下来我们看代码
如果我写的话,应该分数是一个,官卡是一个,砖是一个幕布,下面的发射台是一个,球是一个。应该是5个,然后再加一个功能的js,6个js去实现这个。
我们来看看作者大大怎么实现的吧~
入口的文件html,里面引用了common.js,scene.js 还有game.js,canvas绘制了游戏的背景大小

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>打砖块v1.1</title>
  <link rel="stylesheet" href="css/common.css">
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
<canvas id="canvas" width="1000" height="500"></canvas>
<div>使用左右方向键,进行移动;空格键发射小球并开始游戏,游戏结束时按空格键重置游戏;P 键暂停游戏;通关游戏后,按 N 键可进入下一关卡</div>
<script src="js/common.js"></script>
<script src="js/scene.js"></script>
<script src="js/game.js"></script>
<script src="js/main.js"></script>
</body>
</html>

common.js中的代码写的真好,都看的懂,而且作者大大有注释,真的非常注重浏览器兼容性。

//common.js
/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 封装打印日志方法
const log = console.log.bind(console)
// 生成图片对象方法
const imageFromPath = function (src) {
  let img = new Image()
  img.src = './images/' + src
  return img
}
// 检测页面不可见时自动暂停游戏方法
const isPageHidden = function (game) {
  let hiddenProperty = 'hidden' in document ? 'hidden' :    
      'webkitHidden' in document ? 'webkitHidden' :    
      'mozHidden' in document ? 'mozHidden' :    
      null
  let visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange')
  // 监听页面是否可见事件
  document.addEventListener(visibilityChangeEvent, function () {
    if (!document[hiddenProperty]) {  // 可见状态
      setTimeout(function () {
        game.state = game.state_RUNNING
      }, 100)
    } else { // 不可见状态
      game.state = game.state_STOP
    }
  })
}
// 图片素材路径
const allImg = {
  background: 'background.jpg',
  paddle: 'paddle.png',
  ball: 'ball.png',
  block1: 'block001.png',
  block2: 'block002.png',
}

main.js中就是游戏的初始化

//main.js
/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 游戏主函数
let _main = {
  LV: 1,                               // 初始关卡
  MAXLV: 2,                            // 最终关卡
  scene: null,                         // 场景对象
  blockList: null,                     // 所有砖块对象集合
  ball: null,                          // 小球对象
  paddle: null,                        // 挡板对象
  score: null,                         // 计分板对象
  ball_x: 491,                         // 小球默认x轴坐标
  ball_y: 432,                         // 小球默认y轴坐标
  paddle_x: 449,                       // 挡板默认x轴坐标
  paddle_y: 450,                       // 挡板默认y轴坐标
  score_x: 10,                         // 计分板默认x轴坐标
  score_y: 30,                         // 计分板默认y轴坐标
  fps: 60,                             // 游戏运行帧数
  game: null,                          // 游戏主要逻辑对象
  start: function () {                 // 游戏启动函数
    let self = this
    /**
     * 生成场景(根据游戏难度级别不同,生成不同关卡)
     */
    self.scene = new Scene(self.LV)
    // 实例化所有砖块对象集合
    self.blockList = self.scene.initBlockList()
    /**
     * 小球
     */
    self.ball = new Ball(self)
    /**
     * 挡板
     */
    self.paddle = new Paddle(self)
    /**
     * 计分板
     */
    self.score = new Score(self)
    /**
     * 游戏主要逻辑
     */
    self.game = new Game(self)
    /**
     * 游戏初始化
     */
    self.game.init(self)
  }
}
_main.start()

scene.js里面定义的是各种对象及方法

/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 定义挡板对象
class Paddle {
  constructor (_main) {
    let p = {
      x: _main.paddle_x,                                   // x轴坐标
      y: _main.paddle_y,                                   // y轴坐标
      w: 102,                                              // 图片宽度
      h: 22,                                               // 图片高度
      speed: 10,                                           // x轴移动速度
      ballSpeedMax: 8,                                     // 小球反弹速度最大值
      image: imageFromPath(allImg.paddle),                 // 引入图片对象
      isLeftMove: true,                                    // 能否左移
      isRightMove: true,                                   // 能否右移
    }
    Object.assign(this, p)
  }
  moveLeft () {
    this.x -= this.speed
  }
  moveRight () {
    this.x += this.speed
  }
  // 小球、挡板碰撞检测
  collide (ball) {
    let b = ball
    let p = this
    if (Math.abs((b.x + b.w/2) - (p.x + p.w/2)) < (b.w + p.w)/2 &&
        Math.abs((b.y + b.h/2) - (p.y + p.h/2)) < (b.h + p.h)/2) {
      return true
    }
    return false
  }
  // 计算小球、挡板碰撞后x轴速度值
  collideRange (ball) {
    let b = ball
    let p = this
    let rangeX = 0
    rangeX = (p.x + p.w/2) - (b.x + b.w/2)
    if (rangeX < 0) { // 小球撞击挡板左侧
      return rangeX / (b.w/2 + p.w/2) * p.ballSpeedMax
    } else if (rangeX > 0) { // 小球撞击挡板右侧
      return rangeX / (b.w/2 + p.w/2) * p.ballSpeedMax
    }
  }
}
// 小球对象
class Ball {
  constructor (_main) {
    let b = {
      x: _main.ball_x,                      // x轴坐标
      y: _main.ball_y,                      // y轴坐标
      w: 18,                                // 图片宽度
      h: 18,                                // 图片高度
      speedX: 1,                            // x轴速度
      speedY: 5,                            // y轴速度
      image: imageFromPath(allImg.ball),    // 图片对象
      fired: false,                         // 是否运动,默认静止不动
    }
    Object.assign(this, b)
  }
  move (game) {
    if (this.fired) {
      // 碰撞边界检测
      if (this.x < 0 || this.x > 1000 - this.w) {
        this.speedX *= -1
      }
      if (this.y < 0) {
        this.speedY *= -1
      }
      if (this.y > 500 - this.h) {
        // 游戏结束
        game.state = game.state_GAMEOVER
        // game.isGameOver = true
      }
      // 移动
      this.x -= this.speedX
      this.y -= this.speedY
    }
  }
}
// 砖块
class Block {
  constructor (x, y, life = 1) {
    let bk = {
      x: x,                                                                               // x轴坐标
      y: y,                                                                               // y轴坐标
      w: 50,                                                                              // 图片宽度
      h: 20,                                                                              // 图片高度
      image: life == 1 ? imageFromPath(allImg.block1) : imageFromPath(allImg.block2),     // 图片对象
      life: life,                                                                         // 生命值
      alive: true,                                                                        // 是否存活
    }
    Object.assign(this, bk)
  }
  // 消除砖块
  kill () {
    this.life--
    if (this.life == 0) {
      this.alive = false
    } else if (this.life == 1) {
      this.image = imageFromPath(allImg.block1)
    }
  }
  // 小球、砖块碰撞检测
  collide (ball) {
    let b = ball
    if (Math.abs((b.x + b.w/2) - (this.x + this.w/2)) < (b.w + this.w)/2 &&
        Math.abs((b.y + b.h/2) - (this.y + this.h/2)) < (b.h + this.h)/2) {
      this.kill()
      return true
    } else {
      return false
    }
  }
  // 计算小球、砖块碰撞后x轴速度方向
  collideBlockHorn (ball) {
    let b = ball    // 小球
    let bk = this   // 砖块
    let rangeX = 0
    let rangeY = 0
    rangeX = Math.abs((b.x + b.w/2) - (bk.x + bk.w/2))
    rangeY = Math.abs((b.y + b.h/2) - (bk.y + bk.h/2))
    if (rangeX > bk.w/2 && rangeX < (bk.w/2 + b.w/2) && rangeY < (bk.h/2 + b.h/2)) { // X轴方向与砖块四角相交
      if (b.x < bk.x && b.speedX > 0 || b.x > bk.x && b.speedX < 0) { // 小球在砖块左侧时
        return false
      } else { // 小球在砖块右侧
        return true
      }
    }
    return false
  }
}
// 计分板
class Score {
  constructor (_main) {
    let s = {
      x: _main.score_x,                               // x轴坐标
      y: _main.score_y,                               // y轴坐标
      text: '分数:',                                 // 文本分数
      textLv: '关卡:',                               // 关卡文本
      score: 200,                                     // 每个砖块对应分数
      allScore: 0,                                    // 总分
      blockList: _main.blockList,                     // 砖块对象集合
      blockListLen: _main.blockList.length,           // 砖块总数量
      lv: _main.LV,                                   // 当前关卡
    }
    Object.assign(this, s)
  }
  // 计算总分
  computeScore () {
    let num = 0
    let allNum = this.blockListLen
    num = this.blockListLen - this.blockList.length
    this.allScore = this.score * num
  }
}
// 定义场景
class Scene {
  constructor (lv) {
    let s = {
      lv: lv,                                         // 游戏难度级别
      canvas: document.getElementById("canvas"),      // canvas对象
      blockList: [],                                  // 砖块坐标集合
    }
    Object.assign(this, s)
  }
  // 实例化所有砖块对象
  initBlockList () {
    this.creatBlockList()
    let arr = []
    for (let item of this.blockList) {
      for (let list of item) {
        if (list.type === 1) {
          let obj = new Block(list.x, list.y)
          arr.push(obj)
        } else if (list.type === 2) {
          let obj = new Block(list.x, list.y, 2)
          arr.push(obj)
        }
      }
    }
    return arr
  }
  // 创建砖块坐标二维数组,并生成不同关卡
  creatBlockList () {
    let lv = this.lv,                         // 游戏难度级别
        c_w = this.canvas.width,              // canvas宽度
        c_h = this.canvas.height,             // canvas高度
        xNum_max = c_w/50,                    // x轴砖块最大数量
        yNum_max = 12,                        // y轴砖块最大数量
        x_start = 0,                          // x轴起始坐标,根据砖块数量浮动
        y_start = 60                          // y轴起始坐标,默认从60起

    switch (lv) {
      case 1 : // 正三角形
        var xNum = 16,                               // x轴砖块第一层数量
            yNum = 9                                 // y轴砖块层数
        // 循环y轴
        for(let i = 0;i < yNum;i++){
          let arr = []
          // 修改每层x轴砖块数量
          if (i === 0) {
            xNum = 1
          } else if (i === 1) {
            xNum = 2
          } else {
            xNum += 2
          }
          x_start = (xNum_max - xNum)/2 * 50             // 修改每层x轴砖块起始坐标
          // 循环x轴
          for(let k = 0;k < xNum;k++){
            if (i < 3) { // 前三排为特殊砖块
              arr.push({
                x: x_start + k*50,
                y: y_start + i*20,
                type: 2,
              })
            } else {
              arr.push({
                x: x_start + k*50,
                y: y_start + i*20,
                type: 1,
              })
            }
          }
          this.blockList.push(arr)
        }
        break
      case 2 :  // 倒三角形
        var xNum = 16,                              // x轴砖块第一层数量
            yNum = 9                                // y轴砖块层数
        // 循环y轴
        for(let i = 0;i < yNum;i++){
          let arr = []
          // 修改每层x轴砖块数量
          if (i === yNum - 1) {
            xNum = 1
          } else if (i === 0) {
            xNum = xNum
          } else {
            xNum -= 2
          }
          x_start = (xNum_max - xNum)/2 * 50             // 修改每层x轴砖块起始坐标
          // 循环x轴
          for(let k = 0;k < xNum;k++){
            if (i < 3) { // 前三排为特殊砖块
              arr.push({
                x: x_start + k*50,
                y: y_start + i*20,
                type: 2,
              })
            } else {
              arr.push({
                x: x_start + k*50,
                y: y_start + i*20,
                type: 1,
              })
            }
          }
          this.blockList.push(arr)
        }
        break
      case 3 : // 工字形
        var xNum = 16,                              // x轴砖块第一层数量
            yNum = 9                                // y轴砖块层数
        // 循环y轴
        for(let i = 0;i < yNum;i++){
          let arr = []
          // 修改每层x轴砖块数量
          if (i === 0) {
            xNum = xNum
          } else if (i > 4) {
            xNum += 2
          } else {
            xNum -= 2
          }
          x_start = (xNum_max - xNum)/2 * 50             // 修改每层x轴砖块起始坐标
          // 循环x轴
          for(let k = 0;k < xNum;k++){
            if (i < 3) { // 前三排为特殊砖块
              arr.push({
                x: x_start + k*50,
                y: y_start + i*20,
                type: 2,
              })
            } else {
              arr.push({
                x: x_start + k*50,
                y: y_start + i*20,
                type: 1,
              })
            }
          }
          this.blockList.push(arr)
        }
        break
    }
  }
}

game.js中进行的是一些游戏初始化,以及必备说明

/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 游戏主要运行逻辑
class Game {
  constructor (main) {
    let g = {
      main: main,                                                   // 游戏主函数
      actions: {},                                                  // 记录按键动作
      keydowns: {},                                                 // 记录按键keycode
      state: 1,                                                     // 游戏状态值,初始默认为1
      state_START: 1,                                               // 开始游戏
      state_RUNNING: 2,                                             // 游戏开始运行
      state_STOP: 3,                                                // 暂停游戏
      state_GAMEOVER: 4,                                            // 游戏结束
      state_UPDATE: 5,                                              // 游戏通关
      canvas: document.getElementById("canvas"),                    // canvas元素
      context: document.getElementById("canvas").getContext("2d"),  // canvas画布
      timer: null,                                                  // 轮询定时器
      fps: main.fps,                                                // 动画帧数,默认60
    }
    Object.assign(this, g)
  }
  // 绘制页面所有素材
  draw (paddle, ball, blockList, score) {
    let g = this
    // 清除画布
    g.context.clearRect(0, 0, g.canvas.width, g.canvas.height)
    // 绘制背景图
    g.drawBg()
    // 绘制挡板
    g.drawImage(paddle)
    // 绘制小球
    g.drawImage(ball)
    // 绘制砖块
    g.drawBlocks(blockList)
    // 绘制分数
    g.drawText(score)
  }
  // 绘制图片
  drawImage (obj) {
    this.context.drawImage(obj.image, obj.x, obj.y)
  }
  // 绘制背景图
  drawBg () {
    let bg = imageFromPath(allImg.background)
    this.context.drawImage(bg, 0, 0)
  }
  // 绘制所有砖块
  drawBlocks (list) {
    for (let item of list) {
      this.drawImage(item)
    }
  }
  // 绘制计数板
  drawText (obj) {
    this.context.font = '24px Microsoft YaHei'
    this.context.fillStyle = '#fff'
    // 绘制分数
    this.context.fillText(obj.text + obj.allScore, obj.x, obj.y)
    // 绘制关卡
    this.context.fillText(obj.textLv + obj.lv, this.canvas.width - 100, obj.y)
  }
  // 游戏结束
  gameOver () {
    // 清除定时器
    clearInterval(this.timer)
    // 清除画布
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
    // 绘制背景图
    this.drawBg()
    // 绘制提示文字
    this.context.font = '48px Microsoft YaHei'
    this.context.fillStyle = '#fff'
    this.context.fillText('游戏结束', 404, 226)
  }
  // 游戏晋级
  goodGame () {
    // 清除定时器
    clearInterval(this.timer)
    // 清除画布
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
    // 绘制背景图
    this.drawBg()
    // 绘制提示文字
    this.context.font = '48px Microsoft YaHei'
    this.context.fillStyle = '#fff'
    this.context.fillText('恭喜晋级下一关卡', 308, 226)
  }
  // 游戏通关
  finalGame () {
    // 清除定时器
    clearInterval(this.timer)
    // 清除画布
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
    // 绘制背景图
    this.drawBg()
    // 绘制提示文字
    this.context.font = '48px Microsoft YaHei'
    this.context.fillStyle = '#fff'
    this.context.fillText('恭喜通关全部关卡', 308, 226)
  }
  // 注册事件
  registerAction (key, callback) {
    this.actions[key] = callback
  }
  // 小球碰撞砖块检测
  checkBallBlock (g, paddle, ball, blockList, score) {
    let p = paddle, b = ball
    // 小球碰撞挡板检测
    if (p.collide(b)) {
      // 当小球运动方向趋向挡板中心时,Y轴速度取反,反之则不变
      if (Math.abs(b.y + b.h/2 - p.y + p.h/2) > Math.abs(b.y + b.h/2 + b.speedY - p.y + p.h/2)) {
        b.speedY *= -1
      } else {
        b.speedY *= 1
      }
      // 设置X轴速度
      b.speedX = p.collideRange(b)
    }
    // 小球碰撞砖块检测
    blockList.forEach(function (item, i, arr) {
      if (item.collide(b)) { // 小球、砖块已碰撞
        if (!item.alive) { // 砖块血量为0时,进行移除
          arr.splice(i, 1)
        }
        // 当小球运动方向趋向砖块中心时,速度取反,反之则不变
        if ((b.y < item.y && b.speedY < 0) || (b.y > item.y && b.speedY > 0)) {
          if (!item.collideBlockHorn(b)) {
            b.speedY *= -1
          } else { // 当小球撞击砖块四角时,Y轴速度不变
            b.speedY *= 1
          }
        } else {
          b.speedY *= 1
        }
        // 当小球撞击砖块四角时,X轴速度取反
        if (item.collideBlockHorn(b)) {
          b.speedX *= -1
        }
        // 计算分数
        score.computeScore()
      }
    })
    // 挡板移动时边界检测
    if (p.x <= 0) { // 到左边界时
      p.isLeftMove = false
    } else {
      p.isLeftMove = true
    }
    if (p.x >= 1000 - p.w) { // 到右边界时
      p.isRightMove = false
    } else {
      p.isRightMove = true
    }
    // 移动小球
    b.move(g)
  }
  // 设置逐帧动画
  setTimer (paddle, ball, blockList, score) {
    let g = this
    let main = g.main
    g.timer = setInterval(function () {
      // actions集合
      let actions = Object.keys(g.actions)
      for (let i = 0; i < actions.length; i++) {
        let key = actions[i]
        if(g.keydowns[key]) {
          // 如果按键被按下,调用注册的action
          g.actions[key]()
        }
      }
      // 当砖块数量为0时,挑战成功
      if (blockList.length == 0) {
        if (main.LV === main.MAXLV) { // 最后一关通关
          // 升级通关
          g.state = g.state_UPDATE
          // 挑战成功,渲染通关场景
          g.finalGame()
        } else { // 其余关卡通关
          // 升级通关
          g.state = g.state_UPDATE
          // 挑战成功,渲染下一关卡场景
          g.goodGame()
        }
      }
      // 判断游戏是否结束
      if (g.state === g.state_GAMEOVER) {
        g.gameOver()
      }
      // 判断游戏开始时执行事件
      if (g.state === g.state_RUNNING) {
        g.checkBallBlock(g, paddle, ball, blockList, score)
        // 绘制游戏所有素材
        g.draw(paddle, ball, blockList, score)
      } else if (g.state === g.state_START){
        // 绘制游戏所有素材
        g.draw(paddle, ball, blockList, score)
      }
    }, 1000/g.fps)
  }
  /**
   * 初始化函数
   */
  init () {
    let g = this,
        paddle = g.main.paddle,
        ball = g.main.ball,
        blockList = g.main.blockList,
        score = g.main.score
    // 设置键盘按下及松开相关注册函数
    window.addEventListener('keydown', function (event) {
     g.keydowns[event.keyCode] = true
    })
    window.addEventListener('keyup', function (event) {
      g.keydowns[event.keyCode] = false
    })
    g.registerAction = function (key, callback) {
      g.actions[key] = callback
    }
    // 注册左方向键移动事件
    g.registerAction('37', function(){
      // 判断游戏是否处于运行阶段
      if (g.state === g.state_RUNNING && paddle.isLeftMove) {
        paddle.moveLeft()
      }
    })
    // 注册右方向键移动事件
    g.registerAction('39', function(){
      // 判断游戏是否处于运行阶段
      if (g.state === g.state_RUNNING && paddle.isRightMove) {
        paddle.moveRight()
      }
    })
    window.addEventListener('keydown', function (event) {
      switch (event.keyCode) {
        // 注册空格键发射事件
        case 32 :
          if (g.state === g.state_GAMEOVER) { // 游戏结束时
            // 开始游戏
            g.state = g.state_START
            // 初始化
            g.main.start()
          } else { 
            // 开始游戏
            ball.fired = true
            g.state = g.state_RUNNING
          }
          break
        // N 键进入下一关卡
        case 78 :
          // 游戏状态为通关,且不为最终关卡时
          if (g.state === g.state_UPDATE && g.main.LV !== g.main.MAXLV) { // 进入下一关
            // 开始游戏
            g.state = g.state_START
            // 初始化下一关卡
            g.main.start(++g.main.LV)
          }
          break
        // P 键暂停游戏事件
        case 80 :
          g.state = g.state_STOP
          break
      }
    })
    // 设置轮询定时器
    g.setTimer(paddle, ball, blockList, score)
  }
}

后记:很多代码没有看懂,珍惜时间,好好学习哇~

原文地址:https://www.cnblogs.com/smart-girl/p/11447382.html