One of the main missions of the Infinite Runner Engine is to spawn an infinite (or at least very large) number of objects towards the player. If we just instantiated a new object every time we want a new platform, we’d end up with a lot of objects in our scene. Granted, we could destroy them when they get out of sight for example, but instantiating objects at runtime in Unity is quite costly in terms of performance, and we’d get perf spikes, resulting in small freezes on screen. Not ideal. That’s where object pools come into play. They get filled with instances of an object when the scene starts, and we then activate and use objects from this pool when we need one, and disable them when we don’t need them anymore. That’s all there is to it! Note that object poolers don’t do anything on their own. They’ll just fill the pool with inactive objects. You’ll need a spawner to use them (or any class you’d code that would tap into that pool).
The base class you need to know is ObjectPooler. It’s located under MMTools/ObjectPool. It’s a base class, meant to be extended depending on the use (simple, multiple object pooler), and used as an interface by the spawners. It handles common stuff like singleton and initialization on start(). Do not add this class to a prefab, nothing would happen. Instead, add SimpleObjectPooler or MultipleObjectPooler.
Here’s a breakdown of a pooler’s main methods :
- Awake / FillObjectPool : fills the pool by instantiating the specified GameObjectToPools and grouping them in hierarchy.
- GetPooledGameObject : called by spawners typically, but you can use it in any other class you want (a weapon for example). Returns an object from the pool, ready to use.
A SimpleObjectPooler, as the name implies, is the most simple form of object pooler you can use. To use it, add it as a component to a GameObject in your scene. From its inspector, you’ll be able to drag a prefab into its GameObjectToPool field (that’s what the pool will be filled with), define a PoolSize, and whether or not that pool can expand.
To determine these last two parameters, you need to consider what your game will be like, and how many of these objects you’ll need at a single point in time. Ideally, you’ll want to have a pool large enough so it doesn’t have to expand (expanding means creating new instances at runtime, which is costly, but will prevent you running out of platforms for example and your character falling to a sudden yet inevitable death). A good way I use to determine my pool size is to simply set it super high at first. Then I run the game, and look at the hierarchy view. You’ll be able to see objects getting activated/disabled in real time. You’ll see that only the first X objects of the pool ever get activated. Take a little margin above that X number, and you have your pool size. Whether it should expand or not depends on your game.
In the above image, taken from the Sandbox minimal demo scene, my platform spawner hardly ever needs more than 11 platforms, due to the size of the level, that couldn’t contain much more. I could set my object pool’s size to 12 and probably never run out of platforms to spawn, and it’d never have to expand.
A MultipleObjectPooler is basically a list of SimpleObjectPoolers (or subpools), with some intelligence added into the mix. To use one, just like for a SimpleObjectPooler, just add it as a component to an object in your scene. Define the size of your Pool list (that’s the number of different objects you want to spawn). Let’s say you have two types of platforms you want to spawn (there’s an example of that in the Sandbox demo scene by the way). Set the main pool’s size to 2. Then expand the two elements that appeared. Fill their fields like you would a SimpleObjectPooler’s.
Now below that list you’ll notice you can set a Pooling Method and define whether or not an object can be pooled twice.
There are currently 4 different pooling methods :
- OriginalOrder will spawn objects in the order you’ve set them in the inspector (from top to bottom)
- OriginalOrderSequential will do the same, but will empty each pool before moving to the next object
- RandomBetweenObjects will pick one object from the pool, at random, but ignoring its pool size, each object has equal chances to get picked
- PoolSizeBased randomly choses one object from the pool, based on its pool size probability (the larger the pool size, the higher the chances it’ll get picked)
If you set CanPoolSameObjectTwice to false, the Pooler will try to prevent the same object from being pooled twice to avoid repetition. This will only affect random pooling methods, not ordered pooling.
All this is best demonstrated in the Sandbox demo scene. To give it a try, open the scene, and enable the MultipleObjectPoolerComparison game object in the hierarchy. It contains 8 spawners, with identical pools, but different pooling methods and expand settings. They all contain 3 subpools of 2 clouds, 4 rocks, and 6 trees, in that order. If you press play, you’ll see the different results the various pooling methods can offer you :
The last important part of the object pool system is the PoolableObject component. You’ll need to add it to the prefabs you want to spawn or add to pools. It overrides the Destroy method of the object to prevent it from being destroyed but recycled instead. It also includes a LifeTime system, that allows you to ensure the object will get destroyed after X seconds of existence if you want. A 0 LifeTime value will make it live forever. It’s also responsible for firing the OnSpawnComplete event, that you can use in other classes to know when your object is actually being pooled.