Browse Source

init 射击游戏 template初始化

liuyuqi-dellpc 10 months ago
commit
bc57d9a347

+ 25 - 0
README.md

@@ -0,0 +1,25 @@
+## quickstart
+
+## 源码目录介绍
+```
+./js
+├── base                                   // 定义游戏开发基础类
+│   ├── animatoin.js                       // 帧动画的简易实现
+│   ├── pool.js                            // 对象池的简易实现
+│   └── sprite.js                          // 游戏基本元素精灵类
+├── libs
+│   ├── symbol.js                          // ES6 Symbol简易兼容
+│   └── weapp-adapter.js                   // 小游戏适配器
+├── npc
+│   └── enemy.js                           // 敌机类
+├── player
+│   ├── bullet.js                          // 子弹类
+│   └── index.js                           // 玩家类
+├── runtime
+│   ├── background.js                      // 背景类
+│   ├── gameinfo.js                        // 用于展示分数和结算界面
+│   └── music.js                           // 全局音效管理器
+├── databus.js                             // 管控游戏状态
+└── main.js                                // 游戏入口主函数
+
+```

BIN
audio/bgm.mp3


BIN
audio/boom.mp3


BIN
audio/bullet.mp3


+ 6 - 0
game.js

@@ -0,0 +1,6 @@
+import './js/libs/weapp-adapter'
+import './js/libs/symbol'
+
+import Main from './js/main'
+
+new Main()

+ 3 - 0
game.json

@@ -0,0 +1,3 @@
+{
+    "deviceOrientation": "portrait"
+}

BIN
images/Common.png


BIN
images/bg.jpg


BIN
images/bullet.png


BIN
images/enemy.png


BIN
images/explosion1.png


BIN
images/explosion10.png


BIN
images/explosion11.png


BIN
images/explosion12.png


BIN
images/explosion13.png


BIN
images/explosion14.png


BIN
images/explosion15.png


BIN
images/explosion16.png


BIN
images/explosion17.png


BIN
images/explosion18.png


BIN
images/explosion19.png


BIN
images/explosion2.png


BIN
images/explosion3.png


BIN
images/explosion4.png


BIN
images/explosion5.png


BIN
images/explosion6.png


BIN
images/explosion7.png


BIN
images/explosion8.png


BIN
images/explosion9.png


BIN
images/hero.png


+ 109 - 0
js/base/animation.js

@@ -0,0 +1,109 @@
+import Sprite from './sprite'
+import DataBus from '../databus'
+
+const 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) => {
+      const 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()
+      }
+    }
+  }
+}

+ 45 - 0
js/base/pool.js

@@ -0,0 +1,45 @@
+const __ = {
+  poolDic: Symbol('poolDic')
+}
+
+/**
+ * 简易的对象池实现
+ * 用于对象的存贮和重复使用
+ * 可以有效减少对象创建开销和避免频繁的垃圾回收
+ * 提高游戏性能
+ */
+export default class Pool {
+  constructor() {
+    this[__.poolDic] = {}
+  }
+
+  /**
+   * 根据对象标识符
+   * 获取对应的对象池
+   */
+  getPoolBySign(name) {
+    return this[__.poolDic][name] || (this[__.poolDic][name] = [])
+  }
+
+  /**
+   * 根据传入的对象标识符,查询对象池
+   * 对象池为空创建新的类,否则从对象池中取
+   */
+  getItemByClass(name, className) {
+    const pool = this.getPoolBySign(name)
+
+    const result = (pool.length
+      ? pool.shift()
+      : new className())
+
+    return result
+  }
+
+  /**
+   * 将对象回收到对象池
+   * 方便后续继续使用
+   */
+  recover(name, instance) {
+    this.getPoolBySign(name).push(instance)
+  }
+}

+ 49 - 0
js/base/sprite.js

@@ -0,0 +1,49 @@
+/**
+ * 游戏基础的精灵类
+ */
+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) {
+    const spX = sp.x + sp.width / 2
+    const 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)
+  }
+}

+ 51 - 0
js/databus.js

@@ -0,0 +1,51 @@
+import Pool from './base/pool'
+
+let instance
+
+/**
+ * 全局状态管理器
+ */
+export default class DataBus {
+  constructor() {
+    if (instance) return instance
+
+    instance = this
+
+    this.pool = new Pool()
+
+    this.reset()
+  }
+
+  reset() {
+    this.frame = 0
+    this.score = 0
+    this.bullets = []
+    this.enemys = []
+    this.animations = []
+    this.gameOver = false
+  }
+
+  /**
+   * 回收敌人,进入对象池
+   * 此后不进入帧循环
+   */
+  removeEnemey(enemy) {
+    const temp = this.enemys.shift()
+
+    temp.visible = false
+
+    this.pool.recover('enemy', enemy)
+  }
+
+  /**
+   * 回收子弹,进入对象池
+   * 此后不进入帧循环
+   */
+  removeBullets(bullet) {
+    const temp = this.bullets.shift()
+
+    temp.visible = false
+
+    this.pool.recover('bullet', bullet)
+  }
+}

+ 17 - 0
js/libs/symbol.js

@@ -0,0 +1,17 @@
+/**
+ * 对于ES6中Symbol的极简兼容
+ * 方便模拟私有变量
+ */
+
+let Symbol = window.Symbol
+let idCounter = 0
+
+if (!Symbol) {
+  Symbol = function Symbol(key) {
+    return `__${key}_${Math.floor(Math.random() * 1e9)}_${++idCounter}__`
+  }
+
+  Symbol.iterator = Symbol('Symbol.iterator')
+}
+
+window.Symbol = Symbol

+ 1476 - 0
js/libs/weapp-adapter.js

