How to Make a Particle Trail Effect in Phaser 3

Add sparkles and juiciness to interactions with particle trails

by on 7 minute read


Does your game feel too static or not reactive enough?

Do coins just disappear when the player collects them?

There's just something unsatisfying about that interaction, right?

A greater feeling of liveliness would be beneficial and particles are a great way to add it!

In this article, we will look at using a particle trail from a collected coin to the score counter in the UI.

We'll be using the Rocket Mouse game from our Infinite Runner in Phaser 3 with TypeScript book.

You can get it for free by dropping your email into the box below. It is a great resource to have if you are looking to use TypeScript with Phaser 3!

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.

Create a ParticleEffects Scene

The Rocket Mouse infinite runner game is always scrolling but the particle trail effect that we will be making plays in the UI space that should not scroll with the game environment.

We can try to calculate offsets based on the camera's scrollX property but it would be much simpler to create a new Scene for the particle trails.

So let's create a ParticleEffects.ts file in the scenes folder like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import Phaser from 'phaser'
import SceneKeys from '../consts/SceneKeys'

export default class ParticleEffects extends Phaser.Scene
{
	constructor()
	{
		super(SceneKeys.ParticleEffects)
	}
}

Notice that we also added a new enum member to SceneKeys that should look like this:

1
2
3
4
5
6
7
8
enum SceneKeys
{
	// other keys...

	ParticleEffects = 'particle-effects'
}

export default SceneKeys

Next, let's add the ParticleEffects Scene to the scene list in our GameConfig:

1
2
3
4
5
6
7
8
9
// other imports...

import ParticleEffects from './scenes/ParticleEffects'

const config: Phaser.Types.Core.GameConfig = {
	// other options...

	scene: [Preloader, Game, GameOver, ParticleEffects]
}

Now, we can run this Scene as soon as the Game Scene starts in the init() method like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// imports...

export default class Game extends Phaser.Scene
{
	// properties and constructor...

	init()
	{
		this.score = 0
		this.cursors = this.input.keyboard.createCursorKeys()

		this.scene.run(SceneKeys.ParticleEffects)
	}

	// other code...
}

Add a create() method to the ParticleEffects Scene with a console.log('hello!') to make sure it is being run.

Send a Message on Coin Collect

The goal is to create a particle trail that goes to the score counter each time a coin is collected and keep all that particle logic contained.

To achieve this goal we will pass a message to the ParticleEffects Scene in the handleCollectCoin() method.

Let's do that by getting a reference to the ParticleEffects Scene and then emitting an event.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private handleCollectCoin(obj1: Phaser.GameObjects.GameObject, obj2: Phaser.GameObjects.GameObject)
{
	// other code...

	const particleEffects = this.scene.get(SceneKeys.ParticleEffects)
	particleEffects.events.emit('trail-to', {
		fromX: coin.x - this.cameras.main.scrollX,
		fromY: coin.y,
		toX: this.scoreLabel.x + this.scoreLabel.width * 0.5,
		toY: this.scoreLabel.y + this.scoreLabel.height * 0.5
	})
}

A reference to the ParticleEffects Scene is retrieved with this.scene.get(SceneKeys.ParticleEffects) and stored in particleEffects.

Then we emit a 'trail-to' event using the Scene's EventEmitter with data about where the trail should start from and go to.

The main camera's scrollX is used to offset the fromX value and then the toX and toY values are calculated based on where this.scoreLabel is.

Next, we have to listen for the 'trail-to' event in the ParticleEffects Scene and create the particle trail effect.

Creating a Particle Trail

We've added a star.png file to use as particles. Pick an image of your own and preload it like this:

1
2
3
4
5
6
7
8
// Preloader.ts

preload()
{
	// other assets...

	this.load.image(TextureKeys.ParticleStar, 'effects/star.png')
}

We are using the enum value ParticleStar. Remember to add that to TextureKeys like this:

