Reveal Effect in Phaser 3 with Alpha Masks

An alpha mask approach for reveal effects.

by on 7 minute read


A reveal effect is where an image on top is erased to reveal another image below it.

This can be used for a simple scratch-off mini-game or a more complicated spotlight or flashlight effect.

There are multiple ways to achieve this effect in Phaser 3 and in this article, we will look at using a RenderTexture as an alpha mask.

If your reveal effect is fairly simple then you may be able to use a simpler method discussed here.

How it Works

The basic premise is that we will put two images–a cover image and a reveal image–on top of each other and then mask them both with a RenderTexture.

A RenderTexture is a texture created at runtime that you can draw into.

A normal Texture is like an image that we store on our computers. They are created ahead of time in a graphics program while a RenderTexture does not exist until we run the game and create it in code. It is also empty until we draw into it.

Phaser lets us draw one or more GameObjects into a RenderTexture as many times as we want.

Because we are using a RenderTexture as our alpha mask, the masked area will change as we draw into it. We will be using a brush image–a radial gradient or a transparent image with one dot from a PhotoShop soft brush–to do this.

This process creates the reveal effect.

Setting Up

We will use the Phaser logo as both the cover and reveal images for simplicity. One difference is that the cover image will have a green tint so that we can tell them apart.

Let's set up our Scene.

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

const KEY_PHASER_LOGO = 'phaser-logo'
const KEY_BRUSH = 'brush'

export default class RevealMaskScene extends Phaser.Scene
{
	constructor()
	{
		super('reveal-mask')

		this.cover = null
	}

	preload()
	{
		this.load.image(KEY_PHASER_LOGO, 'assets/phaser-logo.png')
		this.load.image(KEY_BRUSH, 'assets/brush.png')
	}

	create()
	{
		const x = 400
		const y = 300

		const reveal = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover.setTint(0x4bf542)
	}
}

Running the Scene now will show a green-tinted Phaser logo.

Nothing too exciting. 😎

Next, let's get to the interesting stuff by creating a RenderTexture and masking image.

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

const KEY_PHASER_LOGO = 'phaser-logo'
const KEY_BRUSH = 'brush'

export default class RevealMaskScene extends Phaser.Scene
{
	constructor()
	{
		super('reveal-mask')

		this.cover = null
	}

	// ...

	create()
	{
		const x = 400
		const y = 300

		const reveal = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover.setTint(0x4bf542)

		const width = this.cover.width
		const height = this.cover.height

		const rt = this.make.renderTexture({
			width,
			height,
			add: false
		})

		const maskImage = this.make.image({
			x,
			y,
			key: rt.texture.key,
			add: false
		})
	}
}

We make a RenderTexture on line 29 but do not add it to the Scene. Our RenderTexture does not need to be visible.

Instead, it will be used as the underlying texture for our maskImage created on line 35. Note that it is also not added to the Scene.

The maskImage data will only be used as alpha masks for the 2 Image instances: reveal and this.cover.

Using Alpha Masks

Now that we have all the necessary parts we can create and use a BitmapMask.

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

const KEY_PHASER_LOGO = 'phaser-logo'
const KEY_BRUSH = 'brush'

export default class RevealMaskScene extends Phaser.Scene
{
	constructor()
	{
		super('reveal-mask')

		this.cover = null
	}

	// ...

	create()
	{
		const x = 400
		const y = 300

		const reveal = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover.setTint(0x4bf542)

		const width = this.cover.width
		const height = this.cover.height

		const rt = this.make.renderTexture({
			width,
			height,
			add: false
		})

		const maskImage = this.make.image({
			x,
			y,
			key: rt.texture.key,
			add: false
		})

		this.cover.mask = new Phaser.Display.Masks.BitmapMask(this, maskImage)
		this.cover.mask.invertAlpha = true

		reveal.mask = new Phaser.Display.Masks.BitmapMask(this, maskImage)
	}
}

There are only 3 new lines that you need to worry about.

We create a new BitmapMask on line 42 and set it to the mask property of this.cover.

The mask is then set to invertAlpha = true because we want to hide the cover image as we draw into our RenderTexture to show the reveal image. This makes the mask show what should normally hide and hide what should normally show.

Lastly, we create another BitmapMask and set it to the mask property of the reveal image and leave invertAlpha with a default value of false so that it behaves normally.

This will give us the effect where parts of the reveal image become visible while those same parts become hidden in the cover image.

Drawing into a RenderTexture

The last thing we need to do is handle drawing the brush image into our RenderTexture when the mouse is pressed and dragged.

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

const KEY_PHASER_LOGO = 'phaser-logo'
const KEY_BRUSH = 'brush'

export default class RevealMaskScene extends Phaser.Scene
{
	constructor()
	{
		super('reveal-mask')

		this.isDown = false
		this.cover = null
		this.renderTexture = null
	}

	// ...

	create()
	{
		const x = 400
		const y = 300

		const reveal = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover = this.add.image(x, y, KEY_PHASER_LOGO)
		this.cover.setTint(0x4bf542)

		const width = this.cover.width
		const height = this.cover.height

		const rt = this.make.renderTexture({
			width,
			height,
			add: false
		})

		const maskImage = this.make.image({
			x,
			y,
			key: rt.texture.key,
			add: false
		})

		this.cover.mask = new Phaser.Display.Masks.BitmapMask(this, maskImage)
		this.cover.mask.invertAlpha = true

		reveal.mask = new Phaser.Display.Masks.BitmapMask(this, maskImage)

		this.cover.setInteractive()
		this.cover.on(Phaser.Input.Events.POINTER_DOWN, this.handlePointerDown, this)
		this.cover.on(Phaser.Input.Events.POINTER_MOVE, this.handlePointerMove, this)
		this.cover.on(Phaser.Input.Events.POINTER_UP, () => this.isDown = false)

		this.brush = this.make.image({
			key: KEY_BRUSH,
			add: false
		})

		this.renderTexture = rt
	}

	handlePointerDown(pointer)
	{
		this.isDown = true
		this.handlePointerMove(pointer)
	}

	handlePointerMove(pointer)
	{
		if (!this.isDown)
		{
			return
		}

		const x = pointer.x - this.cover.x + this.cover.width * 0.5
		const y = pointer.y - this.cover.y + this.cover.height * 0.5
		this.renderTexture.draw(this.brush, x, y)
	}
}

We create an isDown property on line 12 to keep track of whether the left mouse button is pressed or not.

Then we set this.cover to interactive and add 3 events: POINTER_DOWN, POINTER_MOVE, and POINTER_UP. The handlers for each are fairly simple. handlePointerMove(pointer) is where the drawing logic lives.

Next, we create an Image instance as the brush on line 54 without adding it to the Scene. It will only be used on line 77 to draw into the RenderTexture.

As the RenderTexture is updated, the BitmapMask instances we created on lines 44 and 47 will update and give us a reveal effect.

It will look something like this:

Next Steps

We covered the basic setup for using alpha masks on two different images to create a reveal effect.

This simple alpha mask example should help you get started on more interesting effects.

For example, we will create a spotlight or flashlight effect in the next article. Stay tuned! 👍

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 Scratch-Off Magic Lens Flashlight Masks

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