Modular Game Worlds in Phaser 3 (Tilemaps #5) — Matter Physics Platformer

Michael Hadley
ITNEXT
Published in
16 min readAug 20, 2018

--

This is the fifth (and final!) post in a series of blog posts about creating modular worlds with tilemaps in the Phaser 3 game engine. In this edition, we’ll step up our Matter.js knowledge and create a little puzzle-y platformer:

Pushing crates around to avoid spikes and hopping over seesaw platforms

If you haven’t checked out the previous posts in the series, here are the links:

  1. Static tilemaps & a Pokémon-style world
  2. Dynamic tilemaps & puzzle-y platformer
  3. Dynamic tilemaps & Procedural Dungeons
  4. Meet Matter.js

Before we dive in, all the code that goes along with this post is in this repository. These tutorials use the latest version of Phaser (v3.55.2) as of 08/13/21.

Intended Audience

This post will make the most sense if you have some experience with JavaScript (classes, arrow functions & modules), Phaser and the Tiled map editor. If you don’t, you might want to start at the beginning of the series, or continue reading and keep Google, the Phaser tutorial and the Phaser examples & documentation handy.

Overview

In the last post, we got acquainted with the Matter.js physics engine and played with dropping bouncy emoji around a scene. Now we’re going to build on that Matter knowledge and step through building a 2D platformer. We’ll learn how collisions work in Matter, get familiar with a plugin that will allow us to neatly watch for collisions in Phaser and then get into the core of the platformer.

Collisions in Matter

The crux of what we are going to do revolves around handling Matter collisions. If we want to use physics in a game, we need to be able to respond when certain objects collide with one another, e.g. like a player character stepping on a trap door. Since Phaser’s implementation of Matter is a thin wrapper around the underlying library, it’s worth revisiting our vanilla Matter example from last time to learn about collision detection in Matter. If you are impatient, you could jump ahead two sections to get straight to the platformer. But if you like to understand how something really works — which I think will pay off in the long run — then stick with me here.

Here’s what we’re aiming for in this section:

The shapes light up when they collide with each other, and they turn purple when they hit the floor.

Here’s a CodeSandbox starter project that matches what we did last time. I’d recommend opening that up and coding along. There’s a comment towards the bottom of the file that shows you where to start coding. The setup is the same as last time:

  1. We create a renderer and engine.
  2. We create some different shaped bodies that will bounce around the world.
  3. We add some static bodies — bodies that are unable to move or rotate — to act as obstacles.
  4. We add everything to the world and kick off the renderer & engine loops.

To start listening for collisions, we need to add a new module alias at the top of the file, Events:

Events allows us to subscribe to event emitters in Matter. The two events we will play with in this demo are collisionStart and collisionEnd. (See the documentation for other engine events.)

On each tick of the engine’s loop, Matter keeps track of all pairs of objects that just started colliding (collisionStart), have continued colliding for multiple ticks (collisionActive) or just finished colliding (collisionEnd). The events have the same structure. Each provides a single argument — an object — with a pairs property that is an array of all pairs of Matter bodies that were colliding. Each pair has bodyA and bodyB properties that give us access to which two bodies collided. Inside of our event listener, we can loop over all the pairs, look for collisions we care about and do something. Let's start by making anything that collides slightly transparent (using the body's render property):

Now we can extend our collisionStart to have some conditional logic based on which bodies are colliding:

In the first conditional, we check if one of the bodies is the floor, then we adjust the color of the other body to match the floor color. In the second conditional, we check if the circle hit the floor, and if so, kill it. With those basics, we can do a lot in a game world — like checking if the player hit a button, or if any object fell into lava.

Check out the CodeSandbox, live example or the source code here.

This approach isn’t terribly friendly or modular though. We have to worry about the order of bodyA and bodyB - was the floor A or B? We also have to have a big centralized function that knows about all the colliding pairs. Matter takes the approach of keeping the engine itself as lean as possible and leaving it up to the user to add in their specific way of handling collisions. If you want to go further with Matter without Phaser, then check out this Matter plugin to that makes collision handling easier: dxu/matter-collision-events. When we get to Phaser, we'll similarly solve this with a plugin.

Simple Collisions in Phaser

Now that we understand how collisions work in Matter, let’s use them in Phaser. Before getting into creating a platformer, let’s quickly revisit our emoji dropping example from last time:

Dropping emoji like last time, except now they get angry when they collide.