1
2
3
4
5
6
7
8
enum TextureKeys
{
	// other members...

	ParticleStar = 'particle-star'
}

export default TextureKeys

Next, we need to create a ParticleEmitterManager. Let's do that in the create() method like this:

1
2
3
4
create()
{
	const particles = this.add.particles(TextureKeys.ParticleStar)
}

Then, listen for the 'trail-to' event and create a ParticleEmitter:

 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
// add to top of file to help with typing
interface TrailToData
{
	fromX: number
	fromY: number
	toX: number
	toY: number
}

// then in the create method...
create()
{
	const particles = this.add.particles(TextureKeys.ParticleStar)

	this.events.on('trail-to', (data: TrailToData) => {
		const emitter = particles.createEmitter({
			x: data.fromX,
			y: data.fromY,
			quantity: 5,
			speed: { random: [50, 100] },
			lifespan: { random: [200, 400]},
			scale: { random: true, start: 1, end: 0 },
			rotate: { random: true, start: 0, end: 180 },
			angle: { random: true, start: 0, end: 270 },
			blendMode: 'ADD'
		})
	})
}

The important new code is on lines 15 - 27 where we listen for the event and create a ParticleEmitter. The fromX and fromY values are used as the starting point and then the rest of the properties can be adjusted for a different look and feel.

We also added an interface called TrailToData to help us with typing information in the event callback.

Try the game now and you'll see particles created in place of a collected coin.

To animation the particle trail we will create a counter tween that generates values to move along a Catmull-Rom spline.

 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
create()
{
	const particles = this.add.particles(TextureKeys.ParticleStar)

	this.events.on('trail-to', (data: TrailToData) => {
		// previous code...

		const xVals = [data.fromX, 300, 100, data.toX]
		const yVals = [data.fromY, 100, 150, data.toY]
		
		this.tweens.addCounter({
			from: 0,
			to: 1,
			ease: Phaser.Math.Easing.Sine.InOut,
			duration: 1000,
			onUpdate: tween => {
				const v = tween.getValue()
				const x = Phaser.Math.Interpolation.CatmullRom(xVals, v)
				const y = Phaser.Math.Interpolation.CatmullRom(yVals, v)

				emitter.setPosition(x, y)
			},
			onComplete: () => {
				emitter.explode(50, data.toX, data.toY)
				emitter.stop()

				this.time.delayedCall(1000, () => {
					particles.removeEmitter(emitter)
				})
			}
		})
	})
}

First, we create 2 arrays to hold the positions for the Catmull-Rom spline to use called xVals and yVals.

Then we create a counter tween with this.tweens.addCounter() that goes from 0 to 1 with a duration of 1 second using a Sine.InOut ease.

For each onUpdate() we calculate the Catmull-Rom position at the given value between 0 and 1. The calculated position is then used to move the emitter to the score counter UI.

When the counter tween is finished, we perform a particle explosion and remove the emitter after 1 second. This cleanup step is important since we are creating a new ParticleEmitter each time the 'trail-to' event is fired.

The effect should look something like this:

Final Cleanup

The last thing we need is to stop listening for the 'trail-to' event when the ParticleEffects Scene is shutdown.

1
2
3
4
5
6
7
8
create()
{
	// previous code...

	this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => {
		this.events.off('trail-to')
	})
}

Next Steps

Particle trails are an effective way to help players read a user interface by pointing out meters that relate to a collectible.

It also adds a feeling of liveliness and some extra eye candy. ✨

Try adjusting the values in the ParticleEmitter config and the ones given to the Catmull-Rom spline to achieve a different look & feel!

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 particles typescript particle trail

Want tips and techniques more suited for you?


You may also like...


Video Guides


Beginner Guides


Articles Recommended For You

How to Load Images Dynamically in Phaser 3

by on

Does your game have a lot of images that not every player sees? Maybe a collectible card game? Loading those images …

6 minute read

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

Didn't find what you were looking for?


comments powered by Disqus