粒子发射器

构建粒子发射器

粒子系统目前内置了不同形态的发射器类型,通过对相关参数的调整,满足开发者所需要的粒子效果。

目前支持以下发射器类型:

  • PointShape 点状发射器
  • SphereShape 球形发射器
  • BoxShape 箱形发射器
  • ConeShape 锥形发射器
  • CircleShape 圆形发射器

通过emitter-type指定发射器类型。emitter-props可以配置发射器参数,示例如下:

<xr-particle capacity="20" emit-rate="10" life-time="1.5 2" speed="0.4 0.8" size="0.3 0.5"
emitter-type="SphereShape" emitter-props="radius:1,randomizeDirection:0"></xr-particle>

不同发射器可以指定的相关参数如下:

BoxShapeEmitter 箱形发射器

参数名称 类型 备注 默认值
direction Vector3 粒子初始发射方向左区间 (0, 1.0, 0)
direction2 Vector3 粒子初始发射方向右区间 (0, 1.0, 0)
minEmitBox Vector3 粒子初始位置左区间 (-0.5, -0.5, -0.5)
maxEmitBox Vector3 粒子初始位置右区间 (0.5, 0.5, 0.5)

CircleShapeEmitter 圆形发射器

参数名称 类型 备注 默认值
radius number 粒子随机生成圆形区域的半径 1
radiusRange number 粒子在圆形区域内的覆盖范围 [0-1] 0
direction Vector3 粒子初始发射方向左区间 (0, 1.0, 0)
direction2 Vector3 粒子初始发射方向右区间 (0, 1.0, 0)
arc number 规定粒子生成的扇形区域角度大小 [0-360] 360

ConeShapeEmitter 锥形发射器

参数名称 类型 备注 默认值
radius number 粒子随机生成锥型区域的半径 3
radiusRange number 粒子在锥型区域内的覆盖范围 [0-1] 0
heightRange number 粒子在高度方向上的覆盖范围 [0-1] 1
arc number 规定粒子生成的扇形区域角度大小 [0-360] 360
randomizeDirection number 粒子发射方向的扰动程度 [0-1] 0

PointShapeEmitter 点状发射器

参数名称 类型 备注 默认值
direction Vector3 粒子初始发射方向左区间 (0, 1.0, 0)
direction2 Vector3 粒子初始发射方向右区间 (0, 1.0, 0)

SphereShapeEmitter 球形发射器

参数名称 类型 备注 默认值
radius number 粒子随机生成球型区域的半径 3
radiusRange number 粒子在球型区域内的覆盖范围 [0-1] 0
arc number 规定粒子生成的扇形区域角度大小 [0-360] 360
randomizeDirection number 粒子发射方向的扰动程度 [0-1] 0

控制粒子系统运作

通过调用stop函数, 可以对当前粒子效果进行暂停。

 particleComponent.stop()

通过调用start函数, 播放粒子效果。

 particleComponent.start() // 可以设置参数,如: particleComponent.start(2), 延时二秒启动

通过代码构建发射器

系统支持通过代码动态创建发射器,这种生成方式可以对发射器的具体参数进行设置, 这里以点型态发射器为例。

首先需要获得元素的实例,详情见element元素, 并获取对应的组件,详情见component组件

 particleComponent.createPointEmitter(direction1, direction2) 

通过自定义粒子发射过程构建一副画作

粒子运作的轨迹由粒子发射器规定的粒子运动矢量direction大小与粒子初始位置position决定,这一运作过程均由粒子系统进行模拟运算。

