Organize Phaser 3 Code with Custom GameObjectFactory Methods

Use modern JavaScript or TypeScript classes while preserving Phaser's readable code style

by on 5 minute read updated on


Are you at a point where you've made a few simple games in Phaser and generally know your way around?

And now you want to make a bigger game with better practices that allow for maintainable code?

One strategy is to use modern JavaScript or TypeScript.

With that strategy comes the traditional Object Oriented approach of organizing code with classes.

But Phaser code has a nice, readable style with few uses of new:

1
2
3
4
5
6
create()
{
	const slime = this.add.sprite(400, 300, 'slime')

	// use the sprite here
}

It tells us in clear, concise English that we are adding a Sprite. 👏

So how do we keep our code nice to read and use custom classes?

In this article, we will show how to do that with the GameObjectFactory.

Custom GameObject Class

Let's make a simple Slime enemy class that can change color. This example assumes the 'slime' key is associated with a preloaded image.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import Phaser from 'phaser'

export default class Slime extends Phaser.GameObjects.Sprite
{
	constructor(scene, x, y)
	{
		super(scene, x, y, 'slime')
	}

	changeColor()
	{
		this.tint = 0xffee0d
	}

	// ... other methods and actions
}

At this point, you can use this class in a Scene with this.add.existing like this:

1
2
3
4
5
6
7
8
9
// at the top of the Scene file
import Slime from './Slime'

// then in the Scene class
create()
{
	const slime = new Slime(this, 400, 300)
	this.add.existing(slime)
}

Check out the source to see how this.add.existing works and you'll notice that items may be added to the Scene Systems’ displayList or updateList.

Using this.add.existing is a convenience method to keep things simple.

An alternative is to manually add slime to the Scene Systems’ displayList and updateList.

Integrate with GameObjectFactory

Given this information, we can hack together a function that will create and add an instance of Slime to the Scene.

But that won't be necessary because Phaser has a system for this called the GameObjectFactory. 🎉

It has a register function for adding creation methods. This will allow us to use them with this.add.

1
2
3
4
5
6
7
8
9
// at the bottom of the Slime.js file
Phaser.GameObjects.GameObjectFactory.register('slime', function (x, y) {
	const slime = new Slime(this.scene, x, y)

    this.displayList.add(slime)
    this.updateList.add(slime)

    return slime
})

The first parameter is the key that will be used with this.add. In the example above it is 'slime' which means we will be able to use it in the Scene as this.add.slime().

A Slime instance is created and then added to the displayList and updateList like we saw in the code for this.add.existing.

We didn't bother including the if checks because we know our Slime class extends from Sprite and will need to be displayed and updated.

The best way to check what other code may be needed is to see what Phaser does for the GameObject type you are subclassing.

Slime subclasses Phaser.GameObjects.Sprite so this is the code for the sprite factory method.

GameObjectFactory.register with TypeScript

If you are using TypeScript then change function signature to look like this:

1
2
3
4
5
6
Phaser.GameObjects.GameObjectFactory.register(
	'slime',
	function (this: Phaser.GameObjects.GameObjectFactory, x: number, y: number) {
		// same logic as JavaScript example
	}
)

Adding this: Phaser.GameObjects.GameObjectFactory lets TypeScript know that this in the anonymous function is bound to Phaser.GameObjects.GameObjectFactory.

VS Code will also provide proper code completion and IntelliSense this way. 🎉

Use in a Scene

The last step is to use our factory function in a Scene.

It is important to remember that we have to import our Slime.js file for the factory function to be registered.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// at the top of our Scene file
import './Slime.js'

// then in the Scene class
create()
{
	const slime = this.add.slime(400, 300)
	slime.changeColor()
}

Now we can create Slime instances in a way that keeps the readable style of Phaser code intact. 🤗

Add Typing for TypeScript

If you are using TypeScript then you'll need to use Declaration Merging to provide type information or you'll get compilation errors.

Add a Slime.d.ts file to your project that looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
declare interface ISlime extends Phaser.GameObjects.Sprite
{
	changeColor(): void
}

declare namespace Phaser.GameObjects
{
	interface GameObjectFactory
	{
		slime(): ISlime
	}
}

The key point is the second half that declares slime(): ISlime in the GameObjectFactory.

Next Steps

One thing to watch out for are key collisions when using the GameObjectFactory. You can overwrite existing keys by Phaser or other plugins by accident and experience unexpected behaviors.

One way to avoid this is to namespace your custom keys like this.add.oc_slime().

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 GameObjectFactory

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