Object Pooling in Phaser 3 with Matter Physics

Use Object Pools with Physics Bodies in Phaser 3

by on 7 minute read


Maintaining a good framerate is important for games where players are expected to react in realtime.

Creating new objects and allocating memory inside update loops is a common cause of framerate drops.

The severity depends on how complex the object is to create and how many times it is created in a single frame.

In this article, we will look at using Object Pools for Phaser 3 GameObjects with Matterjs physics enabled because your complex objects are likely also using physics.

We will be building on the example from Game Optimization with Object Pools in Phaser 3.

Using Matterjs

Phaser 3 defaults to using Arcade Physics. You can choose which physics engine to use in the GameConfig you pass to new Phaser.Game(config).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const config = {
	type: Phaser.AUTO,
	width: 800,
	height: 600,
	physics: {
		default: 'matter',
		matter: {
			debug: true
		}
	},
	scene: [CratesScene]
}

There are more options available to physics.matter. You can find them here.

Updating CratePool

The CratePool class we created in the previous article specified a classType of Phaser.GameObjects.Image.

We need to change this to Phaser.Physics.Matter.Image but there is a small gotcha.

The Phaser.Physics.Matter.Image constructor wants a Phaser.Physics.Matter.World instance but the Phaser Group will pass in an instance of Phaser.Scene when we call get().

This can be solved by subclassing Phaser.Physics.Matter.Image and then passing in a Phaser.Physics.Matter.World instance to super().

1
2
3
4
5
6
7
class Crate extends Phaser.Physics.Matter.Image
{
	constructor(scene: Phaser.Scene, x: number, y: number, key: string)
	{
		super(scene.matter.world, x, y, key)
	}
}

Then in CratePool, we can change the classType to Crate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Crate extends Phaser.Physics.Matter.Image
{
	constructor(scene: Phaser.Scene, x: number, y: number, key: string)
	{
		super(scene.matter.world, x, y, key)
	}
}

export default class CratePool extends Phaser.GameObjects.Group implements ICratePool
{
	constructor(scene: Phaser.Scene, config: Phaser.Types.GameObjects.Group.GroupConfig = {})
	{
		const defaults: Phaser.Types.GameObjects.Group.GroupConfig = {
			classType: Crate,
			maxSize: -1
		}

		super(scene, Object.assign(defaults, config))
	}

	// ...
}

CratePool will now properly spawn images with a Matter physics body attached.

Next, we need to handle the physics body when a member instance is despawned and respawned.

Changing active and visible to false hides the GameObject but does not disable the physics body.

Handling the Physics Body

What we need to do is remove the physics body from the physics world on despawn and add it back on spawn.

But we only want to add the physics body back to the world if we spawned a recycled object. Otherwise, the physics body will get updated twice by the physics engine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import Phaser from 'phaser'

// ...

export default class CratePool extends Phaser.GameObjects.Group implements ICratePool
{
	// ...

	spawn(x = 0, y = 0, key: string = KEY_CRATE)
	{
		const spawnExisting = this.countActive(false) > 0

		const crate = super.get(x, y, key)

		if (!crate)
		{
			return
		}

		if (spawnExisting)
		{
			crate.setActive(true)
			crate.setVisible(true)
			crate.world.add(crate.body)
		}

		return crate
	}

	despawn(crate: Crate)
	{
		crate.setActive(false)
		crate.setVisible(false)
		crate.removeInteractive()
		crate.world.remove(crate.body)
	}
}
// ...

We create a spawnExisting variable to see if the pool will be creating a new instance or recycling one.

this.countActive(false) will return the number of inactive instances in the pool. If this number is greater than 0 before we call get() then we can assume that we will be getting a recycled instance.

In that case, we add crate.body back to the world on line 24. If the pool creates a new instance then we can just leave it alone.

Then in despawn() we remove any registered interactions like mouse events and remove crate.body from the physics world.

Crates should now properly appear and disappear when spawned and despawned. 🎉

Updating the Scene

The Scene in this example is different enough from the previous article that we will build it from scratch.

We will have the Scene spawn a Crate every half-second if there are less than 10 crates active.

Clicking on a crate will make it disappear.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import Phaser from 'phaser'

