现有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颗子弹”为例子
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颗子弹”为例子
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()
})
}
}
/**
* 通过节点池创建节点
* @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
}
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()
}
}
}
当节点在 NodePool 中时
回收节点时,节点状态并不会还原节点初始状态,需要用户手动还原
这就是方案二中,onDisable需要做的事
这就是Cocos3.x 对象池NodePool使用介绍和注意事项,我也是从坑中走出来的,希望对您有帮助。
点个赞再走!