@@ -0,0 +1,1476 @@
+/** *** */ (function (modules) { // webpackBootstrap
+/** *** */ 	// The module cache
+/** *** */ 	const installedModules = {}
+
+  /** *** */ 	// The require function
+  /** *** */ 	function __webpack_require__(moduleId) {
+    /** *** */ 		// Check if module is in cache
+    /** *** */ 		if (installedModules[moduleId])
+    /** *** */ 			{ return installedModules[moduleId].exports }
+
+    /** *** */ 		// Create a new module (and put it into the cache)
+    /** *** */ 		const module = installedModules[moduleId] = {
+      /** *** */ 			exports: {},
+      /** *** */ 			id: moduleId,
+      /** *** */ 			loaded: false
+      /** *** */}
+
+    /** *** */ 		// Execute the module function
+    /** *** */ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
+
+    /** *** */ 		// Flag the module as loaded
+    /** *** */ 		module.loaded = true
+
+    /** *** */ 		// Return the exports of the module
+    /** *** */ 		return module.exports
+    /** *** */ 	}
+
+
+  /** *** */ 	// expose the modules object (__webpack_modules__)
+  /** *** */ 	__webpack_require__.m = modules
+
+  /** *** */ 	// expose the module cache
+  /** *** */ 	__webpack_require__.c = installedModules
+
+  /** *** */ 	// __webpack_public_path__
+  /** *** */ 	__webpack_require__.p = ''
+
+  /** *** */ 	// Load entry module and return exports
+  /** *** */ 	return __webpack_require__(0)
+/** *** */ }([
+/* 0 */
+/***/ (function (module, exports, __webpack_require__) {
+    const _window2 = __webpack_require__(1)
+
+    const _window = _interopRequireWildcard(_window2)
+
+    function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj } else { const newObj = {}; if (obj != null) { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key] } } newObj.default = obj; return newObj } }
+
+    const global = GameGlobal
+
+    function inject() {
+	  _window.addEventListener = _window.canvas.addEventListener = function (type, listener) {
+	    _window.document.addEventListener(type, listener)
+	  }
+	  _window.removeEventListener = _window.canvas.removeEventListener = function (type, listener) {
+	    _window.document.removeEventListener(type, listener)
+	  }
+
+	  const _wx$getSystemInfoSync = wx.getSystemInfoSync()
+	      const platform = _wx$getSystemInfoSync.platform
+
+	  // 开发者工具无法重定义 window
+
+
+	  if (typeof __devtoolssubcontext === 'undefined' && platform === 'devtools') {
+	    for (const key in _window) {
+	      const descriptor = Object.getOwnPropertyDescriptor(global, key)
+
+	      if (!descriptor || descriptor.configurable === true) {
+	        Object.defineProperty(window, key, {
+	          value: _window[key]
+	        })
+	      }
+	    }
+
+	    for (const _key in _window.document) {
+	      const _descriptor = Object.getOwnPropertyDescriptor(global.document, _key)
+
+	      if (!_descriptor || _descriptor.configurable === true) {
+	        Object.defineProperty(global.document, _key, {
+	          value: _window.document[_key]
+	        })
+	      }
+	    }
+	    window.parent = window
+	  } else {
+	    for (const _key2 in _window) {
+	      global[_key2] = _window[_key2]
+	    }
+	    global.window = _window
+	    window = global
+	    window.top = window.parent = window
+	  }
+    }
+
+    if (!GameGlobal.__isAdapterInjected) {
+	  GameGlobal.__isAdapterInjected = true
+	  inject()
+    }
+    /***/ }),
+  /* 1 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+    exports.cancelAnimationFrame = exports.requestAnimationFrame = exports.clearInterval = exports.clearTimeout = exports.setInterval = exports.setTimeout = exports.canvas = exports.location = exports.localStorage = exports.HTMLElement = exports.FileReader = exports.Audio = exports.Image = exports.WebSocket = exports.XMLHttpRequest = exports.navigator = exports.document = undefined
+
+    const _WindowProperties = __webpack_require__(2)
+
+    Object.keys(_WindowProperties).forEach(function (key) {
+	  if (key === 'default' || key === '__esModule') return
+	  Object.defineProperty(exports, key, {
+	    enumerable: true,
+	    get: function get() {
+	      return _WindowProperties[key]
+	    }
+	  })
+    })
+
+    const _constructor = __webpack_require__(3)
+
+    Object.keys(_constructor).forEach(function (key) {
+	  if (key === 'default' || key === '__esModule') return
+	  Object.defineProperty(exports, key, {
+	    enumerable: true,
+	    get: function get() {
+	      return _constructor[key]
+	    }
+	  })
+    })
+
+    const _Canvas = __webpack_require__(9)
+
+    const _Canvas2 = _interopRequireDefault(_Canvas)
+
+    const _document2 = __webpack_require__(10)
+
+    const _document3 = _interopRequireDefault(_document2)
+
+    const _navigator2 = __webpack_require__(17)
+
+    const _navigator3 = _interopRequireDefault(_navigator2)
+
+    const _XMLHttpRequest2 = __webpack_require__(18)
+
+    const _XMLHttpRequest3 = _interopRequireDefault(_XMLHttpRequest2)
+
+    const _WebSocket2 = __webpack_require__(19)
+
+    const _WebSocket3 = _interopRequireDefault(_WebSocket2)
+
+    const _Image2 = __webpack_require__(11)
+
+    const _Image3 = _interopRequireDefault(_Image2)
+
+    const _Audio2 = __webpack_require__(12)
+
+    const _Audio3 = _interopRequireDefault(_Audio2)
+
+    const _FileReader2 = __webpack_require__(20)
+
+    const _FileReader3 = _interopRequireDefault(_FileReader2)
+
+    const _HTMLElement2 = __webpack_require__(4)
+
+    const _HTMLElement3 = _interopRequireDefault(_HTMLElement2)
+
+    const _localStorage2 = __webpack_require__(21)
+
+    const _localStorage3 = _interopRequireDefault(_localStorage2)
+
+    const _location2 = __webpack_require__(22)
+
+    const _location3 = _interopRequireDefault(_location2)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    exports.document = _document3.default
+    exports.navigator = _navigator3.default
+    exports.XMLHttpRequest = _XMLHttpRequest3.default
+    exports.WebSocket = _WebSocket3.default
+    exports.Image = _Image3.default
+    exports.Audio = _Audio3.default
+    exports.FileReader = _FileReader3.default
+    exports.HTMLElement = _HTMLElement3.default
+    exports.localStorage = _localStorage3.default
+    exports.location = _location3.default
+
+
+    // 暴露全局的 canvas
+    const canvas = new _Canvas2.default()
+
+    exports.canvas = canvas
+    exports.setTimeout = setTimeout
+    exports.setInterval = setInterval
+    exports.clearTimeout = clearTimeout
+    exports.clearInterval = clearInterval
+    exports.requestAnimationFrame = requestAnimationFrame
+    exports.cancelAnimationFrame = cancelAnimationFrame
+    /***/ }),
+  /* 2 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _wx$getSystemInfoSync = wx.getSystemInfoSync()
+	    const screenWidth = _wx$getSystemInfoSync.screenWidth
+	    const screenHeight = _wx$getSystemInfoSync.screenHeight
+	    const devicePixelRatio = _wx$getSystemInfoSync.devicePixelRatio
+
+    const innerWidth = exports.innerWidth = screenWidth
+    const innerHeight = exports.innerHeight = screenHeight
+    exports.devicePixelRatio = devicePixelRatio
+    const screen = exports.screen = {
+	  availWidth: innerWidth,
+	  availHeight: innerHeight
+    }
+    const performance = exports.performance = {
+	  now: function now() {
+	    return Date.now() / 1000
+	  }
+    }
+    const ontouchstart = exports.ontouchstart = null
+    const ontouchmove = exports.ontouchmove = null
+    const ontouchend = exports.ontouchend = null
+    /***/ }),
+  /* 3 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+    exports.HTMLCanvasElement = exports.HTMLImageElement = undefined
+
+    const _HTMLElement3 = __webpack_require__(4)
+
+    const _HTMLElement4 = _interopRequireDefault(_HTMLElement3)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called") } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
+
+    function _inherits(subClass, superClass) {
+      if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError(`Super expression must either be null or a function, not ${typeof superClass}`) } subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+          value: subClass, enumerable: false, writable: true, configurable: true
+        }
+      }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
+    }
+
+    const HTMLImageElement = exports.HTMLImageElement = (function (_HTMLElement) {
+	  _inherits(HTMLImageElement, _HTMLElement)
+
+	  function HTMLImageElement() {
+	    _classCallCheck(this, HTMLImageElement)
+
+	    return _possibleConstructorReturn(this, (HTMLImageElement.__proto__ || Object.getPrototypeOf(HTMLImageElement)).call(this, 'img'))
+	  }
+
+	  return HTMLImageElement
+    }(_HTMLElement4.default))
+
+    const HTMLCanvasElement = exports.HTMLCanvasElement = (function (_HTMLElement2) {
+	  _inherits(HTMLCanvasElement, _HTMLElement2)
+
+	  function HTMLCanvasElement() {
+	    _classCallCheck(this, HTMLCanvasElement)
+
+	    return _possibleConstructorReturn(this, (HTMLCanvasElement.__proto__ || Object.getPrototypeOf(HTMLCanvasElement)).call(this, 'canvas'))
+	  }
+
+	  return HTMLCanvasElement
+    }(_HTMLElement4.default))
+    /***/ }),
+  /* 4 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _createClass = (function () { function defineProperties(target, props) { for (let i = 0; i < props.length; i++) { const descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor } }())
+
+    const _Element2 = __webpack_require__(5)
+
+    const _Element3 = _interopRequireDefault(_Element2)
+
+    const _util = __webpack_require__(8)
+
+    const _WindowProperties = __webpack_require__(2)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called") } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
+
+    function _inherits(subClass, superClass) {
+      if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError(`Super expression must either be null or a function, not ${typeof superClass}`) } subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+          value: subClass, enumerable: false, writable: true, configurable: true
+        }
+      }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
+    }
+
+    const HTMLElement = (function (_Element) {
+	  _inherits(HTMLElement, _Element)
+
+	  function HTMLElement() {
+	    const tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''
+
+	    _classCallCheck(this, HTMLElement)
+
+	    const _this = _possibleConstructorReturn(this, (HTMLElement.__proto__ || Object.getPrototypeOf(HTMLElement)).call(this))
+
+	    _this.className = ''
+	    _this.childern = []
+	    _this.style = {
+	      width: `${_WindowProperties.innerWidth}px`,
+	      height: `${_WindowProperties.innerHeight}px`
+	    }
+	    _this.insertBefore = _util.noop
+	    _this.innerHTML = ''
+
+	    _this.tagName = tagName.toUpperCase()
+	    return _this
+	  }
+
+	  _createClass(HTMLElement, [{
+	    key: 'setAttribute',
+	    value: function setAttribute(name, value) {
+	      this[name] = value
+	    }
+	  }, {
+	    key: 'getAttribute',
+	    value: function getAttribute(name) {
+	      return this[name]
+	    }
+	  }, {
+	    key: 'getBoundingClientRect',
+	    value: function getBoundingClientRect() {
+	      return {
+	        top: 0,
+	        left: 0,
+	        width: _WindowProperties.innerWidth,
+	        height: _WindowProperties.innerHeight
+	      }
+	    }
+	  }, {
+	    key: 'focus',
+	    value: function focus() {}
+	  }, {
+	    key: 'clientWidth',
+	    get: function get() {
+	      const ret = parseInt(this.style.fontSize, 10) * this.innerHTML.length
+
+	      return Number.isNaN(ret) ? 0 : ret
+	    }
+	  }, {
+	    key: 'clientHeight',
+	    get: function get() {
+	      const ret = parseInt(this.style.fontSize, 10)
+
+	      return Number.isNaN(ret) ? 0 : ret
+	    }
+	  }])
+
+	  return HTMLElement
+    }(_Element3.default))
+
+    exports.default = HTMLElement
+    /***/ }),
+  /* 5 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _Node2 = __webpack_require__(6)
+
+    const _Node3 = _interopRequireDefault(_Node2)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called") } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
+
+    function _inherits(subClass, superClass) {
+      if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError(`Super expression must either be null or a function, not ${typeof superClass}`) } subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+          value: subClass, enumerable: false, writable: true, configurable: true
+        }
+      }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
+    }
+
+    const ELement = (function (_Node) {
+	  _inherits(ELement, _Node)
+
+	  function ELement() {
+	    _classCallCheck(this, ELement)
+
+	    const _this = _possibleConstructorReturn(this, (ELement.__proto__ || Object.getPrototypeOf(ELement)).call(this))
+
+	    _this.className = ''
+	    _this.children = []
+	    return _this
+	  }
+
+	  return ELement
+    }(_Node3.default))
+
+    exports.default = ELement
+    /***/ }),
+  /* 6 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _createClass = (function () { function defineProperties(target, props) { for (let i = 0; i < props.length; i++) { const descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor } }())
+
+    const _EventTarget2 = __webpack_require__(7)
+
+    const _EventTarget3 = _interopRequireDefault(_EventTarget2)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called") } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
+
+    function _inherits(subClass, superClass) {
+      if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError(`Super expression must either be null or a function, not ${typeof superClass}`) } subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+          value: subClass, enumerable: false, writable: true, configurable: true
+        }
+      }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
+    }
+
+    const Node = (function (_EventTarget) {
+	  _inherits(Node, _EventTarget)
+
+	  function Node() {
+	    _classCallCheck(this, Node)
+
+	    const _this = _possibleConstructorReturn(this, (Node.__proto__ || Object.getPrototypeOf(Node)).call(this))
+
+	    _this.childNodes = []
+	    return _this
+	  }
+
+	  _createClass(Node, [{
+	    key: 'appendChild',
+	    value: function appendChild(node) {
+	      if (node instanceof Node) {
+	        this.childNodes.push(node)
+	      } else {
+	        throw new TypeError('Failed to executed \'appendChild\' on \'Node\': parameter 1 is not of type \'Node\'.')
+	      }
+	    }
+	  }, {
+	    key: 'cloneNode',
+	    value: function cloneNode() {
+	      const copyNode = Object.create(this)
+
+	      Object.assign(copyNode, this)
+	      return copyNode
+	    }
+	  }, {
+	    key: 'removeChild',
+	    value: function removeChild(node) {
+	      const index = this.childNodes.findIndex(function (child) {
+	        return child === node
+	      })
+
+	      if (index > -1) {
+	        return this.childNodes.splice(index, 1)
+	      }
+	      return null
+	    }
+	  }])
+
+	  return Node
+    }(_EventTarget3.default))
+
+    exports.default = Node
+    /***/ }),
+  /* 7 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _createClass = (function () { function defineProperties(target, props) { for (let i = 0; i < props.length; i++) { const descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor } }())
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    const _events = new WeakMap()
+
+    const EventTarget = (function () {
+	  function EventTarget() {
+	    _classCallCheck(this, EventTarget)
+
+	    _events.set(this, {})
+	  }
+
+	  _createClass(EventTarget, [{
+	    key: 'addEventListener',
+	    value: function addEventListener(type, listener) {
+	      const options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}
+
+	      let events = _events.get(this)
+
+	      if (!events) {
+	        events = {}
+	        _events.set(this, events)
+	      }
+	      if (!events[type]) {
+	        events[type] = []
+	      }
+	      events[type].push(listener)
+
+	      if (options.capture) {
+	        console.warn('EventTarget.addEventListener: options.capture is not implemented.')
+	      }
+	      if (options.once) {
+	        console.warn('EventTarget.addEventListener: options.once is not implemented.')
+	      }
+	      if (options.passive) {
+	        console.warn('EventTarget.addEventListener: options.passive is not implemented.')
+	      }
+	    }
+	  }, {
+	    key: 'removeEventListener',
+	    value: function removeEventListener(type, listener) {
+	      const listeners = _events.get(this)[type]
+
+	      if (listeners && listeners.length > 0) {
+	        for (let i = listeners.length; i--; i > 0) {
+	          if (listeners[i] === listener) {
+	            listeners.splice(i, 1)
+	            break
+	          }
+	        }
+	      }
+	    }
+	  }, {
+	    key: 'dispatchEvent',
+	    value: function dispatchEvent() {
+	      const event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}
+
+	      const listeners = _events.get(this)[event.type]
+
+	      if (listeners) {
+	        for (let i = 0; i < listeners.length; i++) {
+	          listeners[i](event)
+	        }
+	      }
+	    }
+	  }])
+
+	  return EventTarget
+    }())
+
+    exports.default = EventTarget
+    /***/ }),
+  /* 8 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+    exports.noop = noop
+    function noop() {}
+    /***/ }),
+  /* 9 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+    exports.default = Canvas
+
+    const _constructor = __webpack_require__(3)
+
+    const _HTMLElement = __webpack_require__(4)
+
+    const _HTMLElement2 = _interopRequireDefault(_HTMLElement)
+
+    const _document = __webpack_require__(10)
+
+    const _document2 = _interopRequireDefault(_document)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    const hasModifiedCanvasPrototype = false
+    const hasInit2DContextConstructor = false
+    const hasInitWebGLContextConstructor = false
+
+    function Canvas() {
+	  const canvas = wx.createCanvas()
+
+	  canvas.type = 'canvas'
+
+	  canvas.__proto__.__proto__ = new _HTMLElement2.default('canvas')
+
+	  const _getContext = canvas.getContext
+
+	  canvas.getBoundingClientRect = function () {
+	    const ret = {
+	      top: 0,
+	      left: 0,
+	      width: window.innerWidth,
+	      height: window.innerHeight
+	    }
+	    return ret
+	  }
+
+	  return canvas
+    }
+    /***/ }),
+  /* 10 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _window = __webpack_require__(1)
+
+    const window = _interopRequireWildcard(_window)
+
+    const _HTMLElement = __webpack_require__(4)
+
+    const _HTMLElement2 = _interopRequireDefault(_HTMLElement)
+
+    const _Image = __webpack_require__(11)
+
+    const _Image2 = _interopRequireDefault(_Image)
+
+    const _Audio = __webpack_require__(12)
+
+    const _Audio2 = _interopRequireDefault(_Audio)
+
+    const _Canvas = __webpack_require__(9)
+
+    const _Canvas2 = _interopRequireDefault(_Canvas)
+
+    __webpack_require__(15)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj } else { const newObj = {}; if (obj != null) { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key] } } newObj.default = obj; return newObj } }
+
+    const events = {}
+
+    var document = {
+	  readyState: 'complete',
+	  visibilityState: 'visible',
+	  documentElement: window,
+	  hidden: false,
+	  style: {},
+	  location: window.location,
+	  ontouchstart: null,
+	  ontouchmove: null,
+	  ontouchend: null,
+
+	  head: new _HTMLElement2.default('head'),
+	  body: new _HTMLElement2.default('body'),
+
+	  createElement: function createElement(tagName) {
+	    if (tagName === 'canvas') {
+	      return new _Canvas2.default()
+	    } else if (tagName === 'audio') {
+	      return new _Audio2.default()
+	    } else if (tagName === 'img') {
+	      return new _Image2.default()
+	    }
+
+	    return new _HTMLElement2.default(tagName)
+	  },
+	  getElementById: function getElementById(id) {
+	    if (id === window.canvas.id) {
+	      return window.canvas
+	    }
+	    return null
+	  },
+	  getElementsByTagName: function getElementsByTagName(tagName) {
+	    if (tagName === 'head') {
+	      return [document.head]
+	    } else if (tagName === 'body') {
+	      return [document.body]
+	    } else if (tagName === 'canvas') {
+	      return [window.canvas]
+	    }
+	    return []
+	  },
+	  querySelector: function querySelector(query) {
+	    if (query === 'head') {
+	      return document.head
+	    } else if (query === 'body') {
+	      return document.body
+	    } else if (query === 'canvas') {
+	      return window.canvas
+	    } else if (query === `#${window.canvas.id}`) {
+	      return window.canvas
+	    }
+	    return null
+	  },
+	  querySelectorAll: function querySelectorAll(query) {
+	    if (query === 'head') {
+	      return [document.head]
+	    } else if (query === 'body') {
+	      return [document.body]
+	    } else if (query === 'canvas') {
+	      return [window.canvas]
+	    }
+	    return []
+	  },
+	  addEventListener: function addEventListener(type, listener) {
+	    if (!events[type]) {
+	      events[type] = []
+	    }
+	    events[type].push(listener)
+	  },
+	  removeEventListener: function removeEventListener(type, listener) {
+	    const listeners = events[type]
+
+	    if (listeners && listeners.length > 0) {
+	      for (let i = listeners.length; i--; i > 0) {
+	        if (listeners[i] === listener) {
+	          listeners.splice(i, 1)
+	          break
+	        }
+	      }
+	    }
+	  },
+	  dispatchEvent: function dispatchEvent(event) {
+	    const listeners = events[event.type]
+
+	    if (listeners) {
+	      for (let i = 0; i < listeners.length; i++) {
+	        listeners[i](event)
+	      }
+	    }
+	  }
+    }
+
+    exports.default = document
+    /***/ }),
+  /* 11 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+    exports.default = Image
+    function Image() {
+	  const image = wx.createImage()
+
+	  return image
+    }
+    /***/ }),
+  /* 12 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _createClass = (function () { function defineProperties(target, props) { for (let i = 0; i < props.length; i++) { const descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor } }())
+
+    const _HTMLAudioElement2 = __webpack_require__(13)
+
+    const _HTMLAudioElement3 = _interopRequireDefault(_HTMLAudioElement2)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called") } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
+
+    function _inherits(subClass, superClass) {
+      if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError(`Super expression must either be null or a function, not ${typeof superClass}`) } subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+          value: subClass, enumerable: false, writable: true, configurable: true
+        }
+      }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
+    }
+
+    const HAVE_NOTHING = 0
+    const HAVE_METADATA = 1
+    const HAVE_CURRENT_DATA = 2
+    const HAVE_FUTURE_DATA = 3
+    const HAVE_ENOUGH_DATA = 4
+
+    const _innerAudioContext = new WeakMap()
+    const _src = new WeakMap()
+    const _loop = new WeakMap()
+    const _autoplay = new WeakMap()
+
+    const Audio = (function (_HTMLAudioElement) {
+	  _inherits(Audio, _HTMLAudioElement)
+
+	  function Audio(url) {
+	    _classCallCheck(this, Audio)
+
+	    const _this = _possibleConstructorReturn(this, (Audio.__proto__ || Object.getPrototypeOf(Audio)).call(this))
+
+	    _this.HAVE_NOTHING = HAVE_NOTHING
+	    _this.HAVE_METADATA = HAVE_METADATA
+	    _this.HAVE_CURRENT_DATA = HAVE_CURRENT_DATA
+	    _this.HAVE_FUTURE_DATA = HAVE_FUTURE_DATA
+	    _this.HAVE_ENOUGH_DATA = HAVE_ENOUGH_DATA
+	    _this.readyState = HAVE_NOTHING
+
+
+	    _src.set(_this, '')
+
+	    const innerAudioContext = wx.createInnerAudioContext()
+
+	    _innerAudioContext.set(_this, innerAudioContext)
+
+	    innerAudioContext.onCanplay(function () {
+	      _this.dispatchEvent({type: 'load'})
+	      _this.dispatchEvent({type: 'loadend'})
+	      _this.dispatchEvent({type: 'canplay'})
+	      _this.dispatchEvent({type: 'canplaythrough'})
+	      _this.dispatchEvent({type: 'loadedmetadata'})
+	      _this.readyState = HAVE_CURRENT_DATA
+	    })
+	    innerAudioContext.onPlay(function () {
+	      _this.dispatchEvent({type: 'play'})
+	    })
+	    innerAudioContext.onPause(function () {
+	      _this.dispatchEvent({type: 'pause'})
+	    })
+	    innerAudioContext.onEnded(function () {
+	      _this.dispatchEvent({type: 'ended'})
+	      _this.readyState = HAVE_ENOUGH_DATA
+	    })
+	    innerAudioContext.onError(function () {
+	      _this.dispatchEvent({type: 'error'})
+	    })
+
+	    if (url) {
+	      _innerAudioContext.get(_this).src = url
+	    }
+	    return _this
+	  }
+
+	  _createClass(Audio, [{
+	    key: 'load',
+	    value: function load() {
+	      console.warn('HTMLAudioElement.load() is not implemented.')
+	    }
+	  }, {
+	    key: 'play',
+	    value: function play() {
+	      _innerAudioContext.get(this).play()
+	    }
+	  }, {
+	    key: 'pause',
+	    value: function pause() {
+	      _innerAudioContext.get(this).pause()
+	    }
+	  }, {
+	    key: 'canPlayType',
+	    value: function canPlayType() {
+	      const mediaType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''
+
+	      if (typeof mediaType !== 'string') {
+	        return ''
+	      }
+
+	      if (mediaType.indexOf('audio/mpeg') > -1 || mediaType.indexOf('audio/mp4')) {
+	        return 'probably'
+	      }
+	      return ''
+	    }
+	  }, {
+	    key: 'cloneNode',
+	    value: function cloneNode() {
+	      const newAudio = new Audio()
+	      newAudio.loop = _innerAudioContext.get(this).loop
+	      newAudio.autoplay = _innerAudioContext.get(this).loop
+	      newAudio.src = this.src
+	      return newAudio
+	    }
+	  }, {
+	    key: 'currentTime',
+	    get: function get() {
+	      return _innerAudioContext.get(this).currentTime
+	    },
+	    set: function set(value) {
+	      _innerAudioContext.get(this).seek(value)
+	    }
+	  }, {
+	    key: 'src',
+	    get: function get() {
+	      return _src.get(this)
+	    },
+	    set: function set(value) {
+	      _src.set(this, value)
+	      _innerAudioContext.get(this).src = value
+	    }
+	  }, {
+	    key: 'loop',
+	    get: function get() {
+	      return _innerAudioContext.get(this).loop
+	    },
+	    set: function set(value) {
+	      _innerAudioContext.get(this).loop = value
+	    }
+	  }, {
+	    key: 'autoplay',
+	    get: function get() {
+	      return _innerAudioContext.get(this).autoplay
+	    },
+	    set: function set(value) {
+	      _innerAudioContext.get(this).autoplay = value
+	    }
+	  }, {
+	    key: 'paused',
+	    get: function get() {
+	      return _innerAudioContext.get(this).paused
+	    }
+	  }])
+
+	  return Audio
+    }(_HTMLAudioElement3.default))
+
+    exports.default = Audio
+    /***/ }),
+  /* 13 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _HTMLMediaElement2 = __webpack_require__(14)
+
+    const _HTMLMediaElement3 = _interopRequireDefault(_HTMLMediaElement2)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called") } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
+
+    function _inherits(subClass, superClass) {
+      if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError(`Super expression must either be null or a function, not ${typeof superClass}`) } subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+          value: subClass, enumerable: false, writable: true, configurable: true
+        }
+      }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
+    }
+
+    const HTMLAudioElement = (function (_HTMLMediaElement) {
+	  _inherits(HTMLAudioElement, _HTMLMediaElement)
+
+	  function HTMLAudioElement() {
+	    _classCallCheck(this, HTMLAudioElement)
+
+	    return _possibleConstructorReturn(this, (HTMLAudioElement.__proto__ || Object.getPrototypeOf(HTMLAudioElement)).call(this, 'audio'))
+	  }
+
+	  return HTMLAudioElement
+    }(_HTMLMediaElement3.default))
+
+    exports.default = HTMLAudioElement
+    /***/ }),
+  /* 14 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _createClass = (function () { function defineProperties(target, props) { for (let i = 0; i < props.length; i++) { const descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor } }())
+
+    const _HTMLElement2 = __webpack_require__(4)
+
+    const _HTMLElement3 = _interopRequireDefault(_HTMLElement2)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called") } return call && (typeof call === 'object' || typeof call === 'function') ? call : self }
+
+    function _inherits(subClass, superClass) {
+      if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError(`Super expression must either be null or a function, not ${typeof superClass}`) } subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+          value: subClass, enumerable: false, writable: true, configurable: true
+        }
+      }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
+    }
+
+    const HTMLMediaElement = (function (_HTMLElement) {
+	  _inherits(HTMLMediaElement, _HTMLElement)
+
+	  function HTMLMediaElement(type) {
+	    _classCallCheck(this, HTMLMediaElement)
+
+	    return _possibleConstructorReturn(this, (HTMLMediaElement.__proto__ || Object.getPrototypeOf(HTMLMediaElement)).call(this, type))
+	  }
+
+	  _createClass(HTMLMediaElement, [{
+	    key: 'addTextTrack',
+	    value: function addTextTrack() {}
+	  }, {
+	    key: 'captureStream',
+	    value: function captureStream() {}
+	  }, {
+	    key: 'fastSeek',
+	    value: function fastSeek() {}
+	  }, {
+	    key: 'load',
+	    value: function load() {}
+	  }, {
+	    key: 'pause',
+	    value: function pause() {}
+	  }, {
+	    key: 'play',
+	    value: function play() {}
+	  }])
+
+	  return HTMLMediaElement
+    }(_HTMLElement3.default))
+
+    exports.default = HTMLMediaElement
+    /***/ }),
+  /* 15 */
+  /***/ (function (module, exports, __webpack_require__) {
+    __webpack_require__(16)
+    /***/ }),
+  /* 16 */
+  /***/ (function (module, exports, __webpack_require__) {
+    const _window = __webpack_require__(1)
+
+    const window = _interopRequireWildcard(_window)
+
+    const _document = __webpack_require__(10)
+
+    const _document2 = _interopRequireDefault(_document)
+
+    const _util = __webpack_require__(8)
+
+    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj} }
+
+    function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj } else { const newObj = {}; if (obj != null) { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key] } } newObj.default = obj; return newObj } }
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    const TouchEvent = function TouchEvent(type) {
+	  _classCallCheck(this, TouchEvent)
+
+	  this.target = window.canvas
+	  this.currentTarget = window.canvas
+	  this.touches = []
+	  this.targetTouches = []
+	  this.changedTouches = []
+	  this.preventDefault = _util.noop
+	  this.stopPropagation = _util.noop
+
+	  this.type = type
+    }
+
+    function touchEventHandlerFactory(type) {
+	  return function (event) {
+	    const touchEvent = new TouchEvent(type)
+
+	    touchEvent.touches = event.touches
+	    touchEvent.targetTouches = Array.prototype.slice.call(event.touches)
+	    touchEvent.changedTouches = event.changedTouches
+	    touchEvent.timeStamp = event.timeStamp
+	    _document2.default.dispatchEvent(touchEvent)
+	  }
+    }
+
+    wx.onTouchStart(touchEventHandlerFactory('touchstart'))
+    wx.onTouchMove(touchEventHandlerFactory('touchmove'))
+    wx.onTouchEnd(touchEventHandlerFactory('touchend'))
+    wx.onTouchCancel(touchEventHandlerFactory('touchcancel'))
+    /***/ }),
+  /* 17 */
+  /***/ (function (module, exports, __webpack_require__) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _util = __webpack_require__(8)
+
+    // TODO 需要 wx.getSystemInfo 获取更详细信息
+    const _wx$getSystemInfoSync = wx.getSystemInfoSync()
+	    const platform = _wx$getSystemInfoSync.platform
+
+    const navigator = {
+	  platform,
+	  language: 'zh-cn',
+	  appVersion: '5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
+	  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E8301 MicroMessenger/6.6.0 MiniGame NetType/WIFI Language/zh_CN',
+	  onLine: true, // TODO 用 wx.getNetworkStateChange 和 wx.onNetworkStateChange 来返回真实的状态
+
+	  // TODO 用 wx.getLocation 来封装 geolocation
+	  geolocation: {
+	    getCurrentPosition: _util.noop,
+	    watchPosition: _util.noop,
+	    clearWatch: _util.noop
+	  }
+    }
+
+    exports.default = navigator
+    /***/ }),
+  /* 18 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _createClass = (function () { function defineProperties(target, props) { for (let i = 0; i < props.length; i++) { const descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor } }())
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    const _url = new WeakMap()
+    const _method = new WeakMap()
+    const _requestHeader = new WeakMap()
+    const _responseHeader = new WeakMap()
+    const _requestTask = new WeakMap()
+
+    function _triggerEvent(type) {
+	  if (typeof this[`on${type}`] === 'function') {
+	    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+	      args[_key - 1] = arguments[_key]
+	    }
+
+	    this[`on${type}`].apply(this, args)
+	  }
+    }
+
+    function _changeReadyState(readyState) {
+	  this.readyState = readyState
+	  _triggerEvent.call(this, 'readystatechange')
+    }
+
+    const XMLHttpRequest = (function () {
+	  // TODO 没法模拟 HEADERS_RECEIVED 和 LOADING 两个状态
+	  function XMLHttpRequest() {
+	    _classCallCheck(this, XMLHttpRequest)
+
+	    this.onabort = null
+	    this.onerror = null
+	    this.onload = null
+	    this.onloadstart = null
+	    this.onprogress = null
+	    this.ontimeout = null
+	    this.onloadend = null
+	    this.onreadystatechange = null
+	    this.readyState = 0
+	    this.response = null
+	    this.responseText = null
+	    this.responseType = ''
+	    this.responseXML = null
+	    this.status = 0
+	    this.statusText = ''
+	    this.upload = {}
+	    this.withCredentials = false
+
+	    _requestHeader.set(this, {
+	      'content-type': 'application/x-www-form-urlencoded'
+	    })
+	    _responseHeader.set(this, {})
+	  }
+
+	  /*
+	   * TODO 这一批事件应该是在 XMLHttpRequestEventTarget.prototype 上面的
+	   */
+
+
+	  _createClass(XMLHttpRequest, [{
+	    key: 'abort',
+	    value: function abort() {
+	      const myRequestTask = _requestTask.get(this)
+
+	      if (myRequestTask) {
+	        myRequestTask.abort()
+	      }
+	    }
+	  }, {
+	    key: 'getAllResponseHeaders',
+	    value: function getAllResponseHeaders() {
+	      const responseHeader = _responseHeader.get(this)
+
+	      return Object.keys(responseHeader).map(function (header) {
+	        return `${header}: ${responseHeader[header]}`
+	      }).join('\n')
+	    }
+	  }, {
+	    key: 'getResponseHeader',
+	    value: function getResponseHeader(header) {
+	      return _responseHeader.get(this)[header]
+	    }
+	  }, {
+	    key: 'open',
+	    value: function open(method, url /* async, user, password 这几个参数在小程序内不支持 */) {
+	      _method.set(this, method)
+	      _url.set(this, url)
+	      _changeReadyState.call(this, XMLHttpRequest.OPENED)
+	    }
+	  }, {
+	    key: 'overrideMimeType',
+	    value: function overrideMimeType() {}
+	  }, {
+	    key: 'send',
+	    value: function send() {
+	      const _this = this
+
+	      const data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''
+
+	      if (this.readyState !== XMLHttpRequest.OPENED) {
+	        throw new Error("Failed to execute 'send' on 'XMLHttpRequest': The object's state must be OPENED.")
+	      } else {
+	        wx.request({
+	          data,
+	          url: _url.get(this),
+	          method: _method.get(this),
+	          header: _requestHeader.get(this),
+	          responseType: this.responseType,
+	          success: function success(_ref) {
+	            let data = _ref.data
+	                const statusCode = _ref.statusCode
+	                const header = _ref.header
+
+	            if (typeof data !== 'string' && !(data instanceof ArrayBuffer)) {
+	              try {
+	                data = JSON.stringify(data)
+	              } catch (e) {
+	                data = data
+	              }
+	            }
+
+	            _this.status = statusCode
+	            _responseHeader.set(_this, header)
+	            _triggerEvent.call(_this, 'loadstart')
+	            _changeReadyState.call(_this, XMLHttpRequest.HEADERS_RECEIVED)
+	            _changeReadyState.call(_this, XMLHttpRequest.LOADING)
+
+	            _this.response = data
+
+	            if (data instanceof ArrayBuffer) {
+	              _this.responseText = ''
+	              const bytes = new Uint8Array(data)
+	              const len = bytes.byteLength
+
+	              for (let i = 0; i < len; i++) {
+	                _this.responseText += String.fromCharCode(bytes[i])
+	              }
+	            } else {
+	              _this.responseText = data
+	            }
+	            _changeReadyState.call(_this, XMLHttpRequest.DONE)
+	            _triggerEvent.call(_this, 'load')
+	            _triggerEvent.call(_this, 'loadend')
+	          },
+	          fail: function fail(_ref2) {
+	            const errMsg = _ref2.errMsg
+
+	            // TODO 规范错误
+	            if (errMsg.indexOf('abort') !== -1) {
+	              _triggerEvent.call(_this, 'abort')
+	            } else {
+	              _triggerEvent.call(_this, 'error', errMsg)
+	            }
+	            _triggerEvent.call(_this, 'loadend')
+	          }
+	        })
+	      }
+	    }
+	  }, {
+	    key: 'setRequestHeader',
+	    value: function setRequestHeader(header, value) {
+	      const myHeader = _requestHeader.get(this)
+
+	      myHeader[header] = value
+	      _requestHeader.set(this, myHeader)
+	    }
+	  }])
+
+	  return XMLHttpRequest
+    }())
+
+    XMLHttpRequest.UNSEND = 0
+    XMLHttpRequest.OPENED = 1
+    XMLHttpRequest.HEADERS_RECEIVED = 2
+    XMLHttpRequest.LOADING = 3
+    XMLHttpRequest.DONE = 4
+    exports.default = XMLHttpRequest
+    /***/ }),
+  /* 19 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    const _createClass = (function () { function defineProperties(target, props) { for (let i = 0; i < props.length; i++) { const descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor } }())
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    const _socketTask = new WeakMap()
+
+    const WebSocket = (function () {
+	  // TODO 更新 binaryType
+	  // The connection is in the process of closing.
+	  // The connection is not yet open.
+	  function WebSocket(url) {
+	    const _this = this
+
+	    const protocols = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []
+
+	    _classCallCheck(this, WebSocket)
+
+	    this.binaryType = ''
+	    this.bufferedAmount = 0
+	    this.extensions = ''
+	    this.onclose = null
+	    this.onerror = null
+	    this.onmessage = null
+	    this.onopen = null
+	    this.protocol = ''
+	    this.readyState = 3
+
+	    if (typeof url !== 'string' || !/(^ws:\/\/)|(^wss:\/\/)/.test(url)) {
+	      throw new TypeError(`Failed to construct 'WebSocket': The URL '${url}' is invalid`)
+	    }
+
+	    this.url = url
+	    this.readyState = WebSocket.CONNECTING
+
+	    const socketTask = wx.connectSocket({
+	      url,
+	      protocols: Array.isArray(protocols) ? protocols : [protocols]
+	    })
+
+	    _socketTask.set(this, socketTask)
+
+	    socketTask.onClose(function (res) {
+	      _this.readyState = WebSocket.CLOSED
+	      if (typeof _this.onclose === 'function') {
+	        _this.onclose(res)
+	      }
+	    })
+
+	    socketTask.onMessage(function (res) {
+	      if (typeof _this.onmessage === 'function') {
+	        _this.onmessage(res)
+	      }
+	    })
+
+	    socketTask.onOpen(function () {
+	      _this.readyState = WebSocket.OPEN
+	      if (typeof _this.onopen === 'function') {
+	        _this.onopen()
+	      }
+	    })
+
+	    socketTask.onError(function (res) {
+	      if (typeof _this.onerror === 'function') {
+	        _this.onerror(new Error(res.errMsg))
+	      }
+	    })
+
+	    return this
+	  } // TODO 小程序内目前获取不到,实际上需要根据服务器选择的 sub-protocol 返回
+	  // TODO 更新 bufferedAmount
+	  // The connection is closed or couldn't be opened.
+
+	  // The connection is open and ready to communicate.
+
+
+	  _createClass(WebSocket, [{
+	    key: 'close',
+	    value: function close(code, reason) {
+	      this.readyState = WebSocket.CLOSING
+	      const socketTask = _socketTask.get(this)
+
+	      socketTask.close({
+	        code,
+	        reason
+	      })
+	    }
+	  }, {
+	    key: 'send',
+	    value: function send(data) {
+	      if (typeof data !== 'string' && !(data instanceof ArrayBuffer)) {
+	        throw new TypeError(`Failed to send message: The data ${data} is invalid`)
+	      }
+
+	      const socketTask = _socketTask.get(this)
+
+	      socketTask.send({
+	        data
+	      })
+	    }
+	  }])
+
+	  return WebSocket
+    }())
+
+    WebSocket.CONNECTING = 0
+    WebSocket.OPEN = 1
+    WebSocket.CLOSING = 2
+    WebSocket.CLOSED = 3
+    exports.default = WebSocket
+    /***/ }),
+  /* 20 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+
+    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') } }
+
+    /*
+	 * TODO 使用 wx.readFile 来封装 FileReader
+	 */
+    const FileReader = function FileReader() {
+	  _classCallCheck(this, FileReader)
+    }
+
+    exports.default = FileReader
+    /***/ }),
+  /* 21 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+    const localStorage = {
+	  get length() {
+	    const _wx$getStorageInfoSyn = wx.getStorageInfoSync()
+	        const keys = _wx$getStorageInfoSyn.keys
+
+	    return keys.length
+	  },
+
+	  key: function key(n) {
+	    const _wx$getStorageInfoSyn2 = wx.getStorageInfoSync()
+	        const keys = _wx$getStorageInfoSyn2.keys
+
+	    return keys[n]
+	  },
+	  getItem: function getItem(key) {
+	    return wx.getStorageSync(key)
+	  },
+	  setItem: function setItem(key, value) {
+	    return wx.setStorageSync(key, value)
+	  },
+	  removeItem: function removeItem(key) {
+	    wx.removeStorageSync(key)
+	  },
+	  clear: function clear() {
+	    wx.clearStorageSync()
+	  }
+    }
+
+    exports.default = localStorage
+    /***/ }),
+  /* 22 */
+  /***/ (function (module, exports) {
+    Object.defineProperty(exports, '__esModule', {
+	  value: true
+    })
+    const location = {
+	  href: 'game.js',
+	  reload: function reload() {}
+    }
+
+    exports.default = location
+    /***/ })
+/** *** */ ]))