import { KEY_CRATE } from './CratePool'

const INFO_FORMAT = 
`Size:       %1
Spawned:    %2
Despawned:  %3`

export default class CratesScene extends Phaser.Scene
{
	private group?: ICratePool
	private infoText?: Phaser.GameObjects.Text

	constructor()
	{
		super('crates-scene-physics')
	}

	preload()
    {
		this.load.image(KEY_CRATE, 'assets/crate.png')
    }

    create()
    {
		this.matter.world.setBounds(0, -100, this.scale.width, this.scale.height + 100)
		
		this.group = this.add.cratePool()

		this.infoText = this.add.text(16, 16, '')
		this.infoText.setDepth(1000)
	}

	update()
	{
		if (!this.group || !this.infoText)
		{
			return
		}

		const size = this.group.getLength()
		const used = this.group.getTotalUsed()
		const text = Phaser.Utils.String.Format(
			INFO_FORMAT,
			[
				size,
				used,
				size - used
			]
		)

		this.infoText.setText(text)
	}

	private spawnCrate()
	{
	}
}

This initial setup should be familiar. The one key difference is on line 27 where we set the bounds of the Matterjs physics world.

We are adding 100 pixels of extra height from the top so that the crates will spawn off-screen and fall.

The update() method is still just to show information about the Object Pool.

Next, we will add the timer to check if a crate should be spawned every half second and implement the spawnCrate() logic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import Phaser from 'phaser'

import { KEY_CRATE } from './CratePool'

// ...

export default class CratesScene extends Phaser.Scene
{
	private group?: ICratePool
	private infoText?: Phaser.GameObjects.Text

	// ...

    create()
    {
		this.matter.world.setBounds(0, -100, this.scale.width, this.scale.height + 100)
		
		this.group = this.add.cratePool()
		
		this.time.addEvent({
			delay: 500,
			loop: true,
			callback: () => {
				this.spawnCrate()
			}
		})

		this.infoText = this.add.text(16, 16, '')
		this.infoText.setDepth(1000)
	}

	// ...

	private spawnCrate()
	{
		if (!this.group)
		{
			return
		}

		if (this.group.countActive(true) >= 10)
		{
			return
		}

		const tex = this.textures.get(KEY_CRATE)
		const halfWidth = tex.getSourceImage().width * 0.5
		const x = Phaser.Math.Between(halfWidth, this.scale.width - halfWidth)

		const crate = this.group.spawn(x, 0)

		if (!crate)
		{
			return
		}

		crate.setInteractive()
			.on(Phaser.Input.Events.GAMEOBJECT_POINTER_UP, pointer => {
				this.group!.despawn(crate)
			})

		return crate
	}
}

The timer is added on line 20. It will fire callback every 500 milliseconds and continue to loop forever.

The callback is set to call spawnCrate() which only spawns a crate if there are less than 10 active crates. This check happens on line 41.

We do some math on lines 46 - 48 to pick a random position along the top of the screen to spawn a crate.

Then on line 57, we add a GAMEOBJECT_POINTER_UP event that will despawn the targeted crate.

Run the Scene and you should get something like this:

Next Steps

Now you can pool physics enabled GameObjects in Phaser 3!

You can use this for things like a coin/gem spawner or a bullet storm. Anything that involves creating and destroying a bunch of things frequently.

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.

Phaser 3 TypeScript Optimization Object Pools

Want tips and techniques more suited for you?


You may also like...


Video Guides


Beginner Guides


Articles Recommended For You

Fix Stretched Image Distortions in Phaser 3 with 9-Slice Scaling

by on

Are you having image distortion problems when scaling to make a button or panel graphic bigger? Multiple versions of the …

5 minute read

Command Pattern to Undo Player Actions

by on

Are you looking for a clean and reusable way to implement undo for player actions? Perhaps you are making a turn-based …

15 minute read

Advanced Logging with the Strategy Pattern

by on

Have you ever tried debugging a problem with your game that only seems to happen in production? The Developer Tools …

7 minute read

State Pattern for Character Movement in Phaser 3

by on

Writing clean and well-organized code is something all game developers aspire to. We want code to be reusable and easy …

7 minute read

Didn't find what you were looking for?


comments powered by Disqus