资源加载元素

在资源系统一章中我们简略提到了几个xml中和资源相关的标签xr-assetsxr-asset-load等,这一章就来详细介绍一下它们。

xr-asset-load

xr-asset-load是元素XRAssetLoad在xml中的对应,而这个元素则是组件AssetLoad的一个简单代理。

注意xr-asset-load标签一旦初始化,后续不可动态修改,并且即使移除后再次添加相同asset-id的资源,原先已经使用了此id的组件也不会更新。如果真的有高级需求,请使用脚本的方式。

若要在xml中使用时将其所有的参数写出,大概如下:

<xr-asset-load type="texture" asset-id="waifu" src="/assets/waifu.png" weight="2" options="anisoLevel:2" defer />

其他的属性不用多说,但大家可能会对weightdefer比较好奇。defer是指延迟加载,只有框架发现这个资源被组件应用了才会去真的加载它,而weight,则涉及到下一个要介绍的元素了。

2.28.1版本后,内置资源同样可以使用xr-asset-load配合下面的xr-assets元素获取加载进度,只需要写好类型和资源ID即可:

<xr-asset-load type="env-data" asset-id="xr-frame-team-workspace-day" />

xr-assets

xr-assets对应于元素XRAssets,其是作为组件Assets的一个代理。

这个元素的功能非常简单,一般使用时我们会让它将xr-asset-load元素包裹起来:

<xr-assets bind:progress="handleAssetsProgress" bind:loaded="handleAssetsLoaded">
  <xr-asset-load type="texture" asset-id="waifu" src="/assets/waifu.png" weight="2" />
  <xr-asset-load type="texture" asset-id="waifu2" src="/assets/waifu2.png" weight="1" />
</xr-assets>

可见,其实它本质上就是资源组,是为了在其下资源的加载过程中提供给开发者一些事件,来通知加载进度的。

事件

资源组组件为元素提供了以下事件:

事件 参数 立即 wxml 时机
progress 对象,进度progress,和当前资源描述asset 场景第一次解析完毕
loaded 对象,成功的资源assets,和出的错误errors 场景销毁之前

资源加载器

xr-frame允许开发者定制资源加载器,来添加自己所需的资源类型。所有的资源加载器都需要派生自AssetLoader类,然后使用上一章的方法在xml中或者手动使用。

在基础库版本v2.29.2以上,支持自定义资源加载器。

以一个加载器为例

让我们以内置的纹理加载器为例,

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

// 指定纹理可接受的额外参数
export interface ITextureLoaderOptions {
  anisoLevel?: number;
}

// 加载纹理时会被传入的数据
type ITextureLoadData = XrFrame.IAssetLoadData<ITextureLoaderOptions>;

// 定制加载器
export default class TextureLoader extends xrFrameSystem.AssetLoader<XrFrame.Texture, ITextureLoaderOptions> {
  // 指定加载器参数的`schema`,和**数据解析**一章中的数据类型一致
  public readonly schema: ILoaderOptionsSchema = {
    anisoLevel: {type: 'number', defaultValue: 1},
  };

  // 当纹理资源加载时会调用这个方法
  public load(
    params: ITextureLoadData,
    callbacks: {
      // 开发者需要在加载进度更新时调用的回调
      onLoading(progress: number): void;
      // 开发者需要在加载完成时调用的回调
      onLoaded(value: Kanata.Texture): void;
      // 开发者需要在加载出错时调用的回调
      onError(error: Error): void;
    }
  ): void {
    const {options} = params;

    // 这里可以拿到当前场景`scene`的引用
    const img = this.scene.createImage();

    img.onload = () => {
      const texture = this.scene.createTexture({
        source: [img],
        width: img.width,
        height: img.height,
        anisoLevel: options.anisoLevel
      });

      callbacks.onLoaded(texture);
    }

    img.onerror = (error) => {
      callbacks.onError(error);
    }

    img.src = params.src;
  }

