• Cocos3.x 对象池NodePool使用介绍和注意事项



    前言

    现有Cocos对象池技术贴基本是2.x的,3.x的资料很少,如果是直接从3.x上手的建议看看这里。


    一、适用场景

    稍微复杂的游戏开发均需考虑到性能优化,基本会涉及对象池使用,对象池使用场景针对那种需要相同单位频繁创建、销毁的情况,例如动作游戏中的子弹、敌人等;因为(cc.instantiate)和销毁(node.destroy)操作是非常耗费性能的;

    二、基本原理

    针对相同单位的频繁创建和销毁,为了提高性能,我们可以创建后复用,场景切换时再销毁;
    例如游戏中的枪循环发射5颗子弹,我们可以预先创建5颗子弹,然后把子弹放到缓存池子(NodePool)里,需要的时候从池子里取出,用完放回去,如此循环,那么整个游戏过程中只会涉及5次创建和销毁操作。

    当然,这是最简单的情况,实际会更灵活,例如池子动态扩容等。

    三、创建和方法

    import { NodePool } from "cc";
    
    // 创建,创建后pool是一个节点数组
    const pool = new NodePool()
    
    // 获取当前缓冲池的可用对象数量
    pool.size() 
    
    // 获取对象池中的对象,如果对象池没有可用对象,则返回空。这个函数会调用 poolHandlerComp 的 reuse 函数,如果组件和函数都存在的话。
    pool.get() 
    
    // 向缓冲池中存入一个不再需要的节点对象。这个函数会自动将目标节点从父节点上移除,但是不会进行 cleanup 操作。这个函数会调用 poolHandlerComp 的 unuse 函数,如果组件和函数都存在的话。
    pool.put() 
    
    // 销毁对象池中缓存的所有节点
    pool.clear() 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    四、使用方案

    这里用“循环发射1颗子弹”为例子

    4.1 方案一:预先创建

    流程图

    Created with Raphaël 2.3.0 开始 创建对象池 实例化子弹并放进对象池 开始使用:从对象池取出子弹 结束使用:把子弹放进对象池 游戏结束 销毁对象池里子弹 结束 yes no

    代码

    import { _decorator, Component, Prefab, NodePool, instantiate } from 'cc'
    const { ccclass, property } = _decorator
    
    @ccclass('Start')
    export class Start extends Component {
      public pool!: NodePool // 定义节点池
      @property({ type: Prefab, tooltip: '子弹' }) readonly bulletPfb!: Prefab // 子弹预制体
    
      start() {
        // 1. 创建节点池
        this.pool = new NodePool()
    
        // 2. 通过预制体创建节点,并放到节点池
        const node = instantiate(this.bulletPfb)
        this.pool.put(node)
    
        // 3. 每5秒从节点池中取出节点,使用后放回节点池
        this.schedule(() => {
          // 从节点池中取出节点
          const node = this.pool.get()
    
          // 这里开始使用节点
          // ..............
    
          // 使用结束后放回节点池
          this.pool.put(node)
        }, 5)
      }
    
      // 控件销毁时,清空节点池
      onDestroy() {
        this.pool.clear()
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    4.2 方案二:动态创建

    动态创建核心是用到才创建对象池,这里用“循环发射1颗子弹”为例子

    流程图

    Created with Raphaël 2.3.0 开始 开始使用 存在对象池 对象池有可用子弹 从对象池取出子弹 使用结束:把子弹放进对象池 游戏结束 销毁对象池里子弹 结束 实例化子弹 创建对象池 yes no yes no yes no

    代码

    1、在实际应用中创建全局对象池,每个Prefab创建一个对象池,例如这里的:
    data.pools = {}

    2、封装getPoolNode方法,用于根据对象池是否有节点来动态创建节点

    3、子弹节点会绑定Bullet自定义组件,里面封装了destroyPool方法来处理节点的销毁,以及pool.put()回收节点时触发onDisable来处理回收逻辑

    • 场景使用
    import { _decorator, Component,  Prefab, } from 'cc'
    import data from '../global/Data'
    import { getPoolNode } from '../utils/GameCommon'
    const { ccclass, property } = _decorator
    
    @ccclass('Start')
    export class Start extends Component {
    
      @property({ type: Prefab, tooltip: '子弹' }) readonly bulletPfb!: Prefab // 子弹预制体
    
      start() {
        // 1. 每5秒从节点池中取出节点,使用后放回节点池
        this.schedule(() => {
          // 从节点池中取出节点
          const node = getPoolNode(this.bulletPfb)
    
          // 这里开始使用节点
          // ..............
    
          // 使用结束后放回节点池(destroyPool为自定义方法)
          node.getComponent(Bullet).destroyPool()
        }, 5)
      }
    
      // 控件销毁时,清空节点池
      onDestroy() {
        Object.keys(data.pools).forEach((key) => {
          data.pools[key].clear()
        })
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • getPoolNode 方法
    /**
     * 通过节点池创建节点
     * @param prefab 预制体
     */
    export function getPoolNode(prefab: Prefab) {
      let name = prefab.data.name
      let node: Node = null
      if (data.pools.hasOwnProperty(name)) {
        //已有对应的对象池
        let pool = data.pools[name]
        if (pool.size() > 0) {
          node = pool.get()
        } else {
          node = instantiate(prefab)
        }
      } else {
        // 没有对应对象池,创建他!
        let pool = new NodePool()
        data.pools[name] = pool
    
        node = instantiate(prefab)
      }
      return node
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • Bullet 组件
    import { _decorator, Component } from 'cc'
    import data from '../../global/Data'
    const { ccclass } = _decorator
    
    @ccclass('Bullet')
    export class CommonUnit extends Component {
      public $hp = 1 // 生命
    
      // 禁用时还原状态(节点池pool.put()时触发)
      onDisable() {
        this.$hp = 1
        // 如果有用到tween,则这里要停止,否则节点还会执行
        // Tween.stopAllByTarget(this.node)
      }
    
      // 从对象池中销毁
      destroyPool() {
        const pool = data.pools['Bullet']
        if (pool) {
          pool.put(this.node)
        } else {
          this.destroy()
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    五、注意事项

    5.1 节点池NodePool生命周期

    当节点在 NodePool 中时

    • onLoad start 只会在节点第一次从 NodePool 中取出时触发
    • onEnable 当节点从 NodePool 中取出时触发
    • onDisable 当节点被放回 NodePool 时触发
    • onDestroy 当 NodePool 被 clear 时触发

    5.2 还原节点状态

    回收节点时,节点状态并不会还原节点初始状态,需要用户手动还原
    这就是方案二中,onDisable需要做的事

    六、总结

    这就是Cocos3.x 对象池NodePool使用介绍和注意事项,我也是从坑中走出来的,希望对您有帮助。
    点个赞再走!

  • 相关阅读:
    webpack5 (三)
    MogaFX—Intermex报告对其信贷协议的增强
    分享一下怎么开发一个陪诊小程序
    【MySQL】DML
    通过Redis实现一个异步请求-响应程序
    ATFX:从百济神州半年报,看生物科技板块前景
    Win10/11 删除文件资源管理器左侧栏目文件夹
    RuoYi-Vue-Plus (角色部门-数据权限 @DataPermission使用、自定义数据权限、数据权限拦截 、处理器解读)
    java导出数据到excel表中
    SNMP协议——网络管理概述
  • 原文地址:https://blog.csdn.net/iamlujingtao/article/details/126794528