When an emoji collides with something, we’ll make it play a short angry face animation. Here is another starter template for this section where you can code along. It has a tilemap set up with Matter bodies on the tiles. Note: Phaser versions 3.11 and lower had a bug with Matter’s collisionEnd, but it’s patched now in 3.12 and up. The starter project uses 3.12.

When the player clicks on the screen, we will drop a Matter-enabled emoji. Last time we used a Phaser.Physics.Matter.Image for the emoji, but this time we'll use a Phaser.Physics.Matter.Sprite so that we can use an animation. This goes into our Scene's create method:

Now we just need to handle the collisions (also in create):

Familiar, right? The structure is pretty much the same as with native Matter, except that Phaser lowercases the event name to match its own conventions. bodyA and bodyB are Matter bodies, but with an added property. If the bodies are owned by a Phaser game object (like a Sprite, Image, Tile, etc.), they'll have a gameObject property. We can then use that property to identify what collided:

We’ve got two types of colliding objects in our scene — sprites and tiles. We’re using the instanceof to figure out which bodies are the emoji sprites. We play an angry animation and make the sprite translucent. We can also use the collisionend event to make the sprite opaque again:

Check out the CodeSandbox, live example or the source code here.

Now we’ve seen native Matter events and Phaser’s wrapper around those Matter events. Both are a bit messy to use without a better structure, but they are important to cover before we start using a plugin to help us manage the collisions. It’s especially important if you decide you don’t want to rely on my plugin 😉.

Collision Plugin

I created a Phaser plugin to make our lives a bit easier when it comes to Matter collisions in Phaser: phaser-matter-collision-plugin. We’ll use it to build this (last stop before we step up the complexity with a platformer):

Love-hate collisions

With the plugin, we can detect collisions between specific game objects, for example:

Or between groups of game objects:

Or between a game object and any other body:

There are some other useful features — check out the docs if you want to learn more. We’ll be using it, and unpacking how it works, as we go.

Phaser’s plugin system allows us to hook into the game engine in a structured way and add additional features. The collision plugin is a scene plugin (vs a global plugin, see docs), so an instance will be accessible on each scene after we’ve installed it via this.matterCollision.

Here’s a CodeSandbox starter project for coding along. It has the dependencies — Phaser and PhaserMatterCollisionPlugin — already installed as dependencies. (There are additional instructions here on how to load the plugin from a CDN or install it locally.)

Inside of index.js, we can load up the game with the plugin installed:

Then inside of main-scene.js, inside of create:

Now we don’t have to worry about which order the colliding pair is in, or finding the attached game object (or dealing with compound bodies). We can organize pieces of our collision logic within classes/modules as we see fit — like having the player listen for collisions that it cares about within player.js.

Here’s the final code, with a little extra added in to make the emojis draggable:

Check out the CodeSandbox, live example or the source code here

Platformer: Creating the Player

Now that we’ve got the fundamentals of collisions under our belts, we can tackle something more complicated — a platformer. We’ll build it in pieces, starting with the Player class. In this first section we’ll end up with:

Here’s the last starter CodeSandbox project which you can use to code along for the rest of the post. It already has a map loaded up with collisions and has empty files for the different modules we’ll be creating.

We’re going to start by creating our platforming “player.js” file. One of the challenges that comes with realistic physics engines like Matter is that they can be hard to control in predictable way in a game context. In contrast to our platformer from post two which had a single rectangle body, this character will have a compound body with four parts:

Our player is composed of a main body and three sensors. The main body will be like the rectangle bodies we’ve created before, except that it will have rounded corners (chamfer). This helps smooth out collisions (this will make more sense later). As for the other three parts, a sensor is a body that doesn’t react physically with other bodies in the world, but still triggers collisions. These will allow us to implement some special logic — e.g. the sensor below the player will let us tell if the player is currently on the ground.

Jumping into the code, we’re going to need to rely on the native Matter API, accessible under Phaser.Physics.Matter.Matter (yeah, double Matter), to build the body:

And if we create the player in main-scene.js, loading its position from a Tiled object within our map (which is already in the level.json file in the starter template):

We’ll end up with:

Let’s go back to the Player class and add in some controls:

We’ve done a few things here. First, to make input handling a bit better, we’re using a small class called MultiKey that I've provided for us. The source code is in the sandbox, but for our purposes, all we need to know is that we pass it as many Phaser keys as we want, and then the isDown() method will tell us if any of the keys are down. This makes it easy to have W/A/S/D keys or the arrow keys control the player.

