Tutorial: Object Pooling with PooledObjectCreator¶
Spawn and reclaim GameObjects without the per-frame allocation cost of Instantiate / Destroy. PooledObjectCreator hands out reused instances from a pool; consumers ask for an item via Create, hand it back via Reclaim. Bullets, particle bursts, UI cards, enemy waves — anything that churns.
By the end of this tutorial you'll have a pool that pre-spawns 20 bullet instances and dispenses / reclaims them without garbage spikes.
Prerequisites: Familiarity with Unity prefabs.
How It Works¶
flowchart LR
P["Pool<br/>(inactive reserve)"]
A["Active items"]
Consumer["Consumer<br/>(bullet spawner)"]
Consumer -->|"Create()"| P
P -->|"obj"| Consumer
Consumer -->|"Reclaim(obj)"| A
A -->|"released"| P
A PooledObjectCreator:
- Pre-instantiates up to
initialCounton Awake. - Grows on demand up to
maxCount(0 for unlimited). - Routes returned instances back into the inactive reserve.
- Caps the live "in-use" set via
maxActive(force-reclaims the oldest when exceeded).
Every instance carries a PoolObject component (added automatically if the prefab doesn't have one) — that's what links it back to the creator and lets it participate in the acquire / release dance.
1. Prepare the Prefab¶
Make your prefab (e.g. Bullet). Add a PoolObject component to the root.
The PoolObject component is what tracks "which pool I belong to" and exposes the Acquire / Release hooks. If you forget to add it, the creator will add one at runtime and warn — fine for prototyping, but author it explicitly for production.
2. Add the Pooled Object Creator¶
In the scene, on a long-lived GameObject (e.g. BulletPool), add a PooledObjectCreator.
Add Component → Jungle → Creator → Pooled Object Creator
Configure:
| Field | Value |
|---|---|
| prefab | Drag your Bullet prefab in. |
| initialCount | 20 (pre-spawn 20 on Awake). |
| maxCount | 100 (cap total pool size; 0 for unlimited). |
| poolParent | Optional parent transform for inactive instances. Defaults to the creator's transform. |
| maxActive | 0 (no cap on active items). Set to N to force-reclaim the oldest when more than N are in use. |
Press Play. Open the hierarchy — under the creator you'll see 20 inactive Bullet instances waiting.
3. Acquire and Release from Code¶
The simplest API:
[SerializeField] private PooledObjectCreator bulletPool;
public void Fire() {
bulletPool.Create(bullet => {
// bullet is the GameObject, freshly activated.
// Apply velocity, sound, etc.
});
}
public void Recycle(GameObject bullet) {
bulletPool.Reclaim(bullet, () => {
// Optional: notify other systems the bullet is back.
});
}
Create is async-style — it gives you a callback when the instance is ready (most pools complete it synchronously, but the API leaves room for grace periods or fade-in animations on Acquire).
Reclaim does the inverse — disables the GameObject, resets pool state, returns it to the inactive reserve. The optional callback fires when reclaim is complete.
4. Acquire and Release Without Writing Code¶
Octoputs and Jungle ship operations that drive creators directly. Look for CreateOperation, ReclaimOperation, DestroyOperation in any operation list picker. Wire them to:
- Reactor body — spawn a bullet whenever a "fire" signal arrives.
- AttachingAgent — let the agent reclaim Attachables it can't place anywhere.
- ProcessComponent — fire-and-forget spawn from a process.
The point of using the creator as the indirection is that the who (spawner) and how (instantiate vs pool) are independent. Swap PooledObjectCreator for InstanceObjectCreator and the same spawn calls work — just without reuse.
5. Runtime State Probes¶
| Probe | Returns |
|---|---|
InactiveCount |
Items currently in the reserve (ready to acquire). |
ActiveCount |
Items currently in use. |
TotalCount |
Active + Inactive. |
Inactive |
An IItemSource<GameObject> view of the reserve. |
Useful for HUD displays ("ammo: 12/20"), debug overlays, and writing conditions that gate behaviour on pool state.
6. Bulk Operations¶
| Method | What it does |
|---|---|
ReleaseAll() |
Force-reclaim every active instance back to the pool. Useful on scene reset. |
DestroyItem(obj) |
Permanently remove an instance (e.g. if it was corrupted). |
7. PooledObjectCreator vs InstanceObjectCreator¶
| Creator | Behaviour |
|---|---|
| PooledObjectCreator | Reuse instances. Create = pull from pool (or grow). Reclaim = return to pool. |
| InstanceObjectCreator | One-off. Create = Instantiate. Reclaim = Destroy. |
Both implement the same interface (IItemCreator<GameObject>) — consumers don't care which is wired underneath. Use Instance for "one-shot, throwaway" spawns (debug helpers, short-lived effects you don't care about pooling). Use Pooled when the same kind of object is acquired and released repeatedly.
8. Container Wrappers¶
Sometimes you want to talk to a container of pooled objects rather than the pool itself ("give me one", "store this one back", "how many are available"). Drop a GameObjectCreatorBackedContainer and point it at the PooledObjectCreator. Now you have:
| Container op | Delegates to |
|---|---|
TryTake / Take |
creator.Create |
TryReturn / Store |
creator.Reclaim |
TryRemove |
creator.DestroyItem |
Containers fit anywhere an IItemContainer<GameObject> is expected — agents, transfer commands, UI bindings. The pool stays in charge of the lifecycle, the container exposes it in take/store vocabulary.
Next Steps¶
- InstanceObjectCreator reference — non-pooling alternative.
- PoolObject reference — the per-instance component.
- GameObjectCreatorBackedContainer reference — container wrapper.
- Objects package overview.