How to pool diffrent type of objects?

For example on my enemy script im pooling bullets with new unity api like this. Everything is fine because i only have 1 spesific projectile prefab.

        private ObjectPool<EnemyProjectile> projectilePool;


 
        protected override void Start()
        {
            base.Start();
            _animator = GetComponent<Animator>();

            projectilePool = new ObjectPool<EnemyProjectile>(
                CreateProjectile,
                OnTakeProjectileFromPool,
                OnReturnProjectileToPool,
                OnDestroyProjectile,
                true,
                10,
                30
            );
        }



        private EnemyProjectile CreateProjectile() => Instantiate(projectilePrefab);

        private void OnTakeProjectileFromPool(EnemyProjectile projectile) => projectile.gameObject.SetActive(true);

        private void OnReturnProjectileToPool(EnemyProjectile projectile) => projectile.gameObject.SetActive(false);

        private void OnDestroyProjectile(EnemyProjectile projectile) => Destroy(projectile.gameObject);

        private EnemyProjectile GetProjectile(Vector3 position, Quaternion rotation)
        {
            EnemyProjectile projectile = projectilePool.Get();
            projectile.transform.position = position;
            projectile.transform.rotation = rotation;
            return projectile;
        }

        public void ReleaseProjectile(EnemyProjectile projectile) => projectilePool.Release(projectile);

My question is for this senario, everytime i call this method i call with diffrent gameobject. If i create a one object pool for that it wont return correct object everytime. How should i approach this problem?

Should i create a new object pooling for every diffrent type of gameobject?

        public static GameObject SpawnAndDestroy(GameObject spawnObj, Vector3 spawnPos, float destroyTime = 3, Transform parent = null)
        {
            GameObject obj;
            if (parent == null)
                obj = Instantiate(spawnObj, spawnPos, Quaternion.identity);
            else
                obj = Instantiate(spawnObj, parent);
            Destroy(obj, destroyTime);
            return obj;
        }

Why do you want to pool different kinds of objects though? The point of a pool is to group a bunch of similar/the same objects together so that they can be reused and recycled with impunity.

If you mix a bunch of different and unrelated objects together it defeats the point.

You can certainly make a common interface with which you can use across different objects, and make pools using this interface (given the pools all use the same implementation of this interface). Eg, an IProjectile interface that all different kinds of projectiles implement. Then a reusable projectile pool can be used across different weapons with little fuss.

5 Likes

I mean i said diffrent all the time but its not diffrent all the time actually, its mostly for vfxs. There are for example 10 diffrent type of vfxs that are spawning very often in game.

What I don’t understand is, for example, I created an interface called IVFX and created pooling. I spawned 10 different types of vfx here (fire, water, blood, etc.)
When I ask for this from the pool, it usually will not give me the vfx type I want.

In the past, I created a system where I gathered all the vfxes under a prefab and selected them from there. However, this time, even if it was closed, there were a lot of unnecessary objects on the stage. If we count that there are 10 vfx under 1 prefab and since I call it very often, even if there are 10 of them from that prefab, there will be 100 empty objects on the stage.

The old system I made

using System.Collections;
using UnityEngine;
using UnityEngine.Pool;

public class VFXSpawner : MonoBehaviour
{
    private static ObjectPool<VFX> vfxPool;

    [SerializeField]
    private VFX VFXPrefab;

    private const int relaseTimer = 3;
    private const int defaultAmount = 10;
    private const int maxAmount = 100;
    private void Awake()
    {
        vfxPool = new ObjectPool<VFX>(CreateVFX, OnTakeVFXFromPool, OnReturnVFXToPool, OnDestroyVFX, true, defaultAmount, maxAmount);
    }

    private VFX CreateVFX() => Instantiate(VFXPrefab);
    private void OnTakeVFXFromPool(VFX vfx)
    {
        vfx.gameObject.SetActive(true);

        StartCoroutine(RelaseVFX(vfx));
    }
    private void OnReturnVFXToPool(VFX vfx) => vfx.gameObject.SetActive(false);
    private void OnDestroyVFX(VFX vfx) => Destroy(vfx.gameObject);

    private IEnumerator RelaseVFX(VFX vfx)
    {
        yield return new WaitForSecondsRealtime(relaseTimer);
        vfxPool.Release(vfx);
    }


