在微信小游戏中使用three.js显示3D图形

2019-12-02 15:58:07 浏览数 (1)

年前,微信发布了一个重磅消息:微信小程序的小游戏功能,千呼万唤始出来!

笔者之前从未接触过微信小程序和WebGL的开发,但是却一直有留意相关技术的发展,大概听说原来微信小程序是不支持WebGL 3D技术的。这次借着微信大力推广小游戏,看了一下API文档,发现小游戏是可以使用的WebGL进行开发的。而最近正好又有点时间,就随便搞搞,试试小游戏的效果。因为小游戏“跳一跳”是用three.js所制作的,所以我就选择了three.js所。那么开始吧。

微信小游戏教程地址:教程小游戏

开发环境搭建

下载了最新的微信开发工具,并按照教程建立了示例项目。示例游戏是2D游戏,和我期望的有点距离,找遍网络没有一个三维的微信小游戏示例,看来只能自己试试了。

新建了一个小程序项目,并且按照教程添加了game.js和game.json,但是程序一直报错:

后来发现是调试基础库没有默认为“游戏”,按照截图操作之后就正常了:

引入three.js所

到github上下载three.js所最新版本,笔者当时下载的是R89,用最新的应该也没有问题。前文介绍过,笔者并没有开发小程序的经验,所以一上来就在game.js里直接引用三人。 JS:

代码语言:javascript复制
 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:

代码语言:javascript复制
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,使用这句正则表达式明显不能匹配到。我们稍微改一下:

代码语言:javascript复制
	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 加载远程文件等等,但是我们认为原生方式还是比较好的,至少有以下几个优点:

  1. 保持原始格式,便于后续修改模型;
  2. 最大限度保证代码兼容性,便于移植;
  3. 由于微信小程序/小游戏包体限制,将素材放到服务器上再载入进来比较合理。

于是,我们的载入代码如下(非完整代码,仅为代码示例):

代码语言:javascript复制
// 模型载入处理
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)
  }
);

可能会遇到如下的错误:

代码语言:javascript复制
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

代码语言:javascript复制
{
  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 提供的一个非常便于使用的让摄像机围绕目标对象旋转的交互功能,最简化的时候一行代码就可以搞定了,于是就将其加入到项目文件中。

我们直接将其引入:

代码语言:javascript复制
require('libs/OrbitControls')

但是运行发现错误:

代码语言:javascript复制
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 第一行粗暴的添加这行代码引入即可:

代码语言:javascript复制
var THREE = require('three.min');

注: 因为我没有对 three.js 做任何修改,所以直接引入了 minified 版本,如果您没有使用该版本,去掉 .min 即可。

然后代码中加入这一行就可以用了:

代码语言:javascript复制
controls = new THREE.OrbitControls(camera);

至此没有出现什么问题,但是当想要交互的时候,一有动作就会发现屏幕被清空了。直觉告诉我是摄像机的座标或者旋转角度计算错了,经过跟踪,果然如此,在触摸屏幕并移动的时候,以下代码会出现问题:

代码语言:javascript复制
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 ,造成摄像机无法定位。我临时进行了如下修改:

代码语言:javascript复制
rotateLeft( 2 * Math.PI * rotateDelta.x / window.innerWidth * scope.rotateSpeed );
rotateUp( 2 * Math.PI * rotateDelta.y / window.innerHeight * scope.rotateSpeed );

这只是临时的修改 ,后面有时间会尝试合理一些的解决方案。

不过呢,经过这样的修改以后,已经可以正常的通过手指对摄像机进行旋转,也可以用双指进行缩放了。

0 人点赞