年前,微信发布了一个重磅消息:微信小程序的小游戏功能,千呼万唤始出来!
笔者之前从未接触过微信小程序和WebGL的开发,但是却一直有留意相关技术的发展,大概听说原来微信小程序是不支持WebGL 3D技术的。这次借着微信大力推广小游戏,看了一下API文档,发现小游戏是可以使用的WebGL进行开发的。而最近正好又有点时间,就随便搞搞,试试小游戏的效果。因为小游戏“跳一跳”是用three.js所制作的,所以我就选择了three.js所。那么开始吧。
微信小游戏教程地址:教程小游戏
开发环境搭建
下载了最新的微信开发工具,并按照教程建立了示例项目。示例游戏是2D游戏,和我期望的有点距离,找遍网络没有一个三维的微信小游戏示例,看来只能自己试试了。
新建了一个小程序项目,并且按照教程添加了game.js和game.json,但是程序一直报错:
后来发现是调试基础库没有默认为“游戏”,按照截图操作之后就正常了:
引入three.js所
到github上下载three.js所最新版本,笔者当时下载的是R89,用最新的应该也没有问题。前文介绍过,笔者并没有开发小程序的经验,所以一上来就在game.js里直接引用三人。 JS:
import './js/libs/weapp-adapter.js'
import './js/libs/symbol.js'
import './js/three/three.js'
var scene = new THREE.Scene();
程序直接报错:
耐心看了小程序开发的说明,再看了three.js所的写法,重新修改引用方式,game.js:
import './js/libs/weapp-adapter.js'
import './js/libs/symbol.js'
var THREE = require('./js/three/three.js');
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var context = canvas.getContext('webgl');
var renderer = new THREE.WebGLRenderer(context);
renderer.setSize(window.innerWidth, window.innerHeight);
canvas.appendChild(renderer.domElement);
var geometry = new THREE.CubeGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
function render() {
requestAnimationFrame(render);
cube.rotation.x += 0.1;
cube.rotation.y += 0.1;
renderer.render(scene, camera);
}
render();
一个旋转的立方体就在开发环境下显示出来了!
到目前为止,一切算是比较顺利,于是马上使用预览功能上传到手机微信进行测试:
这个时候发生问题了:手机微信看不到我的立方体!
还好微信环境下有提供控制台,笔者通过记录日志,最终定位了错误:
原来是three.js所里面有一段代码:
是判断当前环境WebGL版本的,而微信环境下是opengl es3.2,使用这句正则表达式明显不能匹配到。我们稍微改一下:
var version = parseFloat( /^(WebGL|OpenGL ES)\ ([0-9])/.exec( gl.getParameter( gl.VERSION ) )[ 1 ] );
再次发布预览试试看!
成功!
网友eastecho 写了一篇文章,更加详细的阐述了这方面的内容:https://indienova.com/indie-game-development/run-threejs-on-wechat-game-platform/
在微信小游戏中载入模型
接下来再建立我们的微信小游戏项目,如果您不是很熟悉要做哪些准备工作,可以参考前文:《 利用 three.js 开发微信小游戏的尝试》。不过我们这次使用的 weapp-adapter.js
会有所不同,是基于 @大城小胖 修改过的,可以在 这里找到 。
接下来我们就尝试着用 three.js
自己的 JSONLoader
来载入。其实有了模型的 json
文件以后,载入方式就可以很多样了,比如可以 require
进去包了壳的 js
文件,或者直接使用 wx.request
加载远程文件等等,但是我们认为原生方式还是比较好的,至少有以下几个优点:
- 保持原始格式,便于后续修改模型;
- 最大限度保证代码兼容性,便于移植;
- 由于微信小程序/小游戏包体限制,将素材放到服务器上再载入进来比较合理。
于是,我们的载入代码如下(非完整代码,仅为代码示例):
// 模型载入处理
let modelLoader = new THREE.JSONLoader()
modelLoader.load(modelURL,
function(geometry, materials){
mesh = new THREE.Mesh(geometry, materials[0])
scene.add(mesh)
console.log('模型载入完成')
},
// onProgress 回调
function (xhr) {
console.log( (xhr.loaded / xhr.total * 100) + '% 已载入' )
},
// onError 回调
function(err) {
console.log('载入出错', err.target.status)
}
);
可能会遇到如下的错误:
TypeError: n.addEventListener is not a function
at Ia.load (three.min.js:638)
at ke.load (three.min.js:723)
at new Main (main.js? [sm]:36)
at game.js? [sm]:6
at require (WAGame.js:11)
at gamePage.html:84
不过对历经过实战的我们来说,应该马上会了解到,这是因为微信给出的 XMLHttpRequest
缺少 addEventListener
造成的。我们可以自行在 weapp-adapter.js
中添加它。
在 weapp-adapter.js
中找到 XMLHttpRequest
的定义部分,为其增加一个新的 key
:
{
key: 'addEventListener',
value: function addEventListener(type, listener) {
if (typeof listener === 'function') {
let event = { target: this }
let that = this
this['on' + type] = function () {
listener.call(that, event)
}
}
}
}
再跑一次,应该就可以正常载入了,开发环境和真机均无问题。
开发环境和真机截屏
至此,模型载入就实现了。
实现交互(临时方案)
本来是准备就此先罢手了,不过看到群中有人在尝试使用 OrbitControls
来实现简单交互,就顺便也试验了一下。OrbitControls
是 three.js
提供的一个非常便于使用的让摄像机围绕目标对象旋转的交互功能,最简化的时候一行代码就可以搞定了,于是就将其加入到项目文件中。
我们直接将其引入:
require('libs/OrbitControls')
但是运行发现错误:
ReferenceError: THREE is not defined
at OrbitControls.js? [sm]:18
at require (WAGame.js:11)
at WAGame.js:11
at main.js? [sm]:2
at require (WAGame.js:11)
at WAGame.js:11
at game.js? [sm]:4
at require (WAGame.js:11)
at gamePage.html:85
临时处理方法只要在 OrbitControls.js
第一行粗暴的添加这行代码引入即可:
var THREE = require('three.min');
注: 因为我没有对 three.js
做任何修改,所以直接引入了 minified 版本,如果您没有使用该版本,去掉 .min
即可。
然后代码中加入这一行就可以用了:
controls = new THREE.OrbitControls(camera);
至此没有出现什么问题,但是当想要交互的时候,一有动作就会发现屏幕被清空了。直觉告诉我是摄像机的座标或者旋转角度计算错了,经过跟踪,果然如此,在触摸屏幕并移动的时候,以下代码会出现问题:
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
// rotating across whole screen goes 360 degrees around
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
// rotating up and down along whole screen attempts to go 360, but limited to 180
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
element.clientWidth
不存在,因此得到的值会是 NaN
,造成摄像机无法定位。我临时进行了如下修改:
rotateLeft( 2 * Math.PI * rotateDelta.x / window.innerWidth * scope.rotateSpeed );
rotateUp( 2 * Math.PI * rotateDelta.y / window.innerHeight * scope.rotateSpeed );
这只是临时的修改 ,后面有时间会尝试合理一些的解决方案。
不过呢,经过这样的修改以后,已经可以正常的通过手指对摄像机进行旋转,也可以用双指进行缩放了。
结束语