阅读 1938

原生js做h5小游戏之打砖块

原文链接: www.yangyunhe.me
作者: 弦云孤赫 原文链接: www.yangyunhe.me/2017/h5-gam…
本文可以转载,但务必注明原作者和原出处。
ps:博主在本次项目中使用了大量 es6 语法,故对于 es6 语法不太熟悉的小伙伴最好能先了解一些基本的原理再继续阅读。

前言

首先,先说明一下做这个系列的目的:其实主要源于博主希望熟练使用 canvas 的相关 api ,同时对小游戏的实现逻辑比较感兴趣,所以希望通过这一系列的小游戏来提升自身编程能力;关于 es6 语法,个人认为以后 es6 语法会越来越普及,所以算是提前熟悉语法使用技巧。小游戏的实现逻辑上可能并不完善,也许会有一些 bug ,但是毕竟只是为了提升编程能力与技巧,希望大家不要太较真

作为第一次分享,我选择打砖块这个逻辑不算太复杂的小游戏。同时,为了接近真实游戏效果,在游戏中也添加了关卡砖块血量,以及物理碰撞模型的简略实现。其实关注游戏实现逻辑就好了

线上演示地址:yangyunhe369.github.io/h5-game-blo…
github地址:github.com/yangyunhe36…
ps:github地址有代码演示,以及源码可供参考,线上演示地址可供预览

先上一个游戏完成后的截图

1

游戏工程目录如下

.
├─ index.html                   // 首页html
│  
├─ css                          // css样式资源文件
├─ images                       // 图片资源文件  
└─ js
   ├─ common.js                 // 公共js方法
   ├─ game.js                   // 游戏主要运行逻辑
   └─ scene.js                  // 游戏场景相关类
复制代码

游戏实现逻辑

这里对游戏中需要绘制的挡板、小球、砖块、计分板都进行了实例化,并将游戏主要运行逻辑单独进行实例化

挡板 Paddle

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 () {
    ...
  }
  // 向右移动
  moveRight () {
    ...
  }
  // 小球、挡板碰撞检测
  collide (ball) {
    ...
  }
  // 计算小球、挡板碰撞后x轴速度值
  collideRange (ball) {
    ...
  }
}复制代码

挡板类:主要会定义其坐标位置、图片大小、x 轴位移速度、对小球反弹速度的控制等,再根据不同按键响应 moveLeftmoveRight 移动事件,collide 方法检测小球与挡板是否碰撞,并返回布尔值

小球 Ball

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) {
    ...
  }
}复制代码

小球类:其大部分属性与挡板类似,主要通过 move 方法控制小球运动轨迹

砖块 Block

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 () {
    ...
  }
  // 小球、砖块碰撞检测
  collide (ball) {
    ...
  }
  // 计算小球、砖块碰撞后x轴速度方向
  collideBlockHorn (ball) {
    ...
  }
}复制代码

砖块类:会有两个属性不同,分别是 life是否存活。然后,在小球和砖块撞击时,调用 kill 方法扣除当前砖块血量,当血量为0时,清除砖块。collide 方法检测小球与砖块是否碰撞,并返回布尔值

计分板 Score

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 () {
    ...
  }
}复制代码

分数类:会记录当前分数、关卡数,其中 computeScore 方法会在小球碰撞砖块且砖块血量为0时调用,并累加总分

场景 Scene

class Scene {
  constructor (lv) {
    let s = {
      lv: lv,                                         // 游戏难度级别
      canvas: document.getElementById("canvas"),      // canvas 对象
      blockList: [],                                  // 砖块坐标集合
    }
    Object.assign(this, s)
  }
  // 实例化所有砖块对象
  initBlockList () {
    ...
  }
  // 创建砖块坐标二维数组,并生成不同关卡
  creatBlockList () {
    ...
  }
}复制代码

场景类:主要是根据游戏难度级别,绘制不同关卡及砖块集合(目前只生成了三个关卡)。其中 creatBlockList 方法先生成所有砖块的二维坐标数组,再调用 initBlockList 方法进行所有砖块的实例化

游戏主逻辑 Game

class Game {
  constructor (fps = 60) {
    let g = {
      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: fps,                                                     // 动画帧数,默认60
    }
    Object.assign(this, g)
  }
  ...
}复制代码

游戏核心类:这里包括游戏主要运行逻辑,包括但不限于以下几点

  • 绘制游戏整个场景
  • 调用定时器逐帧绘制动画
  • 游戏通关及游戏结束判定
  • 绑定按钮事件
  • 边界检测处理函数
  • 碰撞检测处理函数

入口函数 _main

let _main = {
  LV: 1,                               // 初始关卡
  MAXLV: 3,                            // 最终关卡
  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.fps)
    /**
     * 游戏初始化
     */
    self.game.init(self)
  }
}复制代码

入口函数:实现了游戏中需要的所有类的实例化,并进行游戏的初始化

本文总阅读量2次
文章分类
前端