+ 176 - 0
js/main.js

@@ -0,0 +1,176 @@
+import Player from './player/index'
+import Enemy from './npc/enemy'
+import BackGround from './runtime/background'
+import GameInfo from './runtime/gameinfo'
+import Music from './runtime/music'
+import DataBus from './databus'
+
+const ctx = canvas.getContext('2d')
+const databus = new DataBus()
+
+/**
+ * 游戏主函数
+ */
+export default class Main {
+  constructor() {
+    // 维护当前requestAnimationFrame的id
+    this.aniId = 0
+
+    this.restart()
+  }
+
+  restart() {
+    databus.reset()
+
+    canvas.removeEventListener(
+      'touchstart',
+      this.touchHandler
+    )
+
+    this.bg = new BackGround(ctx)
+    this.player = new Player(ctx)
+    this.gameinfo = new GameInfo()
+    this.music = new Music()
+
+    this.bindLoop = this.loop.bind(this)
+    this.hasEventBind = false
+
+    // 清除上一局的动画
+    window.cancelAnimationFrame(this.aniId)
+
+    this.aniId = window.requestAnimationFrame(
+      this.bindLoop,
+      canvas
+    )
+  }
+
+  /**
+   * 随着帧数变化的敌机生成逻辑
+   * 帧数取模定义成生成的频率
+   */
+  enemyGenerate() {
+    if (databus.frame % 30 === 0) {
+      const enemy = databus.pool.getItemByClass('enemy', Enemy)
+      enemy.init(6)
+      databus.enemys.push(enemy)
+    }
+  }
+
+  // 全局碰撞检测
+  collisionDetection() {
+    const that = this
+
+    databus.bullets.forEach((bullet) => {
+      for (let i = 0, il = databus.enemys.length; i < il; i++) {
+        const enemy = databus.enemys[i]
+
+        if (!enemy.isPlaying && enemy.isCollideWith(bullet)) {
+          enemy.playAnimation()
+          that.music.playExplosion()
+
+          bullet.visible = false
+          databus.score += 1
+
+          break
+        }
+      }
+    })
+
+    for (let i = 0, il = databus.enemys.length; i < il; i++) {
+      const enemy = databus.enemys[i]
+
+      if (this.player.isCollideWith(enemy)) {
+        databus.gameOver = true
+
+        break
+      }
+    }
+  }
+
+  // 游戏结束后的触摸事件处理逻辑
+  touchEventHandler(e) {
+    e.preventDefault()
+
+    const x = e.touches[0].clientX
+    const y = e.touches[0].clientY
+
+    const area = this.gameinfo.btnArea
+
+    if (x >= area.startX
+        && x <= area.endX
+        && y >= area.startY
+        && y <= area.endY) this.restart()
+  }
+
+  /**
+   * canvas重绘函数
+   * 每一帧重新绘制所有的需要展示的元素
+   */
+  render() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height)
+
+    this.bg.render(ctx)
+
+    databus.bullets
+      .concat(databus.enemys)
+      .forEach((item) => {
+        item.drawToCanvas(ctx)
+      })
+
+    this.player.drawToCanvas(ctx)
+
+    databus.animations.forEach((ani) => {
+      if (ani.isPlaying) {
+        ani.aniRender(ctx)
+      }
+    })
+
+    this.gameinfo.renderGameScore(ctx, databus.score)
+
+    // 游戏结束停止帧循环
+    if (databus.gameOver) {
+      this.gameinfo.renderGameOver(ctx, databus.score)
+
+      if (!this.hasEventBind) {
+        this.hasEventBind = true
+        this.touchHandler = this.touchEventHandler.bind(this)
+        canvas.addEventListener('touchstart', this.touchHandler)
+      }
+    }
+  }
+
+  // 游戏逻辑更新主函数
+  update() {
+    if (databus.gameOver) return
+
+    this.bg.update()
+
+    databus.bullets
+      .concat(databus.enemys)
+      .forEach((item) => {
+        item.update()
+      })
+
+    this.enemyGenerate()
+
+    this.collisionDetection()
+
+    if (databus.frame % 20 === 0) {
+      this.player.shoot()
+      this.music.playShoot()
+    }
+  }
+
+  // 实现游戏帧循环
+  loop() {
+    databus.frame++
+
+    this.update()
+    this.render()
+
+    this.aniId = window.requestAnimationFrame(
+      this.bindLoop,
+      canvas
+    )
+  }
+}