为了实现特殊的粒子效果,每个发射器中支持定义粒子运作接口processInstance, 这里以粒子复现一副图片效果为例。

    // 记录图片数据的二元数组content, 取值范围[0~1]
    var content = ..

    //设置箱型发射器的发射方向,与粒子初始位置范围
    particleComponent.createBoxEmitter(direction1, direction2, minEmitBox, maxEmitBox);

    //实现发射器的自定义粒子运作接口, 声明参数instance代表每个粒子实例,deltaTime为根据渲染更新的时间,均由系统自动传入
    particleComponent.particleEmitter.processInstance = (instance, deltaTime) => {
      var contentTemp = content
      var cellNumY = contentTemp.length
      var cellNumX = contentTemp[0].length
      // 影响画作的大小与粒子疏密程度的因子
      var step = 0.02
      // 实际渲染出的画作高度
      var height = Math.floor(step * content.length)
      // 实际渲染出的画作宽度
      var width =  Math.floor(step * cellNumX)
      if(instance.position.x - instance.particleSystem.emitterPosition.x> width){
        instance.age = instance.lifeTime;
            return;
        }
        instance.age = 0;
        const posX = Math.floor((instance.position.x -  instance.particleSystem.emitterPosition.x)/ step);
        const posY = Math.floor(instance.position.y/ step);
        const speed = contentTemp[cellNumY-1-posY][posX] * 0.97;
        instance.position.x += ( 1 - speed * 0.97 ) * 0.03 + Math.random() * 0.007;
        instance.color.w = speed * 0.3;
    };

这里介绍有关每个粒子实例的相关属性,用户可以在processInstance接口中设计它们的运作规律

粒子实例相关属性

名称 类型 备注
angle number 粒子的偏移角度
color Vector4 粒子颜色
direction Vector3 粒子的运动方向
particleSystem Particle 粒子所归属的粒子系统
position Vector3 粒子所处位置
size number 粒子大小

粒子系统

粒子属性与生命周期过程中的变化效果由系统ParticleSystem管理,其一个版本代理到元素XRParticleSystem,对应在xml中的标签是xr-particle

微信小程序wxml中创建

首先声明资源节点,导入纹理,用于描绘粒子的基本形态

<xr-asset-load type="texture" asset-id="particle-texture" src="path/to/texture.xxx" />

接下来在xml中通过xr-particle标签声明粒子节点,同时设置粒子系统的基本属性,例如

<xr-particle material="particle-mat" capacity="20" emit-rate="5" life-time="3" emitter-type="BoxShape" textre="particle-texture"></xr-particle>

粒子系统基本属性

影响粒子系统生命周期、初始色彩、初始体积等效果的相关属性介绍如下

名称 类型 备注 默认值 用例
angle number[] 粒子的起始角度,”最小值 最大值(可选)” [0, 0] angle=”0 0″
angular-speed number[] 每秒钟粒子旋转的角度(单位:角度) [0, 0] angular-speed=”0 0″
atlas Atlas 描绘粒子形态的纹理图集 atlas=”particle-atlas”
atlas-frames string[] 指定纹理图集中的特定帧,以帧名的形式 atlas-frames=”snow star”
atlas-loop boolean 决定纹理图集播放到最后一帧时是否循环 true atlas-loop=”false”
atlas-random boolean 粒子的初始形态是否从纹理集中随机选择 false atlas-random=”true”
atlas-speed number 粒子纹理的变化速度 1 atlas-speed=”2″
burst-count number 粒子一次性喷射的粒子数量,大于-1生效 -1 burst-count=”5″
capacity number 容许同时存在的最多粒子数量 1 capacity=”5″
delay number 延时启动粒子的秒数 0 delay=”3″
emit-rate number 每秒钟允许生成的最多粒子数量 10 emit-rate=”5″
emitter-type string 粒子发射器的类型 “PointShape” emitter-type=”SphereShape”
emitter-props map 粒子发射器的属性,搭配发射器类型使用 “” emitter-props=”radius:1.5″
emitter-position number[] 粒子发射器的位置 (0, 0, 0) emitter-position=”2 0 0″
end-color number[] 粒子生命周期结束时的颜色 [1, 1, 0, 1] end-color=”1 1 0 1″
gravity number 粒子在y轴上每秒的运动距离 0 gravity=”9.8″
life-time number[] 粒子的生命周期时长区间,”最小值 最大值(可选)” [1, 1] lifeTime=”1 3″
render-mode string 粒子的渲染模式,详见billboard模式介绍 “default” render-mode=”stretched”
size number[] 粒子的大小,”最小值 最大值(可选)” [0.3, 0.3] size=”1″
speed number[] 粒子的速度大小,”最小值 最大值(可选)” [1, 1] speed=”1 1″
start-color number[] 粒子初始颜色左区间 [1, 0, 0, 1] start-color=”1 0 0 1″
start-color2 number[] 粒子初始颜色右区间 start-color2=”1 1 0 1″
stop-duration number 粒子存在的总时长(单位:秒),0表示永远存在 0 stop-duration=”3″
texture string 描绘粒子形态的基本纹理 texture=”particle-texture”
mesh string 以特定网格形态渲染粒子 mesh=”cube”
use-noise boolean 是否对粒子运动轨迹启用噪声 false use-noise=”true”
noise-texture string 采样的噪声纹理名(可选),内置柏林噪声 noise-texture=”noiseTexture”
noise-strength number[] 不同轴向上施加噪声干涉运动的力度(可选) [1, 1, 1] noise-strength=”2 1 1″

