Creating a Flashlight or Spotlight Effect in Phaser 3

Using a RenderTexture and alpha masks

by on 6 minute read


Creating a spotlight or flashlight effect in Phaser 3 is fairly simple once you know how to use alpha masks.

You can also use this technique for a “magic lens” effect or anything that involves revealing hidden information.

If you are not familiar with using alpha masks in Phaser 3 then check out this first.

In this article, we will be building on the code example from our Reveal Effect in Phaser 3 with Alpha Masks to create a flashlight effect that illuminates a darkened Phaser logo.

How it Works

A flashlight effect is fundamentally very similar to a scratch-off effect except you don't do any scratching and the RenderTexture discards state before being drawn into.

We will use the same setup as we did in the Reveal Effect in Phaser 3 with Alpha Masks article where two Phaser logos were placed on top of each other.

The one on top is the “cover” image and the one below is the “reveal” image. We will tint the cover image a dark blue to give it a night feel.

Our brush will be changed to a simple circle instead of an image to create a stark contrast between lit and unlit. This is just a matter of design and you can use an image if you want.

The effect is created by using alpha masks to hide parts of the cover image that will be shown on the reveal image when the mouse moves over it.

Setting Up the Scene

First, we will create a simple Scene that loads and creates 2 Phaser logo images.

 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'

const KEY_PHASER_LOGO = 'phaser-logo'

export default class RevealLightScene extends Phaser.Scene
{
	constructor()
	{
		super('reveal-light')
	}

	preload()
	{
		this.load.image(KEY_PHASER_LOGO, 'assets/phaser-logo.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(0x004c99)
	}
}

Everything should be pretty straight forward.

Next, we add a RenderTexture and masks.

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

const KEY_PHASER_LOGO = 'phaser-logo'

export default class RevealLightScene extends Phaser.Scene
{
	// ...

	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(0x004c99)

		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)
	}
}

So far this is the same as what we did in the Reveal Effect in Phaser 3 with Alpha Masks article.

The difference comes in how we handle mouse input and creating the circle that represents light.

Light and Action

We need to draw into our RenderTexture for the alpha masks to do anything interesting.

So let's create a circle to represent the light from a spotlight or flashlight.

 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'

const KEY_PHASER_LOGO = 'phaser-logo'

export default class RevealLightScene extends Phaser.Scene
{
	constructor()
	{
		super('reveal-light')

		this.light = 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(0x004c99)

		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.light = this.add.circle(0, 0, 30, 0x000000, 1)
		this.light.visible = false
	}
}

We create a circle on line 46 with a radius of 30 and alpha of 1. You can change these values to create a larger, smaller, or dimmer light.

For example, adjusting the alpha value from 1 to 0.5 will make it half as bright.

The light's visible property is set to false because it does not need to be displayed. We will only use it to draw into our RenderTexture.

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

const KEY_PHASER_LOGO = 'phaser-logo'

export default class RevealLightScene extends Phaser.Scene
{
	constructor()
	{
		super('reveal-light')

		this.light = 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(0x004c99)

		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.light = this.add.circle(0, 0, 30, 0x000000, 1)
		this.light.visible = false

		this.input.on(Phaser.Input.Events.POINTER_MOVE, this.handlePointerMove, this)

		this.renderTexture = rt
	}

	handlePointerMove(pointer)
	{
		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.clear()
		this.renderTexture.draw(this.light, x, y)
	}
}

We add a listener for the POINTER_MOVE event on line 50 and handle it in handlePointMove(pointer) on line 55.

Then on line 60, we clear the RenderTexture before drawing this.light into it.

This is a key difference from the scratch to reveal mechanic that keeps all previous changes to the RenderTexture.

For a light reveal effect we want to make sure that any non-illuminated parts reset to being hidden.

You should see something that looks like this:

Next Steps

You now have the tools to create a flashlight or spotlight effect that can be used to reveal things shrouded in darkness!

What else you can do with this technique is up to your imagination!

A pitch-black Scene can be explored by flashlight if you don't use a cover image at all. Maybe there's a horror game here somewhere? 🤔

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

Create an Animated Health Bar in Phaser 3

by on

Using simple numbers to show health is great for prototypes. It is quick to create when you are testing gameplay. But …

6 minute read

Add Pizazz with Parallax Scrolling in Phaser 3

by on

Are you looking to create a sense of depth or add a more immersive and believable experience to a 2D game? You may have …

6 minute read

How to Make a Particle Trail Effect in Phaser 3

by on

Does your game feel too static or not reactive enough? Do coins just disappear when the player collects them? A greater …

7 minute read

State Pattern for Changing AI and Player Control in Phaser 3

by on

Are you wondering about code structure and best practices? You've probably come across design patterns but which design …

10 minute read

Didn't find what you were looking for?


comments powered by Disqus