+ 55 - 0
js/npc/enemy.js

@@ -0,0 +1,55 @@
+import Animation from '../base/animation'
+import DataBus from '../databus'
+
+const ENEMY_IMG_SRC = 'images/enemy.png'
+const ENEMY_WIDTH = 60
+const ENEMY_HEIGHT = 60
+
+const __ = {
+  speed: Symbol('speed')
+}
+
+const databus = new DataBus()
+
+function rnd(start, end) {
+  return Math.floor(Math.random() * (end - start) + start)
+}
+
+export default class Enemy extends Animation {
+  constructor() {
+    super(ENEMY_IMG_SRC, ENEMY_WIDTH, ENEMY_HEIGHT)
+
+    this.initExplosionAnimation()
+  }
+
+  init(speed) {
+    this.x = rnd(0, window.innerWidth - ENEMY_WIDTH)
+    this.y = -this.height
+
+    this[__.speed] = speed
+
+    this.visible = true
+  }
+
+  // 预定义爆炸的帧动画
+  initExplosionAnimation() {
+    const frames = []
+
+    const EXPLO_IMG_PREFIX = 'images/explosion'
+    const EXPLO_FRAME_COUNT = 19
+
+    for (let i = 0; i < EXPLO_FRAME_COUNT; i++) {
+      frames.push(`${EXPLO_IMG_PREFIX + (i + 1)}.png`)
+    }
+
+    this.initFrames(frames)
+  }
+
+  // 每一帧更新子弹位置
+  update() {
+    this.y += this[__.speed]
+
+    // 对象回收
+    if (this.y > window.innerHeight + this.height) databus.removeEnemey(this)
+  }
+}