注意:这里的属性类型指粒子系统内部对应的数据结构,在xml格式中按照用例的字符串形式输入即可,对应字符串转换规律如data-values中所示。

轮廓间交互

⚠️ 轮廓间交互功能目前尚在Beta阶段,并且需要2.32.1及之后的基础库才能使用。

碰撞与重叠

碰撞和重叠是不同的概念:

  • 碰撞是指:两个轮廓发生物理学碰撞,并因此而改变位置与速度,产生碰撞事件;
  • 重叠是指:两个轮廓在位置上发生了重叠,并产生重叠事件,发生物理学碰撞。

需要轮廓才能发生交互

碰撞重叠是在两个轮廓之间发生的行为,所以在添加交互属性之前,请先确保为物体创建了轮廓。

轮廓交互组件

通过为标签添加shape-interact属性来设置轮廓间交互:

<xr-mesh ... mesh-shape shape-interact>

设置shape-interact属性后会自动为对应元素创建ShapeInteract组件。

组件属性

属性值 描述 值类型 备注
disabled 是否禁用交互 boolean disable=true的话,效果和不存在shape-interact属性时一致
collide 是否发生碰撞 boolean collide=true的话,发生碰撞,否则发生重叠,默认false
bounciness 弹性系数 number 0≤bounciness≤1,仅当collide=true时生效
staticFriction 静摩擦系数 number 0≤staticFriction≤1,仅当collide=true时生效
dynamicFriction 动摩擦系数 number 0≤staticFriction≤1,仅当collide=true时生效

⚠️ 只有两个物体的collide都为true,他们之间才能发生碰撞!

交互事件

在两个轮廓之间发生交互时,会产生交互事件,可以在标签上绑定相关绑定事件回调:

<xr-mesh ... mesh-shape shape-interact bind:overlap-begin="handleOverlapBegin">

事件一共有以下6种:

事件名 描述 事件回调参数
collide-begin 发生碰撞 ICollideEvent
collide-persist 碰撞持续 ICollideEvent
collide-exit 碰撞结束 ICollideEvent
overlap-begin 发生重叠 IOverlapEvent
overlap-persist 重叠持续 IOverlapEvent
overlap-exit 重叠结束 IOverlapEvent

其中xxx-begin和xxx-exit会成对地出现,从相交的第二帧开始xxx-persist每帧都会生成一次。

刚体和全局物理

⚠️ 刚体物理功能目前尚在Beta阶段,并且需要2.32.1及之后的基础库才能使用。

全局物理

要让当前的场景成为一个物理世界,需要先在<xr-scene>下配置一个<xr-physics>标签:

<xr-scene>
    <xr-physics />
    ...(场景中的其他标签)
