Easily Use Typescript with Phaser 3

Write game code that scales.

by on 5 minute read


JavaScript is great for creating small games but have you ever gone back to an old JavaScript project and had no idea what properties exist on an object or what should be passed to a function?

The only way to figure out what's going on is to console.dir objects or use the debugger. And even then you won't easily figure out what properties are optional or required at a later point.

The odds of introducing a null reference exception or other bug is very high in this state.

There are a few ways to solve this problem:

  • use JSDoc to annotate your objects and functions
  • use a type checker like Flow
  • use TypeScript: a typed superset of JavaScript

All three options are good and in this article, we are going to look at using TypeScript to make your game code more scalable and maintainable.

Note: you can uses tests to mitigate some of the potential issues as well but it won't catch them while you are writing the code.

Simplicity with Parcel

There are many ways to set up a Phaser 3 project with TypeScript depending on what bundler you use or if you decide to hand-roll your setup.

We recommend using Parcel and the phaser3-parcel-template.

Check out our guide to using the template here if you need detailed instructions. Otherwise clone the template to follow along:

git clone https://github.com/ourcade/phaser3-parcel-template.git my-folder-name

cd my-folder-name

npm install

Add a tsconfig

This step is not strictly necessary but it is recommended to support absolute paths, change TypeScript compiler settings, or configure your code editor. Here's a recommended tsconfig.json file. Create it in the project root.

 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
{
	"compilerOptions": {
		"target": "esnext",
		"module": "esnext",
		"strict": true,
		"noImplicitAny": false,
		"noEmit": true,
		"allowJs": true,
		"jsx": "preserve",
		"importHelpers": true,
		"moduleResolution": "node",
		"experimentalDecorators": true,
		"esModuleInterop": true,
		"allowSyntheticDefaultImports": true,
		"sourceMap": true,
		"baseUrl": "./src",
		"paths": {
		  "~/*": ["./*"]
		},
		"typeRoots": [
			"node_modules/@types",
			"node_module/phaser/types"
		],
		"types": [
			"Phaser"
		]
	  },
	  "include": ["src/**/*"]
}

Learn more about <code>tsconfig</code> options here.

Using TypeScript

There is no other set up steps thanks to Parcel! 🎉

The only thing you need to do is change the .js files to .ts and start writing TypeScript.

Here's what we did to main.ts as an example:

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

import HelloWorldScene from '~/scenes/HelloWorldScene'

const config: Phaser.Types.Core.GameConfig = {
	type: Phaser.AUTO,
	width: 800,
	height: 600,
	physics: {
		default: 'arcade',
		arcade: {
			gravity: { y: 200 }
		}
	},
	scene: [HelloWorldScene]
}

export default new Phaser.Game(config)

Notice that we are giving config a type of Phaser.Types.Core.GameConfig on line 5. Now you'll get property suggestions with a code editor like VS Code.

We will move the basic bouncing Phaser logo logic into a class to demonstrate some more TypeScript features!

 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
import Phaser from 'phaser'

export default class BouncingLogo
{
	private image: Phaser.Physics.Arcade.Image

	get display()
	{
		return this.image
	}
	
	constructor(scene: Phaser.Scene, x: number, y: number, texture: string)
	{
		this.image = scene.physics.add.image(x, y, texture)

		this.initialize()
	}

	private initialize()
	{
		this.image.setVelocity(100, 200)
        this.image.setBounce(1, 1)
		this.image.setCollideWorldBounds(true)
	}
}

Here we have a wrapper class that creates the physics image and sets the physics properties in initialize(). We are using the private modifier on a class property and a class method.

We are also specifying type in the constructor arguments. Now you'll always know that texture is a string and not something else like a Texture instance.

Putting it Together

To put it all together we will convert the HelloWorldScene to use TypeScript features and the BouncingLogo we created.

 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
import Phaser from 'phaser'
import BouncingLogo from './BouncingLogo'

enum ImageNames
{
	Sky = 'sky',
	Logo = 'logo',
	RedParticle = 'red_particle'
}

export default class HelloWorldScene extends Phaser.Scene
{
	constructor()
	{
		super('hello-world')
	}

	preload()
    {
        this.load.setBaseURL('http://labs.phaser.io')

        this.load.image(ImageNames.Sky, 'assets/skies/space3.png')
        this.load.image(ImageNames.Logo, 'assets/sprites/phaser3-logo.png')
        this.load.image(ImageNames.RedParticle, 'assets/particles/red.png')
    }

    create()
    {
        this.add.image(400, 300, ImageNames.Sky)

        const emitter = this.createEmitter(ImageNames.RedParticle)
		const logo = new BouncingLogo(this, 400, 100, ImageNames.Logo)

        emitter.startFollow(logo.display)
	}
	
	private createEmitter(textureName: string)
	{
		const particles = this.add.particles(textureName)

        const emitter = particles.createEmitter({
            speed: 100,
            scale: { start: 1, end: 0 },
            blendMode: 'ADD'
		})
		
		return emitter
	}
}

TypeScript supports enums and we create one on line 4 in place of constant variables for unique image names. In modern JavaScript you would use something like: const ImageNameSky = 'sky'.

Then we create our BouncingLogo on line 32 using the ImageNames.Logo enum.

Last thing of note is the private createEmitter(textureName: string) method that has the private access modifier and a typed textureName parameter.

You can test all this out if you haven't already by going into your project folder and using:

npm run start

Open http://localhost:8000 in your browser and the familiar Phaser logo will be bouncing around. 🎉

Next Steps

Using TypeScript with Phaser 3 is a piece of cake when you use Parcel and the phaser3-parcel-template.

You can use all TypeScript features from decorators to generics. Learn more about TypeScript here.

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.

Learn to make an Infinite Runner in Phaser 3 with TypeScript!

Drop your email into the box below to get this free 90+ page book and join our newsletter.

Learn more about the book here.

Phaser 3 TypeScript Parcel

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