Second, we hook the player’s update method into the scene's life cycle: this.scene.events.on("update", this.update, this). In previous posts, we manually called the player's update method, but now anytime the scene updates, the player will update on its own.

Lastly, inside of update, we apply a force to the player to move them horizontally in response to key input. Since we are applying a force any time the key is down, the velocity will keep increasing the longer we hold the key — all the way up to break neck speeds where the sprite flies off screen. We need to manually impose a (horizontal) speed limit. We also spike the y velocity in order to make the player jump.

We have a player moving around the world, but we have two problems. The first is that the player can jump while in mid-air, a.k.a. superman:

The second is that, because the player has a physical body with friction, the player can stick to a wall by jumping into it, a.k.a. spiderman:

This is where those sensors come in! We can use the ground sensor to know when the player is on the ground or in the air, so that we can solve the jumping problem. We can use the left/right sensors to know when the player is up against a wall, so that we can remove the spidey ability to stick to walls. We’re going to have to modify a few things:

We’ve added two properties — canJump & jumpCooldownTimer - that we'll use in update. We've also added isTouching, which is how we will track which sensors are touching something in the world. We hook into Matter's beforeupdate event, which runs before any collision events, to reset the isTouching fields back to false. Using the matter collision plugin, anytime a sensor hits another body in the scene, we mark the appropriate field of isTouchingtotrue.

We’re also using pair.separation from the Matter event. This tells us how far the bodies would have to move to no longer be colliding. We're pushing the player slightly away from any walls on the left or right side (but leaving 0.5px of overlap so that the sensor continues colliding). The player's mainBody — which is what physically interacts with the world — can no longer be pressed up against a wall, so the friction-sticking-to-walls problem is solved.

Back in update, we can change a few things:

We tweaked the move speed so that you move more slowly in the air and so that you can’t push things around while in the air (important for when we add movable objects to the world). We also modified the jump so that you can only jump when the ground sensor is colliding with something. We’ve also used a Phaser timer to create a cooldown so that you have to wait 250ms between jumps.

Putting that all together:

Check out the CodeSandbox, live example or the source code here

I’ve added player animations in that sandbox, check out the create and update methods to see how they work.

Tidy up On Shutdown & Destroy

We’re going to want to add some more interactivity to our world. The first thing we’ll add is the ability to kill the player and restart the scene when they land on lava or a spike. To do this, we need to be responsible and have the Player instance clean up after itself.

In the last tutorial, we had the scene handle cleaning up and destroying the player. Since we are hooking the player directly into events, we’ll want the player to listen for shutdown and destroy Scene events. shutdown is triggered from callingthis.scene.restart or this.scene.stop within a Scene. When stopped, the scene instance isn't destroyed, so if the scene is started again, it will go through init and create (but not the constructor). destroy kills the Scene instance, and if we start that Scene again, a new instance will be created. In either case, we're going to unsubscribe the listener from any events and destroy the player’s sprite. Inside of player.js:

We listen for shutdown and destroy, and in response, trigger the player’s destroy method. This unsubscribes anything that could trigger code within Player - scene events, matter collision plugin callbacks, the timer. We also destroy the sprite. Even though this method is being called by scene events automatically, it's written in a way that we could decide to destroy the player at any point in time, irrespective of the scene events.

We’ve also added a destroyed flag to the code. This is necessary because of the way EventEmitter3 works. When an event is triggered, any event listeners for the event are cached at the start of the event. So it's possible (and can happen often when working with physics) that a player that’s destroyed during an update event may still receive one more update event. We could get around this in the future with a plugin like samme/phaser-update-plugin or sporadic-labs/phaser-lifecycle-plugin. Both are proxies around scene events, so if you unsubscribe a listener through them, you can trust that the listener will not be invoked again from an event.

Player vs Lethal Tiles

Whew, now we can continue building out the world by adding some simple physics-y puzzles. First up, let’s kill the player when it lands on a spike or lava.

Inside of our MainScene:

We’re setting up the player’s sprite to collide with anything in the world. When it does, we check if the thing it collided with (gameObjectB) is a tile. If it is, and it's a lethal tile (a tile property set up in Tiled - see "Moving with Physics" section of post 1), we fade out and restart the scene.

The freeze method on the player (given in the starter template) simply makes the player's body static, so that it doesn't move for the duration of the fade. unsubscribePlayerCollide is really important here. This function - which is returned from addOnCollideStart - will remove the collision listener that we added, so that the player can only die once.