+ 35 - 0
js/player/bullet.js

@@ -0,0 +1,35 @@
+import Sprite from '../base/sprite'
+import DataBus from '../databus'
+
+const BULLET_IMG_SRC = 'images/bullet.png'
+const BULLET_WIDTH = 16
+const BULLET_HEIGHT = 30
+
+const __ = {
+  speed: Symbol('speed')
+}
+
+const databus = new DataBus()
+
+export default class Bullet extends Sprite {
+  constructor() {
+    super(BULLET_IMG_SRC, BULLET_WIDTH, BULLET_HEIGHT)
+  }
+
+  init(x, y, speed) {
+    this.x = x
+    this.y = y
+
+    this[__.speed] = speed
+
+    this.visible = true
+  }
+
+  // 每一帧更新子弹位置
+  update() {
+    this.y -= this[__.speed]
+
+    // 超出屏幕外回收自身
+    if (this.y < -this.height) databus.removeBullets(this)
+  }
+}

+ 119 - 0
js/player/index.js

@@ -0,0 +1,119 @@
+import Sprite from '../base/sprite'
+import Bullet from './bullet'
+import DataBus from '../databus'
+
+const screenWidth = window.innerWidth
+const screenHeight = window.innerHeight
+
+// 玩家相关常量设置
+const PLAYER_IMG_SRC = 'images/hero.png'
+const PLAYER_WIDTH = 80
+const PLAYER_HEIGHT = 80
+
+const databus = new DataBus()
+
+export default class Player extends Sprite {
+  constructor() {
+    super(PLAYER_IMG_SRC, PLAYER_WIDTH, PLAYER_HEIGHT)
+
+    // 玩家默认处于屏幕底部居中位置
+    this.x = screenWidth / 2 - this.width / 2
+    this.y = screenHeight - this.height - 30
+
+    // 用于在手指移动的时候标识手指是否已经在飞机上了
+    this.touched = false
+
+    this.bullets = []
+
+    // 初始化事件监听
+    this.initEvent()
+  }
+
+  /**
+   * 当手指触摸屏幕的时候
+   * 判断手指是否在飞机上
+   * @param {Number} x: 手指的X轴坐标
+   * @param {Number} y: 手指的Y轴坐标
+   * @return {Boolean}: 用于标识手指是否在飞机上的布尔值
+   */
+  checkIsFingerOnAir(x, y) {
+    const deviation = 30
+
+    return !!(x >= this.x - deviation
+              && y >= this.y - deviation
+              && x <= this.x + this.width + deviation
+              && y <= this.y + this.height + deviation)
+  }
+
+  /**
+   * 根据手指的位置设置飞机的位置
+   * 保证手指处于飞机中间
+   * 同时限定飞机的活动范围限制在屏幕中
+   */
+  setAirPosAcrossFingerPosZ(x, y) {
+    let disX = x - this.width / 2
+    let disY = y - this.height / 2
+
+    if (disX < 0) disX = 0
+
+    else if (disX > screenWidth - this.width) disX = screenWidth - this.width
+
+    if (disY <= 0) disY = 0
+
+    else if (disY > screenHeight - this.height) disY = screenHeight - this.height
+
+    this.x = disX
+    this.y = disY
+  }
+
+  /**
+   * 玩家响应手指的触摸事件
+   * 改变战机的位置
+   */
+  initEvent() {
+    canvas.addEventListener('touchstart', ((e) => {
+      e.preventDefault()
+
+      const x = e.touches[0].clientX
+      const y = e.touches[0].clientY
+
+      //
+      if (this.checkIsFingerOnAir(x, y)) {
+        this.touched = true
+
+        this.setAirPosAcrossFingerPosZ(x, y)
+      }
+    }))
+
+    canvas.addEventListener('touchmove', ((e) => {
+      e.preventDefault()
+
+      const x = e.touches[0].clientX
+      const y = e.touches[0].clientY
+
+      if (this.touched) this.setAirPosAcrossFingerPosZ(x, y)
+    }))
+
+    canvas.addEventListener('touchend', ((e) => {
+      e.preventDefault()
+
+      this.touched = false
+    }))
+  }
+
+  /**
+   * 玩家射击操作
+   * 射击时机由外部决定
+   */
+  shoot() {
+    const bullet = databus.pool.getItemByClass('bullet', Bullet)
+
+    bullet.init(
+      this.x + this.width / 2 - bullet.width / 2,
+      this.y - 10,
+      10
+    )
+
+    databus.bullets.push(bullet)
+  }
+}

