前言
这是一次利用 three.js
开发微信小游戏的尝试,并不能算作是教程,只能算是一篇笔记吧。
微信 WeChat 6.6.1 开始引入了微信小游戏,初期上线了一批质量相当不错的小游戏。我在查阅各处的文章时候,发现其中有几款是基于 three.js 开发的,目前火爆朋友圈的《跳一跳》就是其中之一。这引起了我的注意,想起几年前也做过不少 WebGL 的尝试,于是禁不住想要弄到微信小游戏平台上试试。
准备工作
- 最新版本的 three.js;
- 首先应该具有一定的
three.js
开发经验,有之前写过的简单演示代码; - 最新版本的“微信开发者工具”。
另外补充一点:需要足够的耐心,微信开发者工具问题多多,编辑器也各种问题,我是使用 WebStorm 来编写代码,您不妨也试试。遇到奇怪的问题的时候,可能需要多启动几次开发者工具,非常令人恼火。
找一个之前的 WebGL 演示
我随便找了一个很久以前做过的演示代码,如下:
可以拖动旋转,滚轮缩放
创建微信小游戏项目
好啦,现在可以进入开发者工具尝试移植了。
首先,创建一个标准的小游戏项目。
选择“小游戏”项目进行创建,并选择一个空的目录作为项目目录
点击“确定”就会打开一个模板项目,是一个射击游戏,大致的结构如下:
代码语言:javascript复制├── game.js
├── game.json
├── project.config.json
├── README.md
├── js
| ├── databus.js
| ├── main.js
| ├── base
| | ├── animation.js
| | └── ...
| ├── libs
| | ├── symbol.js
| | └── weapp-adapter.js
| ├── npc
| | └── enemy.js
| ├── player
| | ├── bullet.js
| | └── ...
| └── runtime
| ├── background.js
| └── ...
├── audio
| ├── bgm.mp3
| └── ...
└── images
├── Common.png
└── ...
基于这个模板,我们可以将不需要的内容暂时删除,以便跑我们自己的项目。其中,射击游戏相关的内容都可以移除了,但是我们要保留一些关键的代码和配置文件,清理以后,大概会是这个样子:
代码语言:javascript复制├── game.js
├── game.json
├── project.config.json
├── README.md
├── js
| ├── main.js
| └── libs
| ├── symbol.js
| └── weapp-adapter.js
└── images
- 其中,
images
目录里面放置我们需要使用到的图片,在这个例子中,我放了粒子图片和 indienova Logo 图片在里面; - 将我们必然会用到的
three.js
(使用three.min.js
也可以,但是后面需要修改这个文件,所以建议使用没有 minified 的版本)放入js/libs
; - 如果有其它资源,比如音频文件什么的,也可以自建目录放进去;
- 然后需要修改一下配置文件,简单的说,只要修改
project.config.json
中的projectname
即可; - 保留
game.js
不变,这个是入口,会引入main.js
并执行; - 将
main.js
清空,我们的新代码会在这里完成。
我们还保留了两个 js 文件,libs
中的 symbol.js
和 weapp-adapter.js
。这里需要注意的是 weapp-adapter.js
很重要,官方解释如下:
小游戏的运行环境在 iOS 上是 JavaScriptCore,在 Android 上是 V8,都是没有 BOM 和 DOM 的运行环境,没有全局的 document 和 window 对象。因此当你希望使用 DOM API 来创建 Canvas 和 Image 等元素的时候,会引发错误。 ………… 这些使用 wx API 模拟 BOM 和 DOM 的代码组成的库称之为 Adapter。顾名思义,这是对基于浏览器环境的游戏引擎在小游戏运行环境下的一层适配层,使游戏引擎在调用 DOM API 和访问 DOM 属性时不会产生错误。Adapter 是一个抽象的代码层,并不特指某一个适配小游戏的第三方库,每位开发者都可以根据自己的项目需要实现相应的 Adapter。官方实现了一个 Adapter 名为 weapp-adapter, 并提供了完整的源码,供开发者使用和参考。 ………… 除此之外 weapp-adapter 还模拟了以下对象和方法:
- document.createElement
- canvas.addEventListener
- localStorage
- Audio
- Image
- WebSocket
- XMLHttpRequest
- 等等...
需要强调的是,weapp-adapter 对浏览器环境的模拟远不完整的,仅仅只针对游戏引擎可能访问的属性和调用的方法进行了模拟,也不保证所有游戏引擎都能通过 weapp-adapter 顺利无缝接入小游戏。直接将 weapp-adapter 提供给开发者,更多地是作为参考,开发者可以根据需要在 weapp-adapter 的基础上进行扩展,以适配自己项目使用的游戏引擎。 原文请参阅:这里。
可见,微信团队已经为我们开发游戏做好了一些准备,比如露出的 canvas
,我们到时候直接拿来使用就是。
将之前的代码移植到项目中
开始将之前写好的代码移植过来,注意由于要使用 ES 6(EMCAScript 6)标准,所以之前的代码可能要做相应的调整,不过大部分都是语法的调整,有一些方法的使用需要增加 bind(this)
,具体的还请大家参阅我们提供的链接地址,就算不看书,随便尝试一下也能很快有所了解。
新的 main.js
的主体代码看起来是这样:
import * as THREE from 'libs/three.js'
let ctx = canvas.getContext('webgl')
let scene
let renderer
// ... 其它变量/常量 ...
/**
* 游戏主函数
*/
export default class Main {
constructor() {
this.start()
}
start() {
// 初始化
scene = new THREE.Scene()
renderer = new THREE.WebGLRenderer({ context: ctx })
//... 其它代码块 ...
// 开始循环
this.loop()
}
// UPDATE 更新
update() {
// ... 数据更新代码块 ...
}
// RENDER 渲染
render() {
// ... 渲染代码块 ...
}
// 实现游戏帧循环
loop() {
this.update()
this.render()
window.requestAnimationFrame( this.loop.bind(this), canvas )
}
}
一些要点:
由于微信已经为我们准备好了的 canvas
,所以我们无需自己再进行创建,只需要取得的 canvas
的 context
就可以了,这里我们使用的不是 2d
,而是 webgl
。
let ctx = canvas.getContext('webgl')
然后我们在创建 WebGLRenderer
的时候,直接使用这个 context
就可以了。
renderer = new THREE.WebGLRenderer({ context: ctx })
这两点做到了,基本上就不会有太大问题了。
一切正常的话,开发者工具里面应该就能跑得起来了。
能跑起来并不算完,还需要真机测试
真机调试
真机调试不复杂,微信开发者工具提供了真机预览功能,只要点一下“预览”,就会上传代码,并生成二维码供测试。
开发者用微信扫描这个二维码,就可以打开测试。
如果您没有修改过 three.js 源文件,那么很有可能只看到一个黑屏。
还好,微信小游戏提供了一个调试开关,我们可以选择打开调试:
然后再次扫码进入,就可以查看调试信息了:
可以清晰的得知,createElementNS
不被支持,那么我们可以将所有 createElementNS
改为 createElement
:(注意,这只是暂时解决方案,此方法并不是理想方案,我们以后应该会有更好的解决方法。通过修改 adapter 应该就可以减少 three.js 源文件的修改。)
document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
// 改为 ==>
document.createElement( 'canvas' );
不止这一处,而且也不止是针对 canvas 的创建,还有针对 img 的创建,可以都修改掉。
另外由于真机上跑的是 OpenGL ES 1.x/2.x/3.x,所以还有一句要处理:
代码语言:javascript复制var version = parseFloat( /^WebGL ([0-9])/.exec(gl.getParameter(gl.VERSION))[ 1 ] );
// 改为 ==>
var version = parseFloat( /^(WebGL|OpenGL ES) ([0-9])/.exec(gl.getParameter(gl.VERSION))[ 1 ] );
这样改过后,重新再尝试一下看,是不是已经可以了?
2018-01-08 更新
临时解决方案使用后,我后来尝试修改 weapp-adapter.js
,就不需要再对 three.js
进行修改了:
createElementNS: function createElementNS(nameSpace, tagName) {
return this.createElement(tagName)
}
方法很简单:我们在 document
的定义中添加 createElementNS
,然后忽略掉 NameSpace 即可。
原文链接:https://indienova.com/indie-game-development/run-threejs-on-wechat-game-platform/