手机版

微信游戏的第一次体验

时间:2021-12-11 来源:互联网 编辑:宝哥软件园 浏览:

本文旨在通过分析一款飞机大战小游戏的官方源代码,说明如何开发一款小游戏。

1.前言

前天,一个小小的跳跳游戏席卷了朋友圈,这也代表了微信小程序有了承载游戏的功能(应该是在这方面发展了很久,这才是应有的形式)。作为一个前端er,我的大刀已经饿了,赶紧来下一波官方微信开发工具,体验一下如何开发一款小游戏。

微信小游戏初体验(图1)

很高兴看到大家可以直接点击小游戏体验,官方也有一个示例源代码,是《战机大战》源代码的简单版本,直接点击模拟器就可以看到效果。

微信小游戏初体验(图2)

2.源码分析

(还是原来的飞机游戏!)通过阅读这个源代码,我们可以知道如何开发小游戏。废话少说,直奔主题。我们先来分析一波源代码的整体结构。

微信小游戏初体验(图3)。/js以下是官方示例中js文件的具体功能。

95000 base/玩家类-runtime -background.js//background类-Gameinfo.js//used显示分数和结算界面-music.js//global声音经理-databus.js//control

接下来,我将分析我认为的主要文件和结构。我不会分析每一行代码。如果你感兴趣,可以自己阅读官方源代码。每份文件后面都会有几个我认为很重要的点。

game.js

导入”。/js/libs/weapp-适配器“导入”。/js/libs/符号“从导入主文件”。/js/main' new main()小程序启动会调用game.js,其中导入了官方游戏提供的适配器。用于注入canvas和模拟DOM和BOM(这个文件后面会详细介绍),可以在https://mp.weixin.qq.com/debu.下载源代码修改适合自己的版本,通过webpack打包为自己使用。当然,目前对我们来说已经足够了。导入符号的Polyfill主要用于模拟ES6类的私有变量。导入Main类并实例化Main,所以我们将注意力转向main.js

Main.js

从'导入播放器。/player/index“导入未知自”。/NPC/未知'导入回来。

Ground from './runtime/background'import GameInfo from './runtime/gameinfo'import Music from './runtime/music'import DataBus from './databus'let ctx = canvas.getContext('2d')let databus = new DataBus()/** * 游戏主函数 */export default class Main { constructor() { 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() window.requestAnimationFrame( this.loop.bind(this), canvas ) } /** * 随着帧数变化的敌机生成逻辑 * 帧数取模定义成生成的频率 */ enemyGenerate() { if ( databus.frame % 30 === 0 ) { let enemy = databus.pool.getItemByClass('enemy', Enemy) enemy.init(6) databus.enemys.push(enemy) } } // 全局碰撞检测 collisionDetection() { let that = this databus.bullets.forEach((bullet) => { for ( let i = 0, il = databus.enemys.length; i < il;i++ ) { let 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++ ) { let enemy = databus.enemys[i] if ( this.player.isCollideWith(enemy) ) { databus.gameOver = true break } } } //游戏结束后的触摸事件处理逻辑 touchEventHandler(e) { e.preventDefault() let x = e.touches[0].clientX let y = e.touches[0].clientY let 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) } // 游戏逻辑更新主函数 update() { this.bg.update() databus.bullets .concat(databus.enemys) .forEach((item) => { item.update() }) this.enemyGenerate() this.collisionDetection() } // 实现游戏帧循环 loop() { databus.frame++ this.update() this.render() if ( databus.frame % 20 === 0 ) { this.player.shoot() this.music.playShoot() } // 游戏结束停止帧循环 if ( databus.gameOver ) { this.gameinfo.renderGameOver(ctx, databus.score) this.touchHandler = this.touchEventHandler.bind(this) canvas.addEventListener('touchstart', this.touchHandler) return } window.requestAnimationFrame( this.loop.bind(this), canvas ) }}
  1. 导入了创建游戏需要的我放飞机,敌方飞机,背景,游戏信息,音乐,游戏全局数据类,并获取了canvas的上下文(看到这是不是有一个疑惑,canvas到底是从哪里定义?先带着这个问题最后再说),创建了一个全局数据实例(后面会提到)。
  2. 创建Main的实例自然会调用构造方法,在构造方法中调用restart函数,进行了游戏的初始化并进行循环刷帧(requestAnimationFrame看起来是不是很亲切)。
  3. loop函数中我们可以看到主要调用了update, render方法,并设置了player发射子弹的时间,对游戏是否结束进行判断,最后接着刷帧。
  4. update方法会调用各个场景内对象的update方法来更新他们的位置以及其他信息。
  5. render方法会调用各个场景内对象的render方法来将他们绘制到canvas中。

Main内结构清晰,主要理解整个流程就是调用requestAnimationFrame来不停地刷帧更新位置信息推动所有对象运动,每个对象在每一帧都有新的位置,连起来就是动画了。分清位置的更新与对象的绘制是关键。

databus.js

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) {    let temp = this.enemys.shift()    temp.visible = false    this.pool.recover('enemy', enemy)  }  /**   * 回收子弹,进入对象池   * 此后不进入帧循环   */  removeBullets(bullet) {    let temp = this.bullets.shift()    temp.visible = false    this.pool.recover('bullet', bullet)  }}
  1. 我们可以看出,databus是一个单例对象,不论在其他代码中new多少次,都是返回的同一个实例,符合我们的期望。
  2. reset定义了所需要的数据源并初始化
  3. 通过一个对象池的概念,控制当前页面对象的数量,避免使用js原有的垃圾处理机制,而是通过对象池来复用已经创建的对象,算是一个性能优化。
  4. frame属性主要是用来刷帧的时候用来控制子弹的发射与敌机的出现时间。

sprite.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  )  }}
  1. 作为所有场景对象的基类,定义了所有精灵对象基本有的信息(位置,图片,是否可见)
  2. 定义了两种能力,检测碰撞与将自己绘制在canvas上

可以看出画图主要是用的canvas里的drawImage方法,也是我们自行开发小游戏以后会用到的方法。包括background,player等类都会继承自精灵类,并且会添加自己的update方法来暴露更新自己位置信息的接口。enermy还会包装一层爆炸动画的封装,思路大同小异,就不在多赘述了。

3.结论

  1. 我们发现小游戏的开发与我们使用canvas进行h5小游戏的开发并没有什么太大的区别,无论从绘图的api还是事件的api都十分相似,还可以用window对象,这主要归功于官方提供的webapp-adapter.js,该js会注入window对象并提供相应的canvas全局变量,也是文章中提到为什么在main.js里找不到canvas变量在哪里定义的原因了。所以我们可以开开心心地使用canvas来开发小游戏了!!!
  2. 官方还说了一句,可以不引入webapp-adapter.js来开发小游戏,(https://mp.weixin.qq.com/debu...)这是小游戏的api文档(当时找了很久)适配器的源码写得也很清晰,可以一读来了解一些,其中也有很多官方写的TODO的事情,还并不十分完善,如果想要快速移植已有的h5游戏代码使用适配器是很有效的。如果想直接开发小游戏根据api文档直接来开发也是很有效的方法,毕竟引入一层适配器还是会有一定的开销。

tips: 读一读适配器源码也有利于了解如何开发小程序(例如事件绑定之类的操作)

4.结语

小程序终于可以来做小游戏了,感觉还是休闲类的游戏会占主导地位,前端大大可以迎接新的战场啦哈哈哈~~~(接下来会去掉适配器用原生api改写官方demo)

版权声明:微信游戏的第一次体验是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。