使用对象池

    对象池就是一组可回收的节点对象,我们通过创建 cc.NodePool 的实例来初始化一种节点的对象池。通常当我们有多个 prefab 需要实例化时,应该为每个 prefab 创建一个 cc.NodePool 实例。 当我们需要创建节点时,向对象池申请一个节点,如果对象池里有空闲的可用节点,就会把节点返回给用户,用户通过 node.addChild 将这个新节点加入到场景节点树中。

    当我们需要销毁节点时,调用对象池实例的 put(node) 方法,传入需要销毁的节点实例,对象池会自动完成把节点从场景节点树中移除的操作,然后返回给对象池。这样就实现了少数节点的循环利用。 假如玩家在一关中要杀死 100 个敌人,但同时出现的敌人不超过 5 个,那我们就只需要生成 5 个节点大小的对象池,然后循环使用就可以了。

    关于 cc.NodePool 的详细 API 说明,请参考 。

    下面是使用对象池的一般工作流程

    把你想要创建的节点事先设置好并做成 Prefab 资源,方法请查看 预制资源工作流程

    在场景加载的初始化脚本中,我们可以将需要数量的节点创建出来,并放进对象池:

    接下来在我们的运行时代码中就可以用下面的方式来获得对象池中储存的对象了:

    1. // ...
    2. createEnemy: function (parentNode) {
    3. if (this.enemyPool.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象
    4. enemy = this.enemyPool.get();
    5. } else { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 cc.instantiate 重新创建
    6. enemy = cc.instantiate(this.enemyPrefab);
    7. }
    8. enemy.parent = parentNode; // 将生成的敌人加入节点树
    9. }

    安全使用对象池的要点就是在 get 获取对象之前,永远都要先用 size 来判断是否有可用的对象,如果没有就使用正常创建节点的方法,虽然会消耗一些运行时性能,但总比游戏崩溃要好!另一个选择是直接调用 get,如果对象池里没有可用的节点,会返回 null,在这一步进行判断也可以。

    当我们杀死敌人时,需要将敌人节点退还给对象池,以备之后继续循环利用,我们用这样的方法:

    这样我们就完成了一个完整的循环,主角需要刷多少怪都不成问题了!将节点放入和从对象池取出的操作不会带来额外的内存管理开销,因此只要是可能,应该尽量去利用。

    使用构造函数创建对象池时,可以指定一个组件类型或名称,作为挂载在节点上用于处理节点回收和复用事件的组件。假如我们有一组可点击的菜单项需要做成对象池,每个菜单项上有一个 MenuItem.js 组件:

    1. // MenuItem.js
    2. cc.Class({
    3. extends: cc.Component,
    4. this.node.selected = false;
    5. this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect.bind(this), this.node);
    6. },
    7. unuse: function () {
    8. this.node.off(cc.Node.EventType.TOUCH_END, this.onSelect.bind(this), this.node);
    9. reuse: function () {
    10. this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect.bind(this), this.node);
    11. }
    12. });

    在创建对象池时可以用:

    这样当使用 menuItemPool.get() 获取节点后,就会调用 MenuItem 里的 reuse 方法,完成点击事件的注册。当使用 menuItemPool.put(menuItemNode) 回收节点后,会调用 MenuItem 里的 unuse 方法,完成点击事件的反注册。

    另外 cc.NodePool.get() 可以传入任意数量类型的参数,这些参数会被原样传递给 reuse 方法:

    如果对象池中的节点不再被需要,我们可以手动清空对象池,销毁其中缓存的所有节点:

      当对象池实例不再被任何地方引用时,引擎的垃圾回收系统会自动对对象池中的节点进行销毁和回收。但这个过程的时间点不可控,另外如果其中的节点有被其他地方所引用,也可能会导致内存泄露,所以最好在切换场景或其他不再需要对象池的时候手动调用 clear 方法来清空缓存节点。

      cc.NodePool 除了可以创建多个对象池实例,同一个 prefab 也可以创建多个对象池,每个对象池中用不同参数进行初始化,大大增强了灵活性;此外 针对节点事件注册系统进行了优化,用户可以根据自己的需要自由的在节点回收和复用的生命周期里进行事件的注册和反注册。

      而之前的 cc.pool 接口是一个单例,无法正确处理节点回收和复用时的事件注册。不再推荐使用。