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颗子弹”为例子

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()
  }
}

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()
    })
  }
}

  • 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
}
  • 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()
    }
  }
}

五、注意事项

5.1 节点池NodePool生命周期

当节点在 NodePool 中时

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

5.2 还原节点状态

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

六、总结

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

猜你喜欢

转载自blog.csdn.net/iamlujingtao/article/details/126794528
今日推荐