Am I relying too much on Instantiate, and is it such a bad thing?

Hi there! So I’m working on my first real big project and I’ve heard many times that Instantiating and Destroying game objects is not a good idea in Unity. I’ve been told to look into object pooling, which I think I understand the basics of. I think the idea is that you keep objects around that you enable and disable based on your needs rather than Instantiate them, from say a prefab, and then destroy them.

The issue is that I’m just really not sure how to handle certain situations with this and so I’ve continued to use Instantiate and Destroy. Right now I’m using Instantiate and Destroy several things like spawning enemies and gatherables, as well as items that get dropped or placed in the world.

For example, I’m working on an RPG and I just started trying out some projectiles. So I’m Instantiating arrows and fireballs, and then I’m destroying them when they collide with something.

Another example is dropping items in the world (think Skyrim cheese wheels). Would I need to have a bunch of dropable items just sitting somewhere and then activate them when I need them to be dropped?

Is it really so important to switch to object pooling for this stuff? How could I go about it?

In the projectile example, would I need to determine how many projectiles the player is allowed to launch at once?

Anyway, some feedback on this would be really helpful, if object pooling is a must, I’d love to hear some strategies for dealing with some of my examples.

Pooling is simply a collection of objects, which you can reuse, during game play and which is predefined at initialization. Alternatively, such collection can grow if needed, to accommodate more objects.

So if you have array or list of bullets, you simply reuse them in scene.
You need track, which bullet is in play, which is a spare.
You can use simply offset index, to indicate, which objects are spare.
When bullet hits target, id is put back to collection. For example, assigning as next index in the array, increasing offset index.

Same applies to anything else, that can be reusable.

You either create enough size for each collection, if you know no more that set number of items is going to be used.
Or set some minimum valid value, and expand collection at run time when needed.

Other than that, I suggest to check internet for different implementations.

1 Like

It depends on your project - how repetitive your objects are, the hardware you intend to run it on, how tight your development schedule is. That said, unless you’re at the extremes of all of those, the answer is almost always “more pooling”. Object pooling costs basically nothing in terms of performance, and only a little in terms of game development effort. If you’re not competing in a 24 hour hackathon or something, there’s little reason not to do it.

And if you don’t know how to do it, it’s a valuable tool that you don’t have in your toolbox. If your toolbox doesn’t have a hammer, you could flip your screwdriver around to bang the nails in, but it’s clear that if your project involves nails you really should just get a hammer.

The easiest way to go about it: Find a package on the Asset Store that handles it for you. There are tons, and I think some of them are even free.

That said, it’s a really simple concept and worth learning how to implement yourself, so just googling “object pooling tutorial” should give you a lot of good, usable results.

1 Like

There’s two possible answers:

  1. No, just choose a sensible maximum. Even if it’s higher than necessary, that’s still preferable to lots of instantiation.
  2. You can write the object pool in such a way that, if you try to spawn an object and you’re out of that kind in the pool, it instantiates a new one, and adds it to the pool. In fact, a lot of implementations of object pooling only instantiate new objects on demand, and then when those eventually get destroyed, hey, there’s your pool.
1 Like

Thanks for the replies! Looks like I’ll be learning more about object pooling methods then.

Your second point is interesting, so are you implying that the Instantiate method isn’t so bad when it comes to memory / long term performance, but the real trouble is with the Destroy method?

Otherwise, if you’re using the Instantiate method for object pooling it would seem like it’s missing the point.

Memory is not your issue. The CPU usage, which requires creating and destroying new objects and related data.
Plus, you have whole garbage collector following the process.

So potentially ramping down your FPS. Or increasing unnecessarily battery usage, if talking about mobile devices.

1 Like

No, the point is that an object only gets instantiated once. “The real trouble is with the Destroy method” is kind of true in a roundabout way, in that you’re destroying something that you could be reusing. (Sort of like - to go on a bit of a tangent - when you hit the brakes on the highway, you’re reducing your gas mileage. The brake pedal obviously doesn’t burn any gas, but it does destroy momentum that you will later have to build back up by burning gas. This is the same sort of thing.)

You’re going to be instantiating objects no matter what (even with “everything happen up front” style object pooling, you’re still instantiating them, you’re just doing it all in Start(). The idea behind the “on-demand object pooling” concept is 1) you don’t have to think about how many objects you might need, and 2) slightly faster startup times. During the first “buildup” of objects, yes, it’ll still take the time to instantiate them and that period of gameplay will be unchanged from not using object pooling at all. But in almost all games that “buildup” is a small fraction of the total gameplay time.

1 Like

If you keep looking, you can find a list of things you “should” be doing as long as your arm. Some are wrong, some are only useful in very specific situations, some are very easy to mess-up. You should plan Achievements from the start (actual advice I’ve heard, and probably good).

