# 场景流式加载

当加载一个具有很多个 Prefab 的大场景时,下载依赖资源大小有可能达到数十MB,整个下载的过程会较为漫长。为了解决该问题,微信小游戏框架提供了场景流式加载的方案,以实现场景中 Prefab 的动态按需加载。

流式加载方案的核心是 StreamingLoadingComponent 组件。

# 编辑以及烘培期

  1. 在编辑器中挂载 StreamingLoadingComponent 的节点,其直接子节点中是由 Prefab 创建的节点树会被视为是要动态加载的节点树。如下图中的 World 节点挂载了 StreamingLoadingComponent,则其下面的 floor、wall1、wall2 等子节点都是由 Prefab 创建的。

  1. StreamingLoadingComponent属性介绍
属性 功能
loadingPrioirty 表示这些子节点的加载优先级。
unloadType 表示这些子节点在被动态卸载时应用怎样的操作。
    Deactivate 表示将这些节点的 active 置为 false。
    Destroy 表示将调用节点的 destroy 方法,即销毁。
unloadDebounce 表示节点被判定要卸载时的一个防抖动等待时间,为了避免玩家在一个边界区域反复移动时造成的反复卸载。
  1. 在 Project 中右键该场景,点击 “烘培流式加载场景” 。

  1. 烘培场景的意思就是将场景中所有需要被动态加载的节点从场景 mygame.scene 中剔除,并另存为一个新的场景文件。开始烘培后需要等待一段时间,完成后又上角会有浮窗提示

以此处的 World 节点为例,其下 15 个子节点都是由 Prefab 创建的,所以新生成的 assets/bake/mygame.bake.scene 中,这 15 个子节点的信息被剔除了。assets/bake 目录下会生成 mygame.bake.scene 和 15 个子节点对应的 Prefab。

  1. 将输出烘培后文件的目录设置为打包入口,这样才生成的 Prefab 才能被动态加载。

  1. 将烘培后的文件作为入口发起构建

# 运行时

运行时需要设置 engine.game.streamingLoadingSystem 来管理 StreamingLoadingComponent。

# interval

streamingLoadingSystem 做的事情就是每隔一段时间检查 StreamingLoadingComponent 下的 StreamingLoadingObject(以上文 World 为例,StreamingLoadingObject 就是 15 个要被动态加载的 Prefab)是否需要被加载/卸载,这是 streamingLoadingSystem 的一次 update。

// 默认值 500,单位 ms
engine.game.streamingLoadingSystem.interval = 1000;

# Tester

判定物体是否要被加载是通过 engine.game.streamingLoadingSystem.tester 来控制的,tester 是一个实现了 needTest 和 test 两个方法的对象。这两个方法都是被 streamingLoadingSystem 在每次 update 时调用的回调函数。

needTest() {
  return true
}

# Tester.prototype.needTest()

needTest 是一个没有参数的回调函数,表示是否需要进行后续的遍历,如视角/玩家位置变化不大,可以理解为玩家视野变化不大,因此无须遍历检查物体是否需要加载/卸载。

# Tester.prototype.test(object)

test 是一个传入 StreamingLoadingObject 的回调函数,StreamingLoadingObject 包含以下几项属性

test(object) {
  // object.center, Vector3, 表示物体包围球的中心,是一个 worldPosition
  // object.radius, 表示物体包围球的半径,也是在 world 坐标系里的
  // object.priority 表示物体的加载优先级
  // 返回 0 表示加载 
  return 0
  // 返回 1 表示卸载
  return 1
}

engine.game.streamingLoadingSystem.tester 的默认值是引擎实现的一个基于包围球相交检测的 tester,如果没有特殊需要,可以使用这个 tester。

const tester = engine.game.streamingLoadingSystem;
// 设置 center 来表示当前玩家的位置,center 是一个 Vector3,而且应该是一个 worldPosition.
tester.center.x = -131;
tester.center.y = 50;
tester.center.z = 80;
// 设置 radius 来表示玩家的视野范围,在这个范围内相交的物体会被加载,反之超出这个范围的则会被卸载
tester.radius = 50;

如果认为这样的 tester 不能满足要求,可以自行实现一个 tester

class CustomTester {
  constructor() {
    this.lastPosition = engine.Vector3.createFromNumber(0, 0, 0);
    this.currentPosition = engine.Vector3.createFromNumber(0, 0, 0);

  }
  needTest() {
    // 上次观测点距离本次观测点大于一定距离,才进行检测
    if (this.currentPosition.distanceTo(this.lastPosition) > 20) {
    this.lastPosition.x = this.currentPosition.x;
    this.lastPosition.y = this.currentPosition.y;
    this.lastPosition.z = this.currentPosition.z;

    return true;
    }
    return false;
  }
  test(object) {
    // 特殊需求,999 表示一定要加载永不卸载
    if (object.priority === 999) {
        return true
    }
    // ...
  }
}

engine.game.streamingLoadingSystem.tester = new CustomTester()

完成场景流式烘培之后,动态加载的节点依赖的资源将不会在加载场景时被加载,假如场景的根节点就是上例中的 World,这个场景将没有任何依赖资源;所有节点都是动态加载的,在 streamingLoadingSystem 判定其需要被加载时,才会去下载 & 读取对应的 Prefab。