Use Spine with Arcade Physics in Phaser 3

Adding Arcade Physics to a SpineGameObject may not work well for complex animations

by on 5 minute read


If you've tried adding an Arcade Physics Body to a complex Spine animation like the SpineBoy example then you probably ran into problems.

First, scaling down SpineBoy probably resulted in a bounding box with the wrong size.

Then changing direction by inverting scaleX moved SpineBoy out of the bounding box completely. 😬

And if you solved those problems then the physics box kept bouncing up and down. 😭

Something like this perhaps?

We can solve this by using Phaser's Container class!

SpinePlugin and TypeScript

It is assumed that you've got the SpinePlugin installed and working in your project.

Check out this article if you need help with that.

We will be using TypeScript in this example but the concepts are the same for vanilla or modern JavaScript.

Using a Container

A Phaser.GameObjects.Container lets us put multiple GameObjects under a single parent.

This technique is similar to the one we used for making a homing missle with Arcade Physics.

We will put a SpineGameObject in a Container and then add an Arcade Physics Body to the Container instead of directly on the SpineGameObject.

This lets us adjust and control the physics box regardless of what the Spine animation is doing.

Here's the starting point for our SpineContainer:

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

export default class SpineContainer extends Phaser.GameObjects.Container
{
	private sgo: SpineGameObject

	get spine()
	{
		return this.sgo
	}

	constructor(scene: Phaser.Scene, x: number, y: number, key: string, anim: string, loop = false)
	{
		super(scene, x, y)

		this.sgo = scene.add.spine(0, 0, key, anim, loop)

		scene.physics.add.existing(this)

		const bounds = this.sgo.getBounds()
		const width = bounds.size.x
		const height = bounds.size.y
		this.setPhysicsSize(width, height)

		this.add(this.sgo)
	}

	faceDirection(dir: 1 | -1)
	{
		if (this.sgo.scaleX === dir)
		{
			return
		}

		this.sgo.scaleX = dir
	}

	setPhysicsSize(width: number, height: number)
	{
		const body = this.body as Phaser.Physics.Arcade.Body
		body.setOffset(width * -0.5, -height)
		body.setSize(width, height)
	}
}

We create a SpineGameObject on line 16 and store it in a class property that can be retrieved with the spine getter.

Then we add a physics body to the SpineContainer on line 18. Notice that we've separated the physics box from the SpineGameObject.

The faceDirection(dir: 1 | -1) method on line 29 is used to change which direction the SpineGameObject is facing so that we can move left or right.

There is a setPhysicsSize(width: number, height: number) on line 39 to allow changing the physics box if using the default bounds from the SpineGameObject is not quite right.

Integrate with GameObjectFactory

We like to keep our code idiomatic to Phaser so we will integrate with the GameObjectFactory.

Check out this article for more details on how to do that.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
declare global
{
	interface ISpineContainer extends Phaser.GameObjects.Container
	{
		readonly spine: SpineGameObject
		faceDirection(dir: 1 | -1)
		setPhysicsSize(width: number, height: number)
	}
}

export default class SpineContainer extends Phaser.GameObjects.Container implements ISpineContainer
{
	// class definition...
}

Phaser.GameObjects.GameObjectFactory.register('spineContainer', function (this: Phaser.GameObjects.GameObjectFactory, x: number, y: number, key: string, anim: string, loop = false) {
	const container = new SpineContainer(this.scene, x, y, key, anim, loop)

	this.displayList.add(container)

	return container
})

We are creating an interface called ISpineContainer that we will use to let TypeScript know about this.add.spineContainer in the snippet below.

Then we add an implements call to the SpineContainer class and add the logic to integrate with the GameObjectFactory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// put this in a spine.d.ts or similar
declare namespace Phaser.GameObjects
{
	interface GameObjectFactory
	{
		spineContainer(x: number, y: number, key: string, anim: string, loop?: boolean): ISpineContainer
	}

	interface Container
	{
		add(go: SpineGameObject): Phaser.GameObjects.Container
	}
}

This uses Declaration Merging to let TypeScript know about the new spineContainer method we registered.

The second addition to the Container interface removes the error when we try to add a SpineGameObject to it.

Use in a Scene

Be sure to import the SpineContainer class in your Scene and then you can use it in create like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import './SpineContainer'

// then later...
create()
{
	this.boy = this.add.spineContainer(400, 550, 'spineboy', 'idle', true)
	this.boy.setScale(0.5)
	const body = this.boy.body as Phaser.Physics.Arcade.Body
	body.setCollideWorldBounds(true)
	this.boy.setPhysicsSize(body.width * 0.5, body.height * 0.9)
}

You can then get a reference to the underlying SpineGameObject using the spine property of SpineContainer and handle changing animations to get something that looks like this:

The physics box stays in place and behaves as you'd expect. No erratic shifting, bouncing, or floating!

Next Steps

The example code is tailored to the SpineBoy asset. Specifically, the setPhysicsSize() method that is assuming the anchor or origin point is at the bottom by the feet.

Different Spine animations will probably need that adjusted or the physics box won't end up where you'd expect.

For example, a centered origin point would want width and height divided in half.

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 Spine Physics Arcade Physics

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