Use Google Fonts in Phaser 3 with Web Font Loader

Use the collection of free fonts from Google and Adobe in your Phaser game!

by on 6 minute read


There are tons of beautiful web fonts we can use for web-based projects–including web-based games–from Google and Adobe.

Phaser is a great framework for making web-based, HTML5 games but it does not handle loading web fonts.

There are a few ways to get web fonts working in your game from hacked together to fully integrated into Phaser as a plugin.

In this article we go just short of making a plugin while still hooking into Phaser's asset management system so that fonts are preloaded before text is created.

We will use the Web Font Loader library along with Phaser's File class.

Create a WebFontFile

The code here will be in Modern JavaScript but you should be able to adapt it as necessary.

As a best practice fonts should be loaded in the Scene's preload() method or the text may not render correctly.

We can do that by using this.load.addFile() which takes a Phaser.Loader.File instance. The File class is intended as an abstract base class so we will extend it in our WebFontFile class.

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

import WebFontLoader from 'webfontloader'

export default class WebFontFile extends Phaser.Loader.File
{
	/**
	 * @param {Phaser.Loader.LoaderPlugin} loader
	 * @param {string | string[]} fontNames
	 * @param {string} [service]
	 */
	constructor(loader, fontNames, service = 'google')
	{
		super(loader, {
			type: 'webfont',
			key: fontNames.toString()
		})

		this.fontNames = Array.isArray(fontNames) ? fontNames : [fontNames]
		this.service = service
	}

	load()
	{
		const config = {
			active: () => {
				this.loader.nextFile(this, true)
			}
		}

		switch (this.service)
		{
			case 'google':
				config['google'] = {
					families: this.fontNames
				}
				break

			default:
				throw new Error('Unsupported font service')
		}
		
		WebFontLoader.load(config)
	}
}

We are importing WebFontLoader on line 3. You will have to have run npm install webfontloader in order for this import to work. Alternatively, you can omit the import and load WebFontLoader from a CDN.

See the instructions on the WebFontLoader Github page.

Phaser.Loader.File has several methods you can override but the most important one is load(). It is where we have WebFontLoader do its magic. Once the fonts are loaded we let Phaser know on line 27.

In this example, we only implemented support for Google Fonts and will throw an error for any other service.

Note: There are shortcomings to this basic example that we will discuss in the Advanced Example section.

Using WebFontFile in a Scene

Now let's give WebFontFile a try by preloading a font and creating some text.

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

import WebFontFile from '../files/WebFontFile'

export default class CustomFontDemo extends Phaser.Scene
{
	constructor()
	{
		super('custom-font')
	}

	preload()
	{
		this.load.addFile(new WebFontFile(this.load, 'Press Start 2P'))
	}

	create()
	{
		const title = this.add.text(400, 300, 'Hello World!', {
			fontFamily: '"Press Start 2P"',
			fontSize: '50px'
		})

		title.setOrigin(0.5, 0.5)
	}
}

Note: For fonts with spaces in the name we need to include double quotes as you see on line 20.

In preload() we use this.load.addFile and pass in a WebFontFile instance that will load Press Start 2P from Google Fonts. Then we create text that says Hello World! with Press Start 2P as the desired font on line 20 in create().

We are using the phaser3-parcel-template to bootstrap our project with a game window size of 800 x 600 so our Scene looks like this:

Hello World

You can also load multiple fonts by using an array like this:

this.load.addFile(new WebFontFile(this.load, [
	'Press Start 2P',
	'Lato',
	'Roboto'
]))

Advanced Example

The basic WebFontFile above should work fine in most cases. One thing it does not handle is being used more than once:

this.load.addFile(new WebFontFile(this.load, 'Press Start 2P'))
this.load.addFile(new WebFontFile(this.load, 'Lato'))

Generally, you will want to use an array of names when loading multiple fonts. This allows WebFontLoader to make a batched request and get all the fonts in one network call.

That said, it doesn't hurt to have a more robust version of WebFontFile!

 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import Phaser from 'phaser'

import WebFontLoader from 'webfontloader'

export default class WebFontFile extends Phaser.Loader.File
{
	/**
	 * @param {Phaser.Loader.LoaderPlugin} loader
	 * @param {string | string[]} fontNames
	 * @param {string} [service]
	 */
	constructor(loader, fontNames, service = 'google')
	{
		super(loader, {
			type: 'webfont',
			key: fontNames.toString()
		})

		this.fontNames = Array.isArray(fontNames) ? fontNames : [fontNames]
		this.service = service

		this.fontsLoadedCount = 0
	}

	load()
	{
		const config = {
			fontactive: (familyName) => {
				this.checkLoadedFonts(familyName)
			},
			fontinactive: (familyName) => {
				this.checkLoadedFonts(familyName)
			}
		}

		switch (this.service)
		{
			case 'google':
				config[this.service] = this.getGoogleConfig()
				break

			case 'adobe-edge':
				config['typekit'] = this.getAdobeEdgeConfig()
				break

			default:
				throw new Error('Unsupported font service')
		}
		

		WebFontLoader.load(config)
	}

	getGoogleConfig()
	{
		return {
			families: this.fontNames
		}
	}

	getAdobeEdgeConfig()
	{
		return {
			id: this.fontNames.join(';'),
			api: '//use.edgefonts.net'
		}
	}

	checkLoadedFonts(familyName)
	{
		if (this.fontNames.indexOf(familyName) < 0)
		{
			return
		}

		++this.fontsLoadedCount
		if (this.fontsLoadedCount >= this.fontNames.length)
		{
			this.loader.nextFile(this, true)
		}
	}
}

We made changes to use the fontactive callback instead of active to check if the specific fonts given in the constructor have been loaded and only say we are finished when that has happened.

The callback change is on line 28 and the logic to check if the specific fonts are loaded starts on line 68 in checkLoadedFonts(familyName).

We also hook into fontinactive because the Internet does not always work and a font may fail to load. Odds are we should continue even though the font is missing. At least the game won't be frozen. 😅

There are more sophisticated ways to handle this problem like retries or fallback fonts but we'll leave that to you. 😎

Lastly, we implemented support for Adobe Edge Web Fonts on lines 42 and 60 - 66. You can use it like this:

this.load.addFile(new WebFontFile(this.load, 'shojumaru', 'adobe-edge'))

Next Steps

If you want to use TypeKit, Fonts.com, or some other service then you need to implement support for them. Just follow the structure described by WebFontLoader.

You can get a Gist of the advanced WebFontFile example 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.

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 Google Fonts Web Fonts Typekit 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