由于微信小游戏开发/运行环境的种种限制,使其UI实现很麻烦。官方出过一个教程《微信3D小游戏下的HUD绘制的经验分享》,里面对数学原理、三维理论、绘制分析讲解偏重,但对具体实现操作偏少,从后面留言看,对新手不友好吧。如果没有基于opengl/webgl实现过一次(或完整阅读过类似实现代码/ 伪代码),要依据该教程实现自己的HUD,还是有很多路子要折腾一番的。
业余时间利用three.js折腾的小游戏里,也实现了一小套UI控件,之前写的《球球作战之迷宫系统》文章后面也有网友咨询它的实现方法,当时简单回复过原理。下面把基于我业余经验,实现的HUD及用同样原理实现的UI控件完整地分享出来,与官方教程差异在:偏重实操,提供可直接使用的控件源代码。控件代码还有待优化/完善,希望能起到抛砖引玉的作用。
实现过程:
1,单独创建一个scene和一个正交投影相机,代码如下:
this.hudScene = new THREE.Scene();
this.orthCamera = new THREE.OrthographicCamera(window.innerWidth / - 2, window.innerWidth / 2,window.innerHeight / 2, window.innerHeight / - 2, 0, 30 );
this.orthCamera.position.set( 0, 0, 3 );
我们将在这个场景中创建HUD(UI元素);该相机的投影中心正对屏幕中心,设置相机位置使其镜头面平行于手机屏幕,便于后面设置HUD位置(UI布局)。
2,在上面scene中创建mesh对象(plane或box),将文字/图片等绘制或纹理依附其上(根据需要控制好透明区域),代码如下:
//创建画布用于绘制HUD信息(UI图片/文字)
this.labelCanvas = document.createElement(‘canvas’);
this.labelContext = this.labelCanvas.getContext(‘2d’);
this.labelContext.font = this.opt.font || ‘24px Arial’;
this.labelContext.fillStyle = this.opt.style || ‘rgba( 255, 0, 0, 1 )’;
// 以上面的HUD文字(UI)为基础创建纹理
this.labelTexture = new THREE.Texture( this.labelCanvas );
this.labelTexture.magFilter = THREE.LinearFilter;
this.labelTexture.minFilter = THREE.LinearFilter;
this.labelTexture.needsUpdate = true;
// 创建 3D mesh 对象, 添加到 hudScene 空间
let labelMaterial = new THREE.MeshBasicMaterial( { map: this.labelTexture, transparent: true, opacity: this.opt.opacity || 0.80, color: 0xffffff} );
let labelPlane = new THREE.PlaneBufferGeometry( this.labelCanvas.width, this.labelCanvas.height );
this.labelMesh = new THREE.Mesh( labelPlane, labelMaterial );
this.hudScene.add( this.labelMesh );
3,使用该场景、相机,在主renderer上进行渲染:
renderer.render( this.hudScene, this.orthCamera );
以上是绘制的实现,对于UI控件还需要支持触控事件。实现过程如下:
1,用微信API获取屏幕坐标:
在wx.onTouchStart或wx.onTouchEnd的回调中获取touch信息,主要是触摸的屏幕x,y坐标;
for (let i = 0; i < res.touches.length; i++) {
if (res.touches[i].clientX === undefined) res.touches[i].clientX = touches[i].screenX;
if (res.touches[i].clientY === undefined) res.touches[i].clientY = touches[i].screenY;
}
2,将屏幕二维坐标转换到HUD场景的三维空间坐标:
this.mouse = new THREE.Vector2();
this.mouse.x = ( touch.clientX / window.innerWidth ) * 2 - 1;
this.mouse.y = - ( touch.clientY / window.innerHeight ) * 2 + 1;
触控点在三维空间的 z 坐标使用0或相机的z值
3,从该三维空间坐标点发射射线判断是否击中HUD(UI元素):
射线从触控点转换出的场景中的三维点开始,平行于Z轴,向投影面而去,代码如下.
this.raycaster = new THREE.Raycaster();
this.raycaster.setFromCamera(this.mouse, this. orthCamera );
let intersects = this.raycaster.intersectObjects( this.hudScene.children );
if ( intersects.length > 0 && intersects[0].object && this.opt.enableHit) {
for(let i = 0; i < intersects.length; i++){
let hittedObj = intersects[i].object;
if (hittedObj && hittedObj.id === this.id && typeof this.opt.hittedCB === ‘function’) {
this.opt.hittedCB(touch, this.mouse, hittedObj, self);
return true;
}
}
}
return false;
以上是实现的核心代码部分。要包装成可用的控件,还需配合wx的API进行调用,及做好UI资源的利用,布局管理(不同分辨率下自动适配,涉及到坐标动态转换),API设计等。github里上传了我在用的实现,还比较简陋,不过已经可用,控件的图片资源使用的是微信官方小游戏demo里的图片,github地址:https://github.com/aceway/wx3DUIComponent
使用说明:
1,创建管理控件:
this.components = new THREE.UIComponent({});
2,创建按钮:
this.optHud = {};
this.tipsHUD = self.components.addButton(optHud);
this.showHUD = true;
// 需要时更新文字,字体等…
this.tipsHUD.update(“Game Over!”);
let optExit = {};
Object.assign(optExit , CFG.UI.BTN_EXIT);
optExit.hittedCB = (touch, mouse, obj, theBtn)=>{
wx.exitMiniProgram({});
};
this.btnExit = this.components.addButton(optExit);
3,触控事件:
theGame = this;
wx.onTouchEnd((res)=>{
if (theGame.components.hitButtonTest(res.changedTouches)){
return;
}
…
});
就这些,欢迎关注微信公众号: HTML53D
问下3d下摇杆怎么实现?
1, 从 wx.onTouchStart 获取起始点坐标(基于屏幕的坐标);
2,在wx.onTouchMove中获取移动中的坐标(基于屏幕的坐标);
然后此处有两种处理方式A,基于新获取的坐标,计算相对于1中的坐标偏移,从而判断摇杆偏向;B,用移动中后一个新点,相对于前一移动点的偏移来判断摇杆的偏移;选用哪种方式可以根据自己玩法需要来定。
3,在wx.onTouchEnd中清除记录的相关点变量。