t1k:cocos:playable:object-pool
| Field | Value |
|---|---|
| Module | playable |
| Version | 0.5.6 |
| Effort | high |
| Tools | — |
Keywords: ads, cocos, object pool, playable, pooling, prefab, spawn
How to invoke
Section titled “How to invoke”/t1k:cocos:playable:object-poolObjectPoolManager
Section titled “ObjectPoolManager”Static singleton for reusing Node instances. Avoids runtime instantiate()/destroy() overhead critical for playable ad performance. See t1k-cocos-playable-asset-management for AssetsManager used by LoadAsync.
Architecture
Section titled “Architecture”ObjectPools (scene root Node)└── Pool_Enemies (category Node, key="Enemies/Goblin") └── Goblin_Pool ├── [inactive nodes] ← pooledObjects[] └── [active nodes] ← spawnedObjects Set- Category extracted from key prefix:
"Enemies/Goblin"→ category"Enemies" - Keys without
/go into"Default"category resetNode()resets position/rotation/scale and callsReset()on all components that implement it
Key APIs
Section titled “Key APIs”const pool = ObjectPoolManager.instance;
// --- Initialization ---// From prefab reference (sync)pool.Load("Effects/Coin", coinPrefab, 20);pool.Load(prefabRef); // key = prefab.name, count = 10
// From resources/ folder (async, tries multiple path variants)await pool.LoadAsync("Effects/Coin", 20);
// --- Spawn ---const node = pool.Spawn("Effects/Coin");const node = pool.Spawn("Effects/Coin", position, rotation, parentNode);
// --- Recycle ---pool.Recycle(node); // auto-finds pool by node membershippool.Recycle("Effects/Coin", node); // explicit key (faster)
// --- Batch recycle ---pool.RecycleAll("Effects/Coin");
// --- Cleanup (trim idle objects, keep retainCount) ---pool.Cleanup("Effects/Coin", 5); // destroy all but 5 idle instances
// --- Teardown ---pool.Unload("Effects/Coin"); // destroy pool + container nodepool.UnloadAll(); // destroy all pools
// --- Monitoring ---const info = pool.GetPoolInfo("Effects/Coin");// { available: 8, active: 2, total: 10 }pool.HasPool("Effects/Coin"); // booleanpool.GetAllPoolKeys(); // string[]Reset() Callback
Section titled “Reset() Callback”Components on pooled nodes can implement Reset() to clean up state on recycle:
export class CoinFX extends Component { private _velocity: Vec3 = new Vec3();
public Reset(): void { // Called automatically by ObjectPoolManager.Recycle() this._velocity.set(0, 0, 0); this.node.setOpacity(255); }}Workflow: Common Tasks
Section titled “Workflow: Common Tasks”One-time pool setup at game start:
// In a loading state or GameView.onLoad():pool.Load("Coin", coinPrefab, 30);pool.Load("HitFX", hitFxPrefab, 10);Async load from resources folder:
// Prefab must be at: assets/resources/Effects/Coin.prefab// or assets/resources/prefab/Coin.prefab (LoadAsync tries variants)await pool.LoadAsync("Effects/Coin", 20);Recycle from animation complete callback:
const node = pool.Spawn("Effects/Coin", startPos, null, canvas);// ... animate ...tween(node).then(() => pool.Recycle(node)).start();FlyingAnimationController pattern (Spawn → auto-Recycle on complete):
// FlyingAnimationController manages its own Spawn/Recycle internally.// Preload via controller:flyingController.preloadPool("Effects/Star", starPrefab, 10);await flyingController.playAnimation({ poolKey: "Effects/Star", ... });AudioService pattern (AudioEmitter pooling):
AudioService internally uses ObjectPoolManager with AudioEmitter prefab from resources/prefab/AudioEmitter.prefab. Do not manually pool audio nodes.
Common Mistakes
Section titled “Common Mistakes”- Calling
Spawn()beforeLoad()/LoadAsync()— returnsnullwith a console warning - Destroying a pooled node manually instead of calling
Recycle()— leaves dangling entry inspawnedObjects - Using the same key string for different prefabs — pools are keyed by string, collisions corrupt the pool
- Not implementing
Reset()— node retains state (position, opacity, tween callbacks) from previous spawn - Calling
LoadAsync()twice for the same key concurrently — safe (second call skips if pool exists), but wasteful
Gotchas
Section titled “Gotchas”- Pool prewarm must happen before first acquire — first acquire on cold pool stutters; prewarm in
onLoad. - Returned objects need state reset — pooling a node with active tweens or active children leaks residual state into the next user.
- Pool size cap is mandatory — uncapped pools grow until OOM on long playable sessions.
Spawn()activates then reparents — breaks lifecycle-armed controllers —Spawn(key, pos, rot, parent)setsnode.active = trueand THEN reparents the node to the passedparent. Reparenting an already-active node firesonDisable → onEnableon it. Any component that arms itself inonEnable(binds node-local events, subscribes signals, registers) and whose setup must be followed by a separateconfigure()/init call will have its arming churned by that reparent cycle — so the follow-upconfigure()runs against a half-armed controller and the object silently fails to drive/animate. Real case:AttackingAnimalController._arm()bindsTargetHealthEvent.DEATHinonEnableand_disarm()unbinds inonDisable; spawning a raider via the pool left it un-driven. Fix: for spawns that needconfigure()after activation, use the direct-instantiate pattern (mirrorsAmbientFoxSpawner): instantiate once,node.active=false, parent once while inactive, then on spawn dosetWorldPosition(anchor) → node.active = true → configure(...)on the already-parented node — never activate-then-reparent. See alsot1k-cocos-playable-animation-corefor animation controller lifecycle patterns.