+ 60 - 0
js/runtime/background.js

@@ -0,0 +1,60 @@
+import Sprite from '../base/sprite'
+
+const screenWidth = window.innerWidth
+const screenHeight = window.innerHeight
+
+const BG_IMG_SRC = 'images/bg.jpg'
+const BG_WIDTH = 512
+const BG_HEIGHT = 512
+
+/**
+ * 游戏背景类
+ * 提供update和render函数实现无限滚动的背景功能
+ */
+export default class BackGround extends Sprite {
+  constructor(ctx) {
+    super(BG_IMG_SRC, BG_WIDTH, BG_HEIGHT)
+
+    this.top = 0
+
+    this.render(ctx)
+  }
+
+  update() {
+    this.top += 2
+
+    if (this.top >= screenHeight) this.top = 0
+  }
+
+  /**
+   * 背景图重绘函数
+   * 绘制两张图片,两张图片大小和屏幕一致
+   * 第一张漏出高度为top部分,其余的隐藏在屏幕上面
+   * 第二张补全除了top高度之外的部分,其余的隐藏在屏幕下面
+   */
+  render(ctx) {
+    ctx.drawImage(
+      this.img,
+      0,
+      0,
+      this.width,
+      this.height,
+      0,
+      -screenHeight + this.top,
+      screenWidth,
+      screenHeight
+    )
+
+    ctx.drawImage(
+      this.img,
+      0,
+      0,
+      this.width,
+      this.height,
+      0,
+      this.top,
+      screenWidth,
+      screenHeight
+    )
+  }
+}

