Make a Homing Missile that Seeks a Target Using Arcade Physics in Phaser 3

Classic enemy behavior for space shooters!

by on 5 minute read


If you are making a space shooter game in Phaser 3 with Arcade Physics and tried to add a homing or target seeking missile then you've probably run into a problem with the physics bounding box.

It doesn't rotate with the GameObject so collisions won't work properly. This is by design

One solution is to use Matter Physics but that could mean changing a lot of existing code. And potentially breaking many things in the process.

There is another option if you can live with a missile that only registers collisions when the tip or front hits a target.

You can find the example project on Github.

Using a Container

This trick uses a Container with a circle physics body that holds a missile image.

Missile Image Requirement

The missile image has to be facing or pointing to the right. If your image faces a different direction then you can account for it in code but it is simpler to rotate it in PhotoShop.

Phaser uses a right-hand clockwise rotation system.

The Container is never rotated. We rotate the child Image instead and then the parent Container is set to move based on where the child Image is facing.

Using a circle physics body means we can keep the collision area the same no matter how the missile image is rotated.

Remember! This means the missile has to hit targets head-first for a collision to register.

The Missile Container Class

First, take note of the constructor to see how the Image and circle physics body is setup.

We are using modern JavaScript in this example.

 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'

export default class Missile extends Phaser.GameObjects.Container
{
	constructor(scene, x, y, texture)
	{
		super(scene, x, y)

		this.image = scene.add.image(0, 0, texture)
		this.image.setOrigin(0.8, 0.5)
		this.add(this.image)

		scene.physics.add.existing(this)

		const radius = this.image.height * 0.3
		this.body.setCircle(radius)
		this.image.y += radius
		this.image.x += radius

		const tx = scene.scale.width * 0.5
		const ty = scene.scale.height * 0.5

		this.target = new Phaser.Math.Vector2(tx, ty)

		this.turnDegreesPerFrame = 1.25
		this.speed = 100
		this.trackMouse = false
	}

	setTrackMouse(enabled)
	{
		this.trackMouse = enabled
	}

	update(dt)
	{
		// implementation below...
	}
}

The origin of the Image is set to 0.8, 0.5 on line 10. The x value of 0.8 can be adjusted to accommodate the specific missile graphic and radius size.

A physics body is added to the Container on line 13.

Then we determine the radius on line 15 and use it to make the body a circle. The multiplier value of 0.3 can be adjusted to best fit the specific missile image.

It is important to offset the Image by whatever value the radius is as shown on lines 17 and 18.

Next, the update() method handles adjusting direction based on target position and movement based on which direction the Image is facing.

 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
update(dt)
{
	const target = this.trackMouse ? this.scene.input.activePointer.position : this.target

	const targetAngle = Phaser.Math.Angle.Between(
		this.x, this.y,
		target.x, target.y
	)

	// clamp to -PI to PI for smarter turning
	let diff = Phaser.Math.Angle.Wrap(targetAngle - this.image.rotation)

	// set to targetAngle if less than turnDegreesPerFrame
	if (Math.abs(diff) < Phaser.Math.DegToRad(this.turnDegreesPerFrame))
	{
		this.image.rotation = targetAngle;
	}
	else
	{
		let angle = this.image.angle
		if (diff > 0)
		{
			// turn clockwise
			angle += this.turnDegreesPerFrame
		}
		else
		{
			// turn counter-clockwise
			angle -= this.turnDegreesPerFrame
		}
		
		this.image.setAngle(angle)
	}

	// move missile in direction facing
	const vx = Math.cos(this.image.rotation) * this.speed
	const vy = Math.sin(this.image.rotation) * this.speed

	this.body.velocity.x = vx
	this.body.velocity.y = vy
}

You can change how nimble and fast the missile is by adjusting the turnDegreesPerFrame and speed properties.

Bonus: GameObjectFactory Registration

We can keep our code idiomatic to Phaser by registering our Missile class with the GameObjectFactory. This is optional but our usage example will assume this was done.

Add this to the bottom of the Missile class file.

1
2
3
4
5
6
7
8
9
Phaser.GameObjects.GameObjectFactory.register('missile', function (x, y, texture) {
	const missile = new Missile(this.scene, x, y, texture)

	this.displayList.add(missile)

    this.scene.physics.world.enableBody(missile, Phaser.Physics.Arcade.DYNAMIC_BODY)

	return missile
})

Using a Missile

The Missile class has an update(dt) method that needs to be called. We can access an update loop from the Scene.

Using the Missile class from a Scene will look like this:

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

import Missile from './Missile'

const MISSLE = 'missle'

export default class MissileDemo extends Phaser.Scene
{
	constructor()
	{
		super('missile-demo')

		/** @type {Missile} */
		this.missile = null
	}

	preload()
    {
		this.load.image(MISSLE, 'assets/red_rocket.png')
    }

    create()
    {
		const x = this.scale.width * 0.5
		const y = this.scale.height * 0.5

		this.missile = this.add.missile(x, y, MISSLE)
		this.missile.setTrackMouse(true)
	}
	
	update(t, dt)
	{
		if (!this.missile)
		{
			return
		}

		this.missile.update(dt)
	}
}

The missile is created on line 27 and then mouse location tracking is enabled on the next line.

Notice the call to missile's update(dt) method from the Scene's update(t, dt) method.

And that is how you can make a homing or target seeking missile using Arcade Physics! 🎉

Next Steps

Turn on debug mode to make sure everything is working properly in your code. Make this change to your game config:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const config = {
	physics: {
		default: 'arcade',
		arcade: {
			gravity: { y: 0 },
			debug: true
		}
	},
	//...
}

Don't miss any future Phaser 3 game development tips, tricks, and knowledge-drops by subscribing to our email list!

Just type in your email into the box below and hit the button!

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 gameplay physics arcade physics missile

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