I recently wrote about handling collisions in a Phaser 3.x game. In this previous tutorial titled, Handle Collisions Between Sprites in Phaser with Arcade Physics, the focus was around the arcade physics engine that Phaser integrates with.
While you should use the arcade physics engine whenever possible, due to its speed and efficiency, sometimes working with box and circle physics bodies isn’t enough.
This is where Matter.js comes in!
Matter.js is another supported physics engine in Phaser 3.x and while it offers quite a bit of functionality that arcade physics doesn’t offer, it also offers custom polygon physics bodies.
In this tutorial, we’re going to explore collisions once more in a Phaser game, but this time with Matter.js and more refined boundaries.
To get an idea of what we are going to build, take a look at the following animated image:
In the above example, we have two sprites, one of which is animated. Both sprites have a custom physics body which is fitted to the image. While this isn’t pixel perfect due to using polygons, it is a lot more refined than what we saw in the previous tutorial. The physics body is only visible for demonstration purposes and can be hidden in a realistic scenario.
For this tutorial we’re not going to explore how to animate sprites even though some code around animation will be included. If you want to learn how to animate a spritesheet that has an atlas file, check out my tutorial titled, Animate a Compressed Sprite Atlas in a Phaser Game.
To get us up to speed when it comes to collisions in a Phaser game, we need to add a foundation to our project. Essentially we need to add some code that was seen in other Phaser 3.x tutorials on the blog.
On your computer, create a new directory with an index.html file that contains the following markup:
<!DOCTYPE html>
<html>
<head>
<script src="//cdn.jsdelivr.net/npm/phaser@3.24.1/dist/phaser.min.js"></script>
</head>
<body>
<div id="game"></div>
<script>
const phaserConfig = {
type: Phaser.AUTO,
parent: "game",
width: 1280,
height: 720,
backgroundColor: "#5DACD8",
scene: {
init: initScene,
preload: preloadScene,
create: createScene,
update: updateScene
}
};
const game = new Phaser.Game(phaserConfig);
var plane, obstacle;
var spritePhysics;
function initScene() { }
function preloadScene() {
this.load.atlas("plane", "plane.png", "plane.json");
this.load.image("obstacle", "obstacle.png");
}
function createScene() {
this.anims.create({
key: "fly",
frameRate: 7,
frames: this.anims.generateFrameNames("plane", {
prefix: "plane",
suffix: ".png",
start: 1,
end: 3,
zeroPad: 1
}),
repeat: -1
});
this.anims.create({
key: "explode",
frameRate: 7,
frames: this.anims.generateFrameNames("plane", {
prefix: "explosion",
suffix: ".png",
start: 1,
end: 3,
zeroPad: 1
}),
repeat: 2
});
}
function updateScene() { }
</script>
</body>
</html>
Once again, the assumption is that you are either familiar with sprite atlas concepts or you’ve seen my previous tutorial on the subject. I’ve even included the spritesheet and the atlas file in the other tutorial if you wanted to use it for this tutorial. For the obstacle, you can use any image.
With the foundation of our project in place, now we can move onto the part of the tutorial that matters.
Before we can add physics to our sprites, we need to define the physics engine that we plan to use in our Phaser 3.x game. To do this, we need to edit the phaserConfig
object in the index.html file:
const phaserConfig = {
type: Phaser.AUTO,
parent: "game",
width: 1280,
height: 720,
backgroundColor: "#5DACD8",
physics: {
default: "matter",
matter: {
debug: true
}
},
scene: {
init: initScene,
preload: preloadScene,
create: createScene,
update: updateScene
}
};
Notice that we’ve defined our default physic engine and we’ve also enabled debug mode for it. Debug mode for this example will outline the physics body on each of our sprites. If you don’t want to see the physics body, just disable debug mode.
This particular example doesn’t use gravity on our sprites. We could choose to disable gravity on a sprite per sprite basis or we can disable gravity for the entire scene. It probably makes sense to do it for the entire scene. This can be done by adding the following to our createScene
function:
function createScene() {
// Animations ...
this.matter.world.disableGravity();
}
Things are about to get potentially more complicated.
To define a custom physics body for our sprites, we need to create a JSON configuration file with the appropriate information. This is similar to the configuration file that acts as our atlas for our spritesheet. You could do this by hand, but I’d strongly recommend against it. I use a tool called PhysicsEditor and it’s by the same creator as TexturePacker. You don’t have to use this tool or you can use a different tool, but I strongly recommend against creating things by hand.
For simplicity, you can download my sprite-physics.json file.
Do note that the configuration information matches the spritesheet from my previous tutorial for the plane. It also matches an obstacle that I didn’t provide. Even so, the file is enough to get a general idea from.
So let’s add some Matter.js powered sprites!
Within the preloadScene
function, we need to add the JSON file that represents our physics data. To do this, alter the function to look like the following:
function preloadScene() {
this.load.atlas("plane", "plane.png", "plane.json");
this.load.image("obstacle", "obstacle.png");
this.load.json("sprites", "sprite-physics.json");
}
The above code assumes that your physics file is named sprite-physics.json like mine.
Now within the createScene
function of the index.html file, add the following:
function createScene() {
// Animations ...
this.matter.world.disableGravity();
spritePhysics = this.cache.json.get("sprites");
plane = this.matter.add.sprite(300, 360, "plane", "plane1.png", { shape: spritePhysics.plane });
plane.play("fly");
obstacle = this.matter.add.sprite(1100, 360, "obstacle", null, { shape: spritePhysics.obstacle });
}
In the above code, we are accessing the sprites
asset which is the name we associated with the sprite-physics.json file. As per my configuration, I have physics information for plane
as well as obstacle
which we’re adding to a sprite. For clarity, the plane
sprite is using plane1.png
and not null
because plane1.png
is the first frame in the animation while the obstacle is not animated.
To move the obstacle, change the updateScene
to look like the following:
function updateScene() {
obstacle.x -= 4;
}
If we did nothing else and ran our game, the obstacle would move until it collides with the plane. When a collision happens, we won’t know about it, but the obstacle will continue to move beyond the plane. This is because in the physics file, both physics bodies are sensor bodies. This means collisions are only reported, not demonstrated. Had these physics bodies not been sensors, the sprites would collide and stop moving, or one of the sprites would be pushed back.
Even though collisions are reported, we’re not looking for them yet.
To be useful, we’re going to want to do something when a collision happens. In our example we want the plane to explode when colliding with an obstacle. This means we need to track our collisions.
Within the createScene
function, modify it to look like the following:
function createScene() {
// Animations ...
this.matter.world.disableGravity();
spritePhysics = this.cache.json.get("sprites");
plane = this.matter.add.sprite(300, 360, "plane", "plane1.png", { shape: spritePhysics.plane });
plane.play("fly");
obstacle = this.matter.add.sprite(1100, 360, "obstacle", null, { shape: spritePhysics.obstacle });
this.matter.world.on("collisionstart", (event, bodyA, bodyB) => {
if((bodyA.label == "plane" && bodyB.label == "obstacle") || (bodyB.label == "plane" && bodyA.label == "obstacle")) {
if(plane.anims.getCurrentKey() != "explode") {
plane.play("explode");
plane.once(Phaser.Animations.Events.SPRITE_ANIMATION_COMPLETE, () => {
plane.destroy();
});
}
}
});
}
Tracking collisions with Matter.js is not as easy as tracking them with arcade physics.
We need to look for a collision event between two physics bodies. Then we need to make sure the collision happened between a plane
and an obstacle
and not something else. If the correct collision happened, we need to make sure our plane isn’t already exploding. If it isn’t, we can start the explosion animation and destroy the plane when the animation ends.
You just saw how to use Matter.js as the physics engine in a Phaser 3.x game. While the focus of this tutorial was around collisions, a physic engine can be used for so much more.
There are a few things to note about what we saw in this tutorial:
If you want to see how to use arcade physics, I strongly recommend checking out my previous tutorial which uses the same example.