+ 62 - 0
js/runtime/gameinfo.js

@@ -0,0 +1,62 @@
+const screenWidth = window.innerWidth
+const screenHeight = window.innerHeight
+
+const atlas = new Image()
+atlas.src = 'images/Common.png'
+
+export default class GameInfo {
+  renderGameScore(ctx, score) {
+    ctx.fillStyle = '#ffffff'
+    ctx.font = '20px Arial'
+
+    ctx.fillText(
+      score,
+      10,
+      30
+    )
+  }
+
+  renderGameOver(ctx, score) {
+    ctx.drawImage(atlas, 0, 0, 119, 108, screenWidth / 2 - 150, screenHeight / 2 - 100, 300, 300)
+
+    ctx.fillStyle = '#ffffff'
+    ctx.font = '20px Arial'
+
+    ctx.fillText(
+      '游戏结束',
+      screenWidth / 2 - 40,
+      screenHeight / 2 - 100 + 50
+    )
+
+    ctx.fillText(
+      `得分: ${score}`,
+      screenWidth / 2 - 40,
+      screenHeight / 2 - 100 + 130
+    )
+
+    ctx.drawImage(
+      atlas,
+      120, 6, 39, 24,
+      screenWidth / 2 - 60,
+      screenHeight / 2 - 100 + 180,
+      120, 40
+    )
+
+    ctx.fillText(
+      '重新开始',
+      screenWidth / 2 - 40,
+      screenHeight / 2 - 100 + 205
+    )
+
+    /**
+     * 重新开始按钮区域
+     * 方便简易判断按钮点击
+     */
+    this.btnArea = {
+      startX: screenWidth / 2 - 40,
+      startY: screenHeight / 2 - 100 + 180,
+      endX: screenWidth / 2 + 50,
+      endY: screenHeight / 2 - 100 + 255
+    }
+  }
+}