</xr-scene>

⚠️ <xr-physics>标签只能位于<xr-scene>下一级,不能放置在更深的层级中。

全局物理配置

可以通过修改在<xr-physics>上的属性来配置全局物理参数(目前仅开放了少数配置项):

属性名 描述 值类型 备注
disabled 是否禁用物理 boolean disable=true的话,效果和不存在<xr-physics>标签时一致。
gravity 全局重力 Vector3 默认重力(0, -9.8, 0)

刚体

在标签上添加rigidbody属性来使其成为一个刚体:

<xr-mesh ... rigidbody />

添加之后就能观察到物体受重力影响而下落了。

刚体配置

通过为rigidbody属性添加属性值来修改刚体配置:

<xr-mesh ... rigidbody="mass: 5.0" />
属性值 描述 值类型 备注
disabled 是否禁用刚体 boolean disable=true的话,效果和不存在rigidbody属性时一致。
mass 刚体质量 number mass > 0
useGravity 是否受重力影响 boolean
constraintsMask 限制刚体在某个轴上的移动 number 具体指参考 @TODO

刚体组件

标签上的rigidbody属性对应元素上的Rigidbody组件。
使用Rigidbody组件上的方法可以移动刚体或者修改刚体属性:

方法名 描述 备注
addForce / addTorque 对刚体施加力,产生加速度。 对刚体施加力,产生加速度。
sleep / wakeUp 强制刚体睡眠/唤醒 正常情况下睡眠状态是由物理引擎自动管理的,如果发现刚体异常静止,可以尝试手动wakeUp。

轮廓

如果想要与场景中的物体进行互动,比如说点击、拖拽物体,那么这个物体得先拥有一个轮廓才行。

轮廓是一个组件。与某个物体互动,实际上是在与这个物体的轮廓进行互动,轮廓让这个物体在物理世界中拥有了一个分身。

创建轮廓

创建轮廓非常简单,只要为xml标签添加上xxx-shape的属性即可。
例如以下代码为一个球体创建了一个球状轮廓:

<xr-mesh node-id="mesh-sphere" geometry="sphere" sphere-shape></xr-mesh>

实际上,不需要标签是xr-mesh,任意一个场景标签都可以创建轮廓:

<xr-node node-id="shape-node" sphere-shape></xr-node>

轮廓种类

名称 标签属性名 组件数据 备注
球状轮廓 sphere-shape center, radius, autoFit
胶囊体轮廓 capsule-shape center, radius, height, autoFit
长方体轮廓 cube-shape center, size, autoFit
网格模型轮廓 mesh-shape 自动适配元素下的Mesh和GLTF模型

除了网格模型轮廓之外,其他轮廓均拥有autoFit属性(实际上网格模型轮廓的autoFit常开,所以不能设置)。开启autoFit之后,轮廓会自动寻找当前元素下的Mesh组件GLTF组件,并使用自身形状去尽量贴合模型。

如果将GLTF组件和开启了autoFit轮廓组合使用,那么会为GLTF内部的每个Mesh都创建一个轮廓。 ⚠️ 使用MeshShape的时候,请确保模型顶点数量小于65535个。如果超过了数量限制,推荐使用CubeShape+autoFit来代替。

轮廓交互

可以通过为标签绑定事件的方式来与轮廓进行互动,如果尚未了解如何绑定事件,可以参考事件文档。

例如使用以下代码,可以为轮廓绑定一个log事件(需自行完成logFunction函数):

<xr-node node-id="shape-node" cube-shape bind:touch-shape="logFunction"></xr-node>
事件名 描述 事件回调参数 备注
touch-shape 点击轮廓时触发 IShapeTouchEvent 如果有多个物体叠在一起,会点中最上层的。
drag-shape 点击轮廓后,手指不松开的情况下进行拖拽时触发 IShapeDragEvent 只有在先触发touch-shape之后才会触发这个事件,target和touch-shape保持一致。
untouch-shape 点击轮廓后,手指松开时触发 IShapeTouchEvent 只有在先触发touch-shape之后才会触发这个事件。

