虽然有了交互,但总不能让交互都是这种弹窗吧。很多时候我们会让交互和UI元素互相影响,但目前xr-frame尚未支持和微信小程序的UI元素混写(会在未来版本支持),但我们可以使用同层方案,而同层方案,就必然涉及到组件通信了。
xr-frame组件和父级的通信与传统组件基本一致,这里让我们用微信小程序的UI元素实现一下HUD。这里可能会有一些3D变换的知识,但不要紧,只是调用接口而已。
首先,让我们修改组件的wxml,为场景添加tick事件,并且为模型和相机都加上id方便索引。
<xr-scene bind:ready="handleReady" bind:tick="handleTick">
......
<xr-gltf
node-id="damage-helmet" model="damage-helmet"
id="helmet" mesh-shape bind:touch-shape="handleTouchModel"
/>
<xr-gltf
model="miku" position="-0.15 0.75 0" scale="0.07 0.07 0.07" rotation="0 180 0" anim-autoplay
id="miku" cube-shape="autoFit:true" shape-gizmo bind:touch-shape="handleTouchModel"
/>
<xr-camera
id="camera" position="0 1.5 4" target="damage-helmet" background="skybox"
clear-color="0.4 0.8 0.6 1" camera-orbit-control
/>
</xr-scene>
之后在组件的脚本中处理事件,编写逻辑:
handleReady: function ({detail}) {
this.scene = detail.value;
const xrFrameSystem = wx.getXrFrameSystem();
this.camera = this.scene.getElementById('camera').getComponent(xrFrameSystem.Camera);
this.helmet = {el: this.scene.getElementById('helmet'), color: 'rgba(44, 44, 44, 0.5)'};
this.miku = {el: this.scene.getElementById('miku'), color: 'rgba(44, 44, 44, 0.5)'};
this.tmpV3 = new (xrFrameSystem.Vector3)();
},
handleAssetsLoaded: function ({detail}) {
this.triggerEvent('assetsLoaded', detail.value);
},
handleTick: function({detail}) {
this.helmet && this.triggerEvent('syncPositions', [
this.getScreenPosition(this.helmet),
this.getScreenPosition(this.miku)
]);
},
handleTouchModel: function ({detail}) {
const {target} = detail.value;
this[target.id].color = `rgba(${Math.random()*255}, ${Math.random()*255}, ${Math.random()*255}, 0.5)`;
},
getScreenPosition: function(value) {
const {el, color} = value;
const xrFrameSystem = wx.getXrFrameSystem();
this.tmpV3.set(el.getComponent(xrFrameSystem.Transform).worldPosition);
const clipPos = this.camera.convertWorldPositionToClip(this.tmpV3);
const {frameWidth, frameHeight} = this.scene;
return [((clipPos.x + 1) / 2) * frameWidth, (1 - (clipPos.y + 1) / 2) * frameHeight, color, el.id];
}
这里我们在ready事件中通过id索引获取了需要的实例并存了下来,然后在每帧的tick事件中实时获取物体的世界坐标,将其转换为屏幕的位置,并且还加上了在用户点击时改变颜色color的效果。在最后,我们通过this.triggerEvent,从组件向页面发起了通信,一个是资源加载完成的事件assetsLoaded,一个是坐标更新的事件syncPositions。让我们看看在场景的脚本中是如何处理这些事件的:
data: {
width: 300, height: 300,
renderWidth: 300, renderHeight: 300,
loaded: false,
positions: [[0, 0, 'rgba(44, 44, 44, 0.5)', ''], [0, 0, 'rgba(44, 44, 44, 0.5)', '']],
},
handleLoaded: function({detail}) {
this.setData({loaded: true});
},
handleSyncPositions: function({detail}) {
this.setData({positions: detail});
},
可见只是简单地接受了事件,然后将其设置为data而已,那么这个data用在哪里呢,来看看页面的wxml:
<view>
<xr-start
disable-scroll
id="main-frame"
width="{{renderWidth}}"
height="{{renderHeight}}"
style="width:{{width}}px;height:{{height}}px;"
bind:assetsLoaded="handleLoaded"
bind:syncPositions="handleSyncPositions"
/>
<block wx:if="{{loaded}}" wx:for="{{positions}}" wx:for-item="pos" wx:key="*this">
<view style="display: block; position: absolute;left: {{pos[0]}}px;top: {{pos[1]}}px;background: {{pos[2]}};transform: translate(-50%, -50%);">
<view style="text-align: center;color: white;font-size: 24px;padding: 8px;">{{pos[3]}}</view>
</view>
</block>
</view>
也很简单,就是在xr-start组件上加上了事件的绑定,然后下面多了一些UI,在模型加载完毕后显示,并按照位置和颜色跟随模型移动,这可以认为是基于DOM的HUD。整个完成了,用户点击物体,会让这些HUD变色,效果如下:
注意这里的左侧效果截图是真机截图P上去的,因为工具暂不支持同层渲染!