+ 38 - 0
js/runtime/music.js

@@ -0,0 +1,38 @@
+let instance
+
+/**
+ * 统一的音效管理器
+ */
+export default class Music {
+  constructor() {
+    if (instance) return instance
+
+    instance = this
+
+    this.bgmAudio = new Audio()
+    this.bgmAudio.loop = true
+    this.bgmAudio.src = 'audio/bgm.mp3'
+
+    this.shootAudio = new Audio()
+    this.shootAudio.src = 'audio/bullet.mp3'
+
+    this.boomAudio = new Audio()
+    this.boomAudio.src = 'audio/boom.mp3'
+
+    this.playBgm()
+  }
+
+  playBgm() {
+    this.bgmAudio.play()
+  }
+
+  playShoot() {
+    this.shootAudio.currentTime = 0
+    this.shootAudio.play()
+  }
+
+  playExplosion() {
+    this.boomAudio.currentTime = 0
+    this.boomAudio.play()
+  }
+}

+ 59 - 0
project.config.json

@@ -0,0 +1,59 @@
+{
+  "description": "项目配置文件。",
+  "setting": {
+    "urlCheck": false,
+    "es6": true,
+    "enhance": false,
+    "postcss": true,
+    "preloadBackgroundData": false,
+    "minified": true,
+    "newFeature": true,
+    "coverView": true,
+    "nodeModules": false,
+    "autoAudits": false,
+    "showShadowRootInWxmlPanel": true,
+    "scopeDataCheck": false,
+    "uglifyFileName": false,
+    "checkInvalidKey": true,
+    "checkSiteMap": true,
+    "uploadWithSourceMap": true,
+    "compileHotReLoad": false,
+    "useMultiFrameRuntime": false,
+    "useApiHook": true,
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "enableEngineNative": false,
+    "bundle": false,
+    "useIsolateContext": true,
+    "useCompilerModule": true,
+    "userConfirmedUseCompilerModuleSwitch": false,
+    "userConfirmedBundleSwitch": false,
+    "packNpmManually": false,
+    "packNpmRelationList": [],
+    "minifyWXSS": true
+  },
+  "compileType": "game",
+  "libVersion": "2.20.1",
+  "appid": "wx5965ee8a9321cbf3",
+  "projectname": "proud77",
+  "simulatorType": "wechat",
+  "simulatorPluginLibVersion": {},
+  "condition": {
+    "search": {
+      "list": []
+    },
+    "conversation": {
+      "list": []
+    },
+    "game": {
+      "currentL": -1,
+      "list": []
+    },
+    "miniprogram": {
+      "list": []
+    }
+  }
+}