无论有没有绑定事件,其实都会触发事件,只是没有事件回调函数就什么都没发生。

轮廓可视化

为标签加上shape-gizmo即可将标签上的轮廓用线框绘制出来:

<xr-node node-id="shape-node" cube-shape shape-gizmo></xr-node>

MeshShape不会显示轮廓,因为基本上和渲染出来的模型一致。

物理系统

xr-frame中的物理系统主要管理场景中的轮廓,以及触发轮廓点击事件。


  • 轮廓 – 为物体创建可交互的轮廓;
  • 刚体和全局物理Beta – 让物体成为刚体,可以受到力的影响而产生加速度;
  • 轮廓间交互Beta – 让物体可以与其他物体产生交互,包括碰撞以及重叠判断。

帧动画

帧动画是一种内置的动画实现,提供给开发者类似于 CSS 动画的能力,去控制一个元素下所有组件的数据。

我们提供了一种资源来定义帧动画,之后可以用对应的加载器加载,在组件中使用。

帧动画资源

首先我们要准备一个帧动画资源,它是被存储为json格式,大致如下:

{
  "keyframe": {
    "parent": {
      "0": {
        "rotation": [0, 0, 0],
      },
      "100": {
        "rotation": [0, 6.28, 0]
      }
    },
    "child": {
      "0": {
        "position.y": -0.5,
        "material.u_baseColorFactor": [0.48, 0.78, 0.64, 1]
      },
      "100": {
        "position.y": 1.5,
        "material.u_baseColorFactor": [0.176, 0.368, 0.937, 1]
      }
    }
  },
  "animation": {
    "parent": {
      "keyframe": "parent",
      "duration": 8,
      "ease": "linear",
      "loop": -1
    },
    "child": {
      "keyframe": "child",
      "duration": 4,
      "ease": "ease-in-out",
      "direction": "both",
      "loop": -1
    }
  }
}

可以看到其中有两个部分:keyframeanimation

keyframe中定义了帧动画具体的行为,本质上类似于css3中的keyframes,其中进度的范围是0~100,每个进度下是动画具体影响的键和值。其中键的规则是[组件].[属性].[属性2].[属性...],比如transform.postion.x,就是对应于使用了这个帧动画的元素下的transform组件的position属性的x属性变化。

当然我们不难看到,如果仅仅是按照这样的规则,一来是开发者必须要实现对应组件下每个属性的访问器,其次是对于一些常见的属性写起来会繁琐,再者对于材质动画会无从下手。为了解决这个问题,我做了一些实现上的优化:

  1. 如果整个属性路径最终是[组件].[属性]的形式,则不需要写访问器,会自动设置。
  2. positionrotationscale会被自动代理到transform组件下。
  3. material是一种特殊的定义,会被自动代理到mesh组件的材质的uniform下。

要注意,属性的值仅仅支持numbernumber-arraycolor类型的数据。 有了keyframe,再者就是animation了。animation中定义了真的暴露给animator的片段,其中第一级的每个键都是片段的名字,它对应的值就是其作为动画播放时的一些参数。keyframe定义这个片段对应于上面定义的哪个keyframeduration是以秒为单位的时长,loop是默认循环次数(-1为永远循环),delay是播放延迟,direction是播放方向,默认forwards,可选backwardsboth(在循环时交替前向反向),ease则是时间函数,决定动画的插值曲线。

支持的时间函数可见 API 文档:noneParamsEaseFuncs 和 useParamsEaseFuncs。

使用

有了资源我们便可以加载使用它,和其他资源一样,keyframe资源遵循标准加载流程:

<xr-asset-load type="keyframe" asset-id="anim" src="/assets/keyframes/test.json" />

然后便可以引用使用:

