Speed is an important performance metric for games. If players have to react in realtime then the framerate is a big deal.
One common cause of framerate problems is memory allocation inside update loops.
A solution to this is to use Object Pools.
We looked at basic Object Pools in the previous article. Be sure to check that out if you are unfamiliar with using a Phaser Group
as an Object Pool.
In this article, we will expand upon the basic example with an Object Pool class.
Object Pool Class
Keeping our Object Pool logic in one place is good for reusability, readabiity, and organization.
We will be spawning crates in our CratePool
class just like we did in the basic example.
Note that the code is in TypeScript.
|
|
The ICratePool
interface is not strictly necessary but it is often better to program against an interface than an implementation.
If interfaces are confusing then just omit it. 😎 It is not required to understand Object Pools.
In the basic example, we talked about using classType
to specify a different type than Phaser.GameObjects.Sprite
. Line 10 is how we can do that.
The maxSize
property is set to -1
which means no maximum size. You can change this value to ensure the Object Pool never gets too big.
Finally, the spawn()
and despawn()
methods are where we will add logic to properly reset instances.
Note that we create the KEY_CRATE
constant here and export it for others to use. It does not have to be here. An alternative is to put all shared constants in a separate file.
|
|
This spawn()
and despawn()
logic is very similar to what we had in the basic example.
We make sure to set the crate
to be active and visible when we spawn it. Then on despawn we set it to inactive and invisible with this.killAndHide(crate)
.
In this example, it does not matter if we set the alpha
and scale
to 1
in despawn()
or spawn()
. We put it in despawn()
but you can chose either.
Integrating with the GameObjectFactory
The pattern in Phaser is to use the GameObjectFactory
to create GameObjects
with code like this.add.group()
.
Phaser doesn't know about our CratePool
class so an alternative is to use this.add.existing()
and pass in an instance of CratePool
. This will work fine but it is not idiomatic to Phaser.
It would be nicer to be able to use code like this.add.cratePool()
. We can do this and Phaser makes it easy!
|
|
We just need to register a function with the GameObjectFactory
that creates a CratePool
instance. The exact logic in this function will vary.
Because we are subclassing the Phaser.GameObjects.Group
class we can look at what Phaser does and do the same things.
Lastly, TypeScript won't know about the new cratePool()
method and complain. We can fix that by using Declaration Merging.
|
|
Using the CratePool
Using the CratePool
is the easy part. This will look similar to the Scene from the basic example.
|
|
We create a CratePool
on line 27 and then use spawn()
and despawn()
on lines 64 and 74.
Not many lines were saved by the CratePool
class but you can use it in another Scene without duplicating code. 👏
You can also add logic like initializing with a starting size or what rule to use when more objects need to be created.
Initialize with Starting Size
It is a common feature of Object Pools to initialize with a set number of premade objects instead of having a lazy pool that only creates instances when necessary.
Different circumstances can benefit from each approach.
We can implement initializing with a starting size like this.
|
|
On line 9 we do a safety check to ensure that we only do this if the pool has not been initialized and some joker didn't pass in a negative number. 😭
The createMultiple
method on line 14 is used to create as many new instances as specified by size
. We set visible
and active
to false
because we will not be using these instances immediately.
The method can be used in a Scene like this:
|
|
The information text should show a starting Size value of 5 opposed to 0.
Next Steps
Object Pools are not without drawbacks and, thanks to some very smart people, JavaScript engines are faster than anyone could have imagined a decade ago.
Check you have a performance problem from object creation before you use Object Pools or you'll just add unnecessary complexity.
In the next article, we will look at using Object Pools with Matterjs physics bodies. Pooling simple images is more academic than practical since games are usually more complex than that!
Let us know in the comments below if anything doesn’t work or is unclear. We’ll be happy to fix or clarify!
Be sure to sign up for our newsletter so you don't miss any future Phaser 3 game development tips and techniques!
Drop your email into the box below.
Don't miss any Phaser 3 game development content
Subscribers get exclusive tips & techniques not found on the blog!
Join our newsletter. It's free. We don't spam. Spamming is for jerks.