    /// <summary>
    /// Spawns a VFX object at the specified position and optionally enables it based on the VFXType.
    /// </summary>
    /// <param name="position">The position to spawn the VFX object.</param>
    /// <param name="type">The type of VFX to spawn.</param>
    /// <param name="enable">Whether to enable the VFX immediately upon spawning (default is true).</param>
    public static void SpawnVFX(Vector3 position, VFXType type, bool enable = true)
    {
        VFX vfx = vfxPool.Get();
        vfx.transform.position = position;

        if(enable)
            vfx.EnableVFX(type);
    }

}
using System.Collections;
using UnityEngine;
using UnityEngine.Pool;

public class VFXSpawner : MonoBehaviour
{
    private static ObjectPool<VFX> vfxPool;

    [SerializeField]
    private VFX VFXPrefab;

    private const int relaseTimer = 3;
    private const int defaultAmount = 10;
    private const int maxAmount = 100;
    private void Awake()
    {
        vfxPool = new ObjectPool<VFX>(CreateVFX, OnTakeVFXFromPool, OnReturnVFXToPool, OnDestroyVFX, true, defaultAmount, maxAmount);
    }

    private VFX CreateVFX() => Instantiate(VFXPrefab);
    private void OnTakeVFXFromPool(VFX vfx)
    {
        vfx.gameObject.SetActive(true);

        StartCoroutine(RelaseVFX(vfx));
    }
    private void OnReturnVFXToPool(VFX vfx) => vfx.gameObject.SetActive(false);
    private void OnDestroyVFX(VFX vfx) => Destroy(vfx.gameObject);

    private IEnumerator RelaseVFX(VFX vfx)
    {
        yield return new WaitForSecondsRealtime(relaseTimer);
        vfxPool.Release(vfx);
    }


    /// <summary>
    /// Spawns a VFX object at the specified position and optionally enables it based on the VFXType.
    /// </summary>
    /// <param name="position">The position to spawn the VFX object.</param>
    /// <param name="type">The type of VFX to spawn.</param>
    /// <param name="enable">Whether to enable the VFX immediately upon spawning (default is true).</param>
    public static void SpawnVFX(Vector3 position, VFXType type, bool enable = true)
    {
        VFX vfx = vfxPool.Get();
        vfx.transform.position = position;

        if(enable)
            vfx.EnableVFX(type);
    }

}

I sended same code for 2 times by mistake, but some reason i can not edit the post so here is the other

using System.Collections.Generic;
using UnityEngine;

public enum VFXType {Default, Blood, Fire, Ice}
public class VFX : MonoBehaviour
{
    private Dictionary<VFXType, GameObject> vfxDictionary;

    [SerializeField]
    private GameObject defaultVFX;
    [SerializeField]
    private GameObject bloodVFX;
    [SerializeField]
    private GameObject fireVFX;
    [SerializeField]
    private GameObject iceVFX;

    private void Awake()
    {
        vfxDictionary = new Dictionary<VFXType, GameObject>
        {
            { VFXType.Default, defaultVFX },
            { VFXType.Blood, bloodVFX },
            { VFXType.Fire, fireVFX },
            { VFXType.Ice, iceVFX }
        };
    }
    public void EnableVFX(VFXType selectedType = VFXType.Default)
    {
        // Disable all VFX objects first
        DisableAllVFX();

        // Activate the selected VFX if it exists in the dictionary
        if (vfxDictionary.TryGetValue(selectedType, out GameObject selectedVFX))
        {
            selectedVFX.SetActive(true);
        }
    }

    public void DisableAllVFX()
    {
        foreach (var kvp in vfxDictionary)
        {
            kvp.Value.SetActive(false);
        }
    }
}

If these are all different kinds of VFX with different properties/behaviours then there isn’t much point in pooling them together. Like I said, pools are for the same/similar kind of object.

Though if you have generally similar objects then you could pool them together with some kind of shell object. For example, bullet holes. Bullet holes, regardless of shape, generally just need to be put on a surface when a projectile hits it. So you could have a universal pool for all bullet holes, and when pulling one from the pool, you pass some kind of object that describes the hole’s appearance (scriptable object works best here), the shell object then has its appearance updated, and is placed where it needs to be.

That could apply here if all your VFX are used in the same way. You might even be able to get away with it if they’re relatively different, so long as you can describe their behaviour in some kind of object cleanly. A situation where you’d want things to be data driven.