<xr-node anim-keyframe="anim" anim-autoplay="clip:parent">
  <xr-node position="0 -0.5 3" anim-keyframe="anim" anim-autoplay="clip:child">
  </xr-node>
</xr-node>

注意这里使用了anim-keyframe字段来指定animator组件要默认使用哪个keyframe动画。

动画组件

动画组件Animator用于管理所挂载元素下所有的动画。

和其他组件一样,它也提供了在xml中和脚本控制两种方法。我们先看看脚本控制是怎么做的:

const animator = el.getComponent(xrFrameSystem.Animator);

// 添加一个动画,`clipMap`可选
animator.addAnimation(new XrTeamCameraAnimation(data), clipMap);

// 通过动画类直接创建一个动画,`clipMap`可选
const anim = animator.createAnimation(XrTeamCameraAnimation, data, clipMap);

// 移除一个动画
animator.removeAnimation(anim);

// 播放名为`name`的动画,可以同时播放多个
animator.play(name, options);

// 暂停名为`name`的动画,不填`name`则暂停所有
animator.pause(name);

// 唤醒名为`name`的动画,不填`name`则唤醒所有
animator.resume(name);

// 停止名为`name`的动画,不填`name`则停止所有
animator.stop(name);

// 将名为`name`的片段定格到某个进度
animator.pauseToFrame(name, progress);

这里要特别注意的有几个点:

  1. clipMap本质上是给开发者提供了一个从动画组件片段的名字到动画实例片段名字的映射,这一般在动画组件有多个动画实例、而动画实例中的片段有重名的情况下会很有用。如果不填,则会默认使用动画实例的片段名来索引。
  2. options是播放时的参数,具体的定义可以参照API文档,要说明的是在play时提供的这个参数,会覆盖掉动画实例onPlay时返回的参数。

相较于脚本控制,大部分开发者在xml中用动画组件会比较常见。所有派生自XRElement元素的元素,都会拥有默认的动画属性映射:

<xr-node
  anim-keyframe="basic-anim"
  anim-clipmap="default:cube"
  anim-autoplay="clip:cube, speed:2"
></xr-node>

这三个属性对应于动画组件的三个数据,最后一个anim-autoplay指定了是否要默认播放以及默认播放的片段和参数,如果不写clip数据则会播放所有的片段。anim-clipmap则是给默认加载的动画指定一个片段映射,而这个默认的动画就是anim-keyframe指定的,它就是前面提到的内置的帧动画,这个可以在后面的章节看到详细说明。

除了anim-keyframe,其他两个参数可以作用于前面提到的gltf动画,也可以在相关章节查看。

事件

动画组件为元素提供了以下事件:

事件 参数 立即 wxml 时机
anim-stop 对象,其中name是片段名字 某个片段播放停止时

动画实现

动画实现的基础是基类Animation,所有的动画都必须派生于它去实现必要的方法。让我们以一个例子来看看:

import XrFrame from 'XrFrame';
const xrFrameSystem = wx.getXrFrameSystem();

// 定制动画接受的初始化数据接口
interface IXrTeamCameraAnimtionData {
  targets: {
    hikari: XrFrame.Vector3;
    roam: XrFrame.Vector3;
    xinyi: XrFrame.Vector3;
    final: XrFrame.Vector3;
  },
}

// 定制动画接受的播放额外配置接口
interface IXrTeamCameraAnimationOptions {

}

// 定制动画的实现
class XrTeamCameraAnimation extends xrFrameSystem.Animation<
  IXrTeamCameraAnimtionData,
  IXrTeamCameraAnimationOptions