Here’s the final platformer sandbox for this post (which includes the lethal tiles plus the code for the next sections):

Check out the CodeSandbox, live example or the source code here

Crates & Spikes Puzzle

The part of the map with the spikes isn’t jumpable without some help. Let’s create a really simple physics puzzle that shows off Matter. We’ll place some crates in front of the player for them to knock down so they can cross the spikes. Because we’re using Tiled to build the level, we’ll use an object layer for this. It’s already set up in the starter project, but here’s how it was made:

Using the tile object tool which allows you to place tile graphics in the map

Constraints and Seesaw Platforms

We’ve got a large chasm filled with lava after the spikes, so let’s add some rotating platforms to that area to create another simple physics puzzle. The platforms will use constraints to keep them pinned in space, but they will rotate freely:

Again, it’s already set up in the starter project, but here’s how to use Tiled to pick the locations of these platforms:

Placing point objects, and holding CTRL to snap to grid locations

Then we can use a yet-to-be created module to place platforms:

We want these platforms to stay in place like a static body, but we can’t use a static body here since then the platforms couldn’t rotate. Instead, we’re going to use something called a constraint to pin the platforms to a fixed location. Check out the constraints demo and source code from Matter.js. We can think of constraints as a way to express that a body should be linked invisibly to another body or point in space. The body will then try to stay a fixed distance (that you specify) from that other body or point.

In a new file “create-rotating-platform.js” we can export a function that creates platforms. (We don’t really need a class here — we’re just configuring a TileSprite.)

We’re taking advantage of TileSprite so that we can use our tile assets (64 x 64 pixels) to create a platform in the world. I’ve just extracted an individual tile from the tileset for us and trimmed it down to 64 x 18 pixels.

Constraints can be a lot of fun. Anything we can do in vanilla Matter, we can do with Matter & Phaser. Check out Matter’s constraint documentation for more information.

Here’s that final platformer sandbox again, which includes the code for this section:

Check out the CodeSandbox, live example or the source code here

Celebration Trigger

Great — we can get from the left side of the screen to the right side. Let’s do something fun to celebrate when the player does that. We’ll create an invisible sensor and drop celebratory emojis on the player when they hit the sensor:

Again, the sensor has already been created in the started project’s level.json, but here’s how it is made:

Using the rectangle object tool and giving this sensor a specific name so that it’s easy to find in Phaser

Then we can load that up and listen for collisions back in our scene:

This same idea of invisible sensors can be used to trigger buttons, falling platforms, loading the next level, etc.

And with that, we’ve got a nice little physics-y level that you could experiment with and extend.

Here’s that final platformer sandbox again (which includes this code):

Check out the CodeSandbox, live example or the source code here

Ghost Collisions

At some point in exploring Matter, you might run into the common problem of ghost collisions. If you notice a player seemingly tripping over nothing as it walks along a platform of tiles, you are likely running into ghost collisions. Here’s what they look like:

You can check out the corresponding live demo that I created on Phaser Labs. As the mushrooms move over the top platform, you can see that they catch on the vertical edge of the tiles. That’s due to how physics engines resolve collisions. The engine sees the tiles as separate bodies when the mushroom collides against them. It doesn’t know that they form a straight line and that the mushrooms shouldn’t hit any of the vertical edges. Check out this article for more information and to see how Box2D solves for this.

There are a couple ways to mitigate this:

  • Add chamfer to bodies, i.e. round the edges, like we did in this post or use circular bodies to reduce the impact of the ghost collisions.
  • Map out your level’s hitboxes in as few shapes as possible, instead of giving each tile a separate body. You can still use Tiled for this. Create an object layer, and fill it with shapes, convert those shapes to Matter bodies in Phaser. The demo code linked above does just that.
  • Or, use @hexus’s phaser-slopes plugin. It solves ghost collisions against tilemaps, letting you keep your tiles as separate bodies.

Series Finale

Thanks for reading. Hope you’ve enjoyed reading this series on modular game worlds.

While this series is over, I’ll still be posting about Phaser 3, so if you’ve got feedback on the format, or there’s something you’d like to see in future posts, let me know!

About Me

I’m a creative developer & educator. I wrote the Tilemap API for Phaser 3 and created a ton of guided examples, but I wanted to collect all of that information into a more guided and digestible format so that people can more easily jump into Phaser 3. You can see more of my work and get in touch here.

--

--

Developer, Artist & Educator - Learning Product Developer at Convergence Design Lab