DOM Element Button in Phaser 3 with JSX and TypeScript

Leverage CSS frameworks in Phaser 3

by on 5 minute read


Phaser is a great HTML5 game framework that can leverage much of the ecosystem around developing web applications.

One of the core pieces of web apps is the UI and there is no shortage of UI frameworks you can use.

In this article, we will look at using Bulma to power buttons in a Phaser game.

JSX will be used to let us write HTML in our code. Doing so better matches how CSS frameworks are used and makes the HTML easier to reason about.

The examples in this article assumes using the phaser3-parcel-template and TypeScript.

Using JSX without React

JSX lets you write HTML that gets transpiled into the corresponding JavaScript code. It is largely associated with the React project but we can use it without React.

First, install the jsx-dom library that will convert JSX to DOM elements instead of React components.

npm install --save jsx-dom

Next, we have to add a shim for React.createElement using jsx-dom as Parcel assumes we are making a React app and doesn't expose a way to use a different JSX factory–at least I didn't find a way 😅.

Add this code to a file named jsx-dom-shim.ts or similar.

1
2
3
4
5
import * as React from 'jsx-dom'

window['React'] = React

export default React

Then include this file as another entry point above main.ts in your index.html. This will ensure React properly exists in the global namespace before our code runs.

1
2
3
4
5
6
<html>
	<body>
		<script src="jsx-dom-shim.ts"></script>
		<script src="main.ts"></script>
	</body>
</html>

Creating a JSX Button

We will be using Bulma and Material Design Icons so add their respective CDN distributions to the <head> of your index.html.

<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
<link rel="stylesheet" href="//cdn.materialdesignicons.com/4.4.95/css/materialdesignicons.min.css">

You can also use Bootstrap and FontAwesome or any combination of CSS + Icon framework.

Any JSX code should use the .jsx or .tsx extension.

Create a file named ButtonJSX.jsx and add this JSX button code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const button = (
	<button class="button is-primary is-large">
		<span class="icon">
			<i class="mdi mdi-gamepad"></i>
		</span>
		<span>Play</span>
	</button>
)

export default button

All the HTML surrounded by parentheses will be transpiled to JavaScript. An HTMLElement instance will be given to us when we import this in another file.

Leveraging CSS frameworks allow us to write a lot less code to create and style a button.

One caveat to consider is that all elements created from CSS frameworks will render above your game. There is no way to layer them between Phaser GameObjects.

Using ButtonJSX in a Scene

The Phaser.GameObjects.DOMElement class lets us create a GameObject from any HTMLElement.

The resulting DOMElement instance can be then be positioned, scaled, rotated, and generally used like any other GameObject.

But before we can use this.add.dom() to add DOMElements to our Scene we need to turn it on in our game config.

1
2
3
4
5
6
7
8
9
const config: Phaser.Types.Core.GameConfig = {
	// ...
	parent: 'phaser',
	dom: {
		createContainer: true
	}
}

const game = new Phaser.Game(config)

Notice line 3 where we set parent: 'phaser'. We need to specify the id of the element for Phaser to render into or it will not create a DOM container as specified by line 6.

We can add a div to our index.html like this:

1
2
3
4
5
<body>
	<div id="phaser"></div>
	<script src="jsx-dom-shim.ts"></script>
	<script src="main.ts"></script>
</body>

Now we can use this.add.dom() to create our button.

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

import buttonJsx from '../buttons/ButtonJSX'

export default class ButtonDomDemo extends Phaser.Scene
{
	constructor()
	{
		super('button-dom-demo')
	}

	preload()
    {
		this.load.image('gem', 'assets/gemRed.png')
    }

    create()
    {
		const gems = this.physics.add.group({
			classType: Phaser.Physics.Arcade.Image
		})
		this.physics.add.collider(gems, gems)

		const button = this.add.dom(400, 300, buttonJsx)
		button.addListener('click').on('click', () => {
			const gem: Phaser.Physics.Arcade.Image = gems.get(button.x, button.y - 50, 'gem')
			gem.setVelocity(Phaser.Math.Between(-100, 100), Phaser.Math.Between(-100, 100))
			gem.setBounce(1, 1)
			gem.setCollideWorldBounds()	
		})
    }
}

The general setup should look similar to our previous two articles on buttons: Basic Button and Container Button.

We import our JSX button on line 3 as buttonJsx and then use this.add.dom() to create it on line 24.

Then we attach a listener to the native click event on line 25.

Notice the call to button.addListener(). This adds the native DOM click event to Phaser's event system so that we can use it with on() like other GameObjects.

Run the Scene and it should look something like this:

Next Steps

There are many CSS frameworks you can use to add UI controls with just a few lines of code!

Anything from radio buttons to tabs! The web UI ecosystem is your oyster.

We did not use RxJS like we had previously but setting that up should be fairly straight forward using the examples from Basic Button and Container Button.

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 game making power-ups. Drop your email in 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.

We’ll be creating more Phaser 3 tutorials so let us know if there’s anything you’d like to see!

Phaser 3 Buttons DOM RxJS TypeScript

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