  // 返回一个当前加载器指定类型资源的**默认资源列表**,这些资源可以直接被组件引用,但它们都是`defer`的,只有在用到的时候才会去加载。
  public getBuiltin() {
    return [
      {
        assetId: 'brdf-lut',
        src: 'https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/brdflut.png',
        options: {}
      }
    ];
  }

  // 某个资源被取消加载时会调用,记得一定要先调用父级的方法
  public cancel(params: ITextureLoadData) {
    super.cancel();
  }

  // 某个资源被释放时会调用,这里可以执行释放操作
  public release(params: ITextureLoadData, value: XrFrame.Texture) {
    value.destroy();
  }
}

// 注册加载器到框架,资源类型为`texture`
xrFrameSystem.registerAssetLoader('texture', TextureLoader);

通过这个纹理加载器我们可以看到,资源系统是通过加载器的loadcancelrelease三个方法来管理整个资源的生命周期的。

最后将加载器注册为某种类型后,这种资源类型将会同时被注册进组件数据解析器,在schema中定义使用。

原始加载器

除了后续会提到的各种类型的资源加载器外,为了最灵活应对需求,框架提供了原始加载器RawLoader来加载最原始的数据,其类型为rawoptions{encoding: 'binary' | 'utl-8'},默认是二进制。

资源系统

资源是一类特殊的对象,其存储着微信小程序框架需要使用的数据,比如纹理、几何数据、材质等等。资源系统管理着整个场景的资源,其包括自身和资源注册、资源加载器、资源元素三部分。

资源系统在运行时可以通过scene.assets获取,可在手动使用中看到具体的用法。

推荐用法

推荐的用法是在xml中用对应的几个标签定义资源,然后在特定的组件中使用资源的asset-id引用:

<xr-assets>
  <xr-asset-load type="texture" asset-id="waifu" src="/assets/textures/waifu.jpg" />
  <xr-asset-material asset-id="standard-mat" effect="standard" />
</xr-assets>

<xr-mesh node-id="waifu" geometry="plane" uniforms="u_baseColorMap: waifu" />

注意这种用法有一定的限制,具体标签和组件的说明,可见资源元素。

需要注意资源加载都是异步的,组件引用资源时会根据资源加载的时机来执行onAdd或者onUpdate周期,所以可能存在资源尚未准备好就开始渲染之类的状况,需要开发者用资源元素中介绍的XRAssets元素的事件回调酌情自行处理。

手动使用

对于高级用户,有时候会想手动加载、获取或者释放资源,资源系统暴露了一系列接口来满足这样的需求:

// 加载资源
const {value: tex1} = await scene.assets.loadAsset({type: 'texture', assetId: 'tex1', src: texUrl1});
scene.assets.loadAsset({type: 'texture', assetId: 'tex2', src: texUrl2});

// 手动获取加载过的资源,最后一个参数是`fallback`资源,作为候补
const tex2 = scene.assets.getAsset('texture', 'tex1', 'white');

// 手动获取加载过的资源和状态
const {value: tex2, state, promise} = scene.assets.getAssetWithState('texture', 'tex1', 'white');

// 手动添加一个资源
scene.assets.addAsset('texture', 'tex3', tex3);

// 取消加载资源
scene.assets.cancelAsset('texture', 'tex2');

// 释放加载过的资源
scene.assets.releaseAsset('texture', 'tex1');

资源注册

除了使用加载机制来添加资源,还有一套额外的机制,就是资源注册。资源注册允许我们为某个id的资源提供创建其实例的一个回调,当有组件引用到这个资源时,便会执行回调去创建它,这个方法只会执行一次:

registerTexture('white', (scene: Scene) => scene.createTexture({
  source: [new Uint8Array([255, 255, 255, 255])],
  width: 1,
  height: 1
}));

这个例子就是注册了一个white的纹理资源。目前微信小程序框架提供的可用资源注册方法会在各个资源的文档(比如纹理)中单独论述。