An object pool makes instantiating more complicated, adds new potential bugs (not resetting everything from a re-used object) while possibly giving a small speed-up. Are you having trouble with sluggish scenes during Instantiates? Do you want to stop working on your game for a week or two? Is your game even finalized enough to worry about it?

2 Likes

I mean, you can literally setup a working object pool in like 10 lines of code, so I’m not sure about this whole complexity thing… I’d say in the case your game has a lot of bullet firing etc it’s pretty much always worth it to look into pooling, if only for the bullets.

Object pooling is something you do need to know. Google around more then enough tutorials on the subject

It doesn’t have a lot of bullets. The is OP is writing about enemies, gatherables, and only arrows and fireballs. This is a big deal. If they wrote “my frame rate drops when I fire a 50-bullet blast” it would be one thing. Massive bullet counts are the simplest and best use of recycling. But this is “some rando told me to pool power-ups, should I?”

As for time, it takes at least hours to figure out how to use a system, and find one that works. Then writing code to create an object pool for each type of object. Some need extra code to reset particle systems (and child systems, Arg!) Any scripts using Awake or Start need rewriting and a custom pool. Any that change during use need extra code to reset that. The mere fact of changing how things work will add bugs. Then when any of that changes, the pool system needs to be changed to account for it.

Sure, put “learn how recycling objects works” on the list. It’s a real thing. But rank it behind “get better at making good particles”, “learn more about lighting”, “learn quaternions”, “find an inventory system” and so on.

1 Like

Like Owen says, aim at the low hanging fruit. Bullets, particle systems etc. Things like that are early candidates for pooling. Complex domain logic can be a bitch to reset. For example we are making a advanced VR shooter simulation and the weapons are super complex state machines. We choose to create and destroy those. There aren’t that many weapons in the scene at any given time anyway

1 Like

Once again, thanks to everyone who has replied to this! I’ve done a little bit more research into object pooling now and though I’m unsure I’m gonna start implementing it right away, it’s definitely on my list of things to learn.

To quote something I posted in a similar thread:

Object pooling offers greater efficiency at the cost of complexity. The reason this complexity comes about in my personal experience is due to needing to rewind any changes to state when returning to the pool. Consider you have a object with a rigidbody. Once forces get applied then the object has velocity. You have to be sure to remove this before reusing the object. Consider a composite object which when exploded falls apart. Now you need to reassemble that object before it can be reused. But what if the parts are available to return to the pool to reassemble the object at different times? What about if the game object has a particle effect which needs to complete before returning to the pool?

With hindsight i probably would have restricted my use of pooling to simple objects, relatively stateless objects of which there are many in use, e.g. bullets / explosions. An alternative is a ‘single use pool’ Good luck!

Also if you are on 2019 you can look into incremental garbage collection. It offload the GC over several frames.

1 Like

for simple objects like bullets it’s easy if you limit the maximum number of objects.

        public int size;
public GameObject go;
        GameObject[] poolObjects;
        int position;
        public void Awake()
        {
this.gameObject.setActive(false);
            position = 0;
            poolObjects = new GameObject[size];
            for (int i = 0; i < size; i++)
            {
                poolObjects[i] = GameObject.Instantiate(go,this.transform);
            }
        }
        public GameObject getNext()
        {
            position = (position + 1) % size;
            return poolObjects[position];
        }

    }

something like this, however once you exceeded maxsize old objects will autmatically be overwritten which might not be what you want. But it’s the most efficient method afaik

but you should only use it for simple objects which don’t effect game-logic, because it can happen that some objects will get overwritten

There may be a mistake here as I haven’t tested.

public GameObject PooledObject;
private List<GameObject> _pooledObjectList = new List<GameObject>();

//setup pool
void SetupPool(int numToSpawn)
{
    for (int i = 0; i < numToSpawn; i++)
        {
            var obj = Instantiate(PooledObject, gameObject.transform);

            obj.SetActive(false);

            _pooledObjectList.Add(obj);
        }

}

//return the first inactive pooled object, if there is none, instantiate a new one
//and set it it active
public GameObject GetPooledObject()
{
    var obj = _pooledObjectList.FirstOrDefault(t => !t.activeInHierarchy);

    if (obj == null)
        obj = (GameObject)Instantiate(PooledObject, transform);
 
    obj.SetActive(true);
 
    return obj == null ? null : obj;
}

just need to set the object to inactive after you’re done

When you Instantiate in GetPooledObject, you forgot to add the new instance to the pool.

There’s also no need to check and explicitly return null at the end of the method, since if the object is null, then null will be returned regardless.

public GameObject GetPooledObject()
{
    var obj = _pooledObjectList.FirstOrDefault(t => !t.activeInHierarchy);

    if (obj == null)
    {
        obj = (GameObject)Instantiate(PooledObject, transform);
        _pooledObjectList.Add(obj);
    }
    obj.SetActive(true);
    return obj;
}
1 Like

Ah, there ya are