微信小游戏 demo 飞机大战 代码分析 (三)(spirit.js, animation.js)

微信小游戏 demo 飞机大战 代码分析(三)(spirit.js, animation.js)

微信小游戏 demo 飞机大战 代码分析(一)(main.js)

微信小游戏 demo 飞机大战 代码分析(二)(databus.js)

微信小游戏 demo 飞机大战 代码分析(四)(enemy.js, bullet.js, index.js)

本博客将使用逐行代码分析的方式讲解该demo,本文适用于对其他高级语言熟悉,对js还未深入了解的同学,博主会尽可能将所有遇到的不明白的部分标注清楚,若有不正确或不清楚的地方,欢迎在评论中指正

本文的代码均由微信小游戏自动生成的demo飞机大战中获取

spirit.js

游戏基础的精灵类

代码

/**
 * 游戏基础的精灵类
 */
export default class Sprite {
  constructor(imgSrc = '', width=  0, height = 0, x = 0, y = 0) {
    this.img     = new Image()
    this.img.src = imgSrc

    this.width  = width
    this.height = height

    this.x = x
    this.y = y

    this.visible = true
  }

  /**
   * 将精灵图绘制在canvas上
   */
  drawToCanvas(ctx) {
    if ( !this.visible )
      return

    ctx.drawImage(
      this.img,
      this.x,
      this.y,
      this.width,
      this.height
    )
  }

  /**
   * 简单的碰撞检测定义:
   * 另一个精灵的中心点处于本精灵所在的矩形内即可
   * @param{Sprite} sp: Sptite的实例
   */
  isCollideWith(sp) {
    let spX = sp.x + sp.width / 2
    let spY = sp.y + sp.height / 2

    if ( !this.visible || !sp.visible )
      return false

    return !!(   spX >= this.x
              && spX <= this.x + this.width
              && spY >= this.y
              && spY <= this.y + this.height  )
  }
}

Spirite类

constructor

构造器

  • 根据输入图片路径,高度,宽度,初始坐标(x,y)生成一个精灵
    • 这里的x,y 是指图片的左上角坐标
    • 应注意的一点是此处所有参数均有缺省值
  • 初始visible设置为true,即可见

drawToCanvas(ctx)

将精灵画于画布上

  • 如果不可见,那么不画到画布上
  • 如果可见,根据该精灵的x,y坐标

isCollideWith(sp)

  • 根据传入物体的左上角的坐标和大小计算中心坐标
  • 若两个物体中任意一个不可见,则无需计算,直接返回失败
  • 判断传入物体的中心坐标有没有在该物体的方框之内

animation.js

动画类所在文件

代码

import Sprite  from './sprite'
import DataBus from '../databus'

let databus = new DataBus()

const __ = {
  timer: Symbol('timer'),
}

/**
 * 简易的帧动画类实现
 */
export default class Animation extends Sprite {
  constructor(imgSrc, width, height) {
    super(imgSrc, width, height)

    // 当前动画是否播放中
    this.isPlaying = false

    // 动画是否需要循环播放
    this.loop = false

    // 每一帧的时间间隔
    this.interval = 1000 / 60

    // 帧定时器
    this[__.timer] = null

    // 当前播放的帧
    this.index = -1

    // 总帧数
    this.count = 0

    // 帧图片集合
    this.imgList = []

    /**
     * 推入到全局动画池里面
     * 便于全局绘图的时候遍历和绘制当前动画帧
     */
    databus.animations.push(this)
  }

  /**
   * 初始化帧动画的所有帧
   * 为了简单,只支持一个帧动画
   */
  initFrames(imgList) {
    imgList.forEach((imgSrc) => {
      let img = new Image()
      img.src = imgSrc

      this.imgList.push(img)
    })

    this.count = imgList.length
  }

  // 将播放中的帧绘制到canvas上
  aniRender(ctx) {
    ctx.drawImage(
      this.imgList[this.index],
      this.x,
      this.y,
      this.width  * 1.2,
      this.height * 1.2
    )
  }

  // 播放预定的帧动画
  playAnimation(index = 0, loop = false) {
    // 动画播放的时候精灵图不再展示,播放帧动画的具体帧
    this.visible   = false

    this.isPlaying = true
    this.loop      = loop

    this.index     = index

    if ( this.interval > 0 && this.count ) {
      this[__.timer] = setInterval(
        this.frameLoop.bind(this),
        this.interval
      )
    }
  }

  // 停止帧动画播放
  stop() {
    this.isPlaying = false

    if ( this[__.timer] )
      clearInterval(this[__.timer])
  }

  // 帧遍历
  frameLoop() {
    this.index++

    if ( this.index > this.count - 1 ) {
      if ( this.loop ) {
        this.index = 0
      }

      else {
        this.index--
        this.stop()
      }
    }
  }
}

准备内容

  • 引入Spirit类和DataBus类
  • 生成一个databus对象
  • 确定一个Symbol对象

Animation类

继承于Sprite类

constructor

构造器

  • 先用图片路径和宽度高度初始化超类(spirit类)
  • 一些基本的动画设置参数,如备注所述作用

initFrames(imgList)

初始化帧动画的所有帧

  • 对传入参数imgList进行遍历
    • forEach是对js中对数组遍历的一种方式
    • =>是匿名函数的语法

aniRender(ctx)

把播放中的帧画到画布上

  • 通过调用drawImage画上动画在该时刻应该有的图像
  • 该函数在main.js中的render中有调用

playAnimation(index = 0, loop = false)

  • 将精灵图的可见设为false
    • 在本例子中有一个敌机被击毁,发生了敌机爆炸,展示爆炸的动画
  • 设置正在播放
  • 将是否循环的情况设置为初始设置的(初始设置为不循环)
  • 判断是否有动画切换间隔和帧数
    • 有的话设置定时器,使用函数setInterval
    • setInterval函数
      • 第一个参数是回调函数,是在这个过程中不断调用的函数
      • 第二个参数是间隔
      • 整个函数的含义就是在该间隔内不断调用传入的回调函数
      • (博主猜测一般情况来说主函数中的图像切换频率大于该间隔,这样才能体现动画的变化)

stop()

停止帧动画播放

  • 将播放设置为false
  • 清除原本设置的定时器

frameLoop()

帧遍历

  • 帧计数变量index加加
  • 若帧数大于图片数-1(由于计数从0开始)
    • 如果要求循环,将index置0
    • 否则将index--,即设置为最后一张图片,并且调用stop()函数暂停
posted @ 2019-05-30 20:18  Phoenix_Xie  阅读(1552)  评论(0编辑  收藏  举报