How to Make Enemy Sprite Track the Player in Phaser 3

Use this to have enemies, opponents, or just NPCs look at the player in a top-down game.

by on 4 minute read


Are you making a top-down game where enemies or opponents should track or keep their eye on the player?

Maybe you have a turret that should face a moving target before firing?

You know that you need to rotate the turret but how do you know what that angle should be?

If you remember your trigonometry classes then you can probably figure it out but Phaser can do the math work for us!

In this article, we will show you how to get that angle using the player's position and the position of the enemy that should face the player.

Angle from Points

You can get an angle from 2 points using Phaser.Math.Angle.Between().

That means we just need the player's position and the enemy's position.

Our code to get an angle from those two points would look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// target or player's x, y
const tx = this.target.x
const ty = this.target.y

// enemy's x, y
const x = this.x
const y = this.y

const rotation = Phaser.Math.Angle.Between(x, y, tx, ty)

// use rotation...

Another way to accomplish the same thing is to use a Vector2 from enemy position to player position like this:

1
2
3
4
5
6
// same variables defined as above

const vec = new Phaser.Math.Vector2(tx - x, ty - y)
const rotation = vec.angle()

// use rotation...

Either implementation will do what you need but we'll provide a full example with zombies to help you see how it can be used within a Phaser game.

Zombies Example

We are going to put the rotation logic above into a custom Zombie class so that it will rotate to always look at the player whether the player is stationary or moving.

The code is in TypeScript.

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

export default class Zombie extends Phaser.GameObjects.Sprite implements IZombie
{
	private target?: Phaser.GameObjects.Components.Transform

	constructor(scene: Phaser.Scene, x: number, y: number, texture: string)
	{
		super(scene, x, y, texture)
	}

	setTarget(target: Phaser.GameObjects.Components.Transform)
	{
		this.target = target
	}

	update(t: number, dt: number)
	{
		if (!this.target)
		{
			return
		}

		const tx = this.target.x
		const ty = this.target.y

		const x = this.x
		const y = this.y

		const rotation = Phaser.Math.Angle.Between(x, y, tx, ty)
		this.setRotation(rotation)
	}
}

Our Zombie extends from Phaser.GameObjects.Sprite and implements the IZombie interface that we define. It just requires a setTarget(target: Phaser.GameObjects.Components.Transform) method.

Then the update(t: number, dt: number) method should look familiar. We use the resulting rotation on line 32 with this.setRotation(rotation) to rotate the Zombie.

Creating Zombies in a Scene

Our Scene will have 4 zombies placed near each corner and then a 5th one that randomly picks a position.

The player will be represented by a white circle. The keyboard input code to move the player is omitted from the example to focus on the zombies.

 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'
import Zombie from './Zombie'

export default class HelloWorldScene extends Phaser.Scene
{
	private zombies?: Phaser.GameObjects.Group
	private player?: Phaser.GameObjects.Arc

	// ...

	preload()
    {
		this.load.image('zombie1', 'assets/zombie1_hold.png')
		this.load.image('zombie2', 'assets/zombie2_hold.png')
    }

    create()
    {
		this.zombies = this.add.group({
			classType: Zombie,
			runChildUpdate: true
		})

		// one zombie at each corner
		this.zombies.get(200, 150, 'zombie1')
		this.zombies.get(600, 450, 'zombie2')
		this.zombies.get(200, 450, 'zombie1')
		this.zombies.get(600, 150, 'zombie2')

		// random positioned zombie
		this.zombies.get(
			Phaser.Math.Between(100, 700),
			Phaser.Math.Between(100, 500),
			'zombie2'
		)

		this.player = this.add.circle(400, 300, 10, 0xffffff, 1)

		// set each zombie's target to be the player
		this.zombies.children.each(child => {
			const zombie = child as IZombie
			zombie.setTarget(this.player!)
		})
	}

	// update...
}

We are using a Group to let Phaser handle creating and adding new Zombie instances to the Scene. You can use new Zombie(...) as well and then manually add them to the Scene.

The important part is on line 42 where we set each zombie's target to be the player.

This is what the end result looks like:

Next Steps

From here you can use distance to determine when a zombie should face and try to attack the player.

In the case of a turret, you can start firing bullets, lasers, or other projectiles at the player.

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 trigonometry zombie turret track

Want tips and techniques more suited for you?


You may also like...


Video Guides


Beginner Guides


Articles Recommended For You

Scene Transition with Fade Out in Phaser 3

by on

Are you looking for a quick way to improve Scene transitions? A hard cut from one Scene to another is appropriate at …

6 minute read

Typewriter Effect for Text and BitmapText in Phaser 3

by on

Are you making a story-driven game like an RPG or interactive novel? Having text show up one character at a time is …

6 minute read

Firebase Leaderboard with Rex Plugins in Phaser 3

by on

Are you looking to add more replayability to your game? Leaderboards are a long-standing feature of single-player games …

9 minute read

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

Didn't find what you were looking for?


comments powered by Disqus