跳到主要内容

Prefab 与对象池:重复使用,也要重置

Prefab 解决“相同结构怎样创建”;对象池解决“反复创建与销毁的成本怎样降低”。二者经常一起用,但不是同一件事。

同一个对象从空闲池借出,使用后重置并归还
同一个对象从空闲池借出,使用后重置并归还(概念演示,非引擎录屏)

先做正确的 Prefab​

把一个带 UITransform、Sprite、行为脚本的节点拖到资源管理器,保存为 Prefab。实例化后每个节点有独立运行状态;Prefab 是模板,不是所有实例共同修改的一份分数表。

十个静态格子可能不需要对象池。大量反复出现的飘字、子弹、短特效,才更值得测量与复用。

可装配示例​

下载 PooledToken.ts 和 TokenSpawner.ts,放入同一个脚本目录。Creator 每份脚本最多定义一个 Component,不能把这两个类合并到同一个文件。

创建一个有 UITransform 与 Sprite 的 Token,挂 PooledToken,保存为 Prefab。给一个较大的 UI 节点挂 TokenSpawner,把 Prefab 拖入 token 字段。点击该区域会借出节点并向上移动,结束后归还;同屏最多 20 个。不要把 TokenSpawner 挂在 Token 模板本身。

展开完整对象池示例
PooledToken.ts
import { _decorator, Component, Node, tween, Tween, Vec3 } from 'cc';
const { ccclass } = _decorator;

@ccclass('PooledToken')
export class PooledToken extends Component {
private motion: Tween<Node> | null = null;
begin(done: () => void) {
this.reset();
this.motion = tween(this.node)
.to(0.6, { position: new Vec3(0, 180, 0) })
.call(done).start();
}
reset() {
this.motion?.stop();
this.motion = null;
this.node.setPosition(0, 0, 0);
this.node.setScale(1, 1, 1);
}
onDisable() { this.reset(); }
}
TokenSpawner.ts
import { _decorator, Component, instantiate, Node, NodePool, Prefab } from 'cc';
import { PooledToken } from './PooledToken';
const { ccclass, property } = _decorator;

@ccclass('TokenSpawner')
export class TokenSpawner extends Component {
@property(Prefab) token: Prefab | null = null;
private pool = new NodePool();
private borrowed = new Set<Node>();

onEnable() {
this.node.on(Node.EventType.TOUCH_END, this.spawn, this);
}
onDisable() {
this.node.off(Node.EventType.TOUCH_END, this.spawn, this);
for (const node of [...this.borrowed]) this.recycle(node);
}
spawn() {
if (!this.token || this.borrowed.size >= 20) return;
const node = this.pool.get() ?? instantiate(this.token);
const token = node.getComponent(PooledToken);
if (!token) { node.destroy(); console.error('预制体缺少 PooledToken'); return; }
node.setParent(this.node);
this.borrowed.add(node);
token.begin(() => this.recycle(node));
}
private recycle(node: Node) {
if (!this.borrowed.delete(node)) return;
node.getComponent(PooledToken)?.reset();
this.pool.put(node);
}
onDestroy() { this.pool.clear(); }
}

池不会替你清理业务​

3.8.8 的 NodePool.put 会把节点从父节点移除,并可调用配置的处理器,但不会自动恢复全部业务状态。旧 Tween、计时、目标引用、透明度和监听都需要由组件负责。

示例显式跟踪 borrowed,停用时回收场上对象,销毁时再清空池。pool.clear() 只处理池里缓存的节点,不能替你找到已经借出的节点。

验收​

连续点击后对象数量有上限;每个对象从相同初始位置出发;停用/启用后不会残留半途动画;离开场景后池与场上节点都得到处理。

给 AI:“先说明是否真的需要对象池。若需要,列出 reset 字段、场上对象追踪、重复归还保护和退出清理,不能只给 get/put 两行。”

依据:Prefab、NodePool 3.8.8 源码、每脚本最多一个组件的注册限制。