> {
  private _camera: XrFrame.Transform | undefined;
  private _target: XrFrame.Transform | undefined;
  private _targets: IXrTeamCameraAnimtionData['targets'] | undefined;
  private _startC: XrFrame.Vector3 = new xrFrameSystem.Vector3();
  private _endC: XrFrame.Vector3 = new xrFrameSystem.Vector3();
  private _startT: XrFrame.Vector3 = new xrFrameSystem.Vector3();
  private _endT: XrFrame.Vector3 = new xrFrameSystem.Vector3();

  // 动画初始化时会被执行,传入初始数据
  // 必须设置`this.clipNames`,提供给动画组件必要的信息
  public onInit(data: IXrTeamCameraAnimtionData) {
    this._targets = data.targets;
    this.clipNames = ['hikari', 'roam', 'xinyi'];
  }

  // 动画被播放时会被执行,必须返回片段时长`duration`
  // 剩下三个返回参数是可选的,详见API文档
  public onPlay(el: XrFrame.Element, clipName: string, options: IXrTeamCameraAnimationOptions): {
    duration: number,
    loop?: number,
    delay?: number,
    direction?: XrFrame.TDirection
  } {
    this._camera = this._camera || el.getComponent(xrFrameSystem.Transform);
    this._target = el.getComponent(xrFrameSystem.Camera).target;
    this._startT.set(this._target.position);
    this._endT.setValue(this._targets![clipName].x, this._targets![clipName].y, this._targets![clipName].z);
    this._startC.set(this._camera.position);
    this._endC.set(this._endT);
    this._endC.z += 2;

    return {duration: 3};
  }

  // 动画播放进度更新是会被执行,`progress`的范围是`0~1`
  // `el`参数是指这个动画目前作用于哪个元素,因为动画和元素、组件并非总是一一对应的
  public onUpdate(el: XrFrame.Element, progress: number, reverse: boolean) {
    progress = xrFrameSystem.noneParamsEaseFuncs['ease-in-out'](progress);
    this._startT?.lerp(this._endT, progress, this._target?.position);
    this._startC?.lerp(this._endC, progress, this._camera?.position);
  }

  // 动画播放暂停时会被执行,暂停本身的逻辑是自动的
  public onPause(el: XrFrame.Element) {

  }

  // 动画从暂停中唤醒时会被执行
  public onResume(el: XrFrame.Element) {

  }

  // 动画停止时会被执行,包括播放结束和手动停止
  public onStop(el: XrFrame.Element) {

  }
}

这段代码中,我们定制了一个动画,它的几个生命周期来定义其是如何运作的。框架内置了两种动画帧动画和gltf动画,但这里我们先不讨论它们,先看看在实现了一个动画后,如何去创建和操纵它,这也就引入了动画组件。

获取追踪状态

在追踪器使用过程中,开发者往往会希望实时监测追踪状态,包括追踪中、追踪到、出错等,并进行相应的处理。微信小程序提供了两个事件来实现这个需求,其中ar-tracker-switch比较简单,仅仅在追踪到/追踪中切换,而ar-tracker-state则提供了更加详尽的信息:

ar-tracker-state事件从基础库2.29.1开始支持。

<xr-ar-tracker id="ar-tracker" mode="Marker" src="{{markerImg}}" bind:ar-tracker-state="handleARTrackerState">
  <xr-gltf model="gltf" />
</xr-ar-tracker>

绑定后编写逻辑:

handleARTrackerState({detail}) {
  // 事件的值即为`ARTracker`实例
  const tracker = detail.value;
  // 获取当前状态和错误信息
  const {state, errorMessage} = tracker;
}

以上state的类型为EARTrackerState,当状态为EARTrackerState.Error时,可以从errorMessage获取详细错误信息。

但这里也要注意,由于某些使用时序的问题,很多场景下开发者需要自己去确定ARTracker的初始状态,比如在ARSystemar-ready事件中通过id获取ARTracker的引用,然后判定初始状态:

handleARReady({detail}) {
  const xrFrameSystem = wx.getXrFrameSystem();
  const tracker = this.scene.getElementById('ar-tracker').getComponent(xrFrameSystem.ARTracker);
  // 初始状态
  const {state, errorMessage} = tracker;
  // 绑定事件
  tracker.el.event.add('ar-tracker-state', tracker => {
    const {state, errorMessage} = tracker;
  });
}