2 Likes

Thank you very much, it is clearer in my mind now.

I can shorten that to:

“Why do you want to pool?”

If you’re just learning, great, have a ball.

But if you think it’s actually going to materially improve your game, make sure you first understand the damage you are 100% for sure going to do to your codebase and to your project.

The costs and issues associated with object pooling / pools:

https://discussions.unity.com/t/892797/10

https://discussions.unity.com/t/833104/2

In very rare extremely-high-count object circumstances I have seen small benefits from pooling.

In 100% of ALL circumstances, object pooling is a source of constant future bugs and edge case disasters.

1 Like

That is the case with all code and systems one is gonna write though.
And “object creation is 100% a source of lagspikes” has about the same universal validity as your saying :stuck_out_tongue:

1 Like

It seems clear to most that object pooling can result in a net benefit when used in appropriate situations. This example (and I like it when people post evidence to support their opinion)
https://www.youtube.com/watch?v=7EZ2F-TzHYw
shows how it can be done fairly generically.

1 Like

Here’s an object pool that supports multiple types of objects:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public sealed class MultiObjectPool<TId, TObject> where TId : struct, Enum where TObject : Component
{
    private readonly Dictionary<TId, ObjectPool<TObject>> pools;

    public MultiObjectPool(int capacity = 8) => pools = new(capacity);

    public void AddPool(TId id, Func<TObject> create, Action<TObject> onGet = null, Action<TObject> onRelease= null, Action<TObject> onDestroy= null, bool collectionChecks = true, int defaltCapacity = 10, int maxSize = 10000)
    {
        pools.Add(id, new(create, onGet, onRelease, onDestroy, collectionChecks, defaltCapacity, maxSize));
    }

    public TObject Get(TId id) => pools[id].Get();
    public void Release(TId id, TObject instance) => pools[id].Release(instance);

    public void Clear()
    {
        foreach(var pool in pools.Values)
        {
            pool.Clear();
        }
    }
}
1 Like

If you want to be able to cache instances based on type, one trick that I use is to have a generic class with a static instance property. Then you can just access that property to get the instance that corresponds to any type. It’s only relevant where you’re comfortable with those types of static references, but my generic object pool classes usually have a static instance property. The ArrayPool class does this with the Shared property. ArrayPool<T> Class (System.Buffers) | Microsoft Learn Then, you can just request the static instance that corresponds to any type you want, like Pool.SharedInstance and it’s kind of like having a single static Dictionary of pools by type, but without using a Dictionary or any collection to hold them all. Consequently, there is no single point of reference to do something like clearing them all.

1 Like

Like spiney said a data-driven approach is usually best. Though you can take an additional step and separate the individual parts of your prefabs and then use the data to compose which object to spawn from multiple different pools.

For example, I often separate my NPCs into their root prefabs which is basically the same for everything, their brain prefab which defines how they think and behave, and their avatar prefab which defines how they look. Then in the spawning system I have a simple database (really just a dictionary in a scriptable object) and the id of the type of npc I want to spawn. The spawning function is responsible for looking up which pool to use for each part of the npc and then assembling it into a complete entity and ensuring all default states are set and ready to go.

When it’s time to despawn have another function that does all of this in reverse. Make sure everything is in the ‘off’ state. Break down each element and return them to their appropriate pool.

2 Likes

My “Spawner” class is a pooling system, where the caller decides what prefab to spawn, and the spawner decides if it has that kind of object already pooled or not. It keeps a dictionary with prefab-assets as keys to separate pools. It can manage “limit to 100 objects active total” or “limit to 10 objects active per prefab.” It can be told what to do when asked to spawn something and you’re at the limit: spawn nothing, recycle the oldest, or recycle the farthest. It can be told to expire sleeping pooled items after a given time, so memory is freed if you haven’t spawned in a while (at the expense of instantiating again if needed later). It can be given a set of prefabs up front and told to spawn them in a radius at regular intervals somewhat like a limited particle emitter.

This test scene has three Spawners. One in the moving green volume set to spawn from two types at random. One as part of an impact system to spawn appropriate audio effects on collisions. One in the red volume spawning a popping avfx whenever it de-spawns a ball.

I was originally inspired by an ancient coin arcade video game, Gauntlet, where physical boxes in the game world would be set to spawn all of the enemies by type.

1 Like