Due to the rapid pace of change some links or details may no longer be correct.
A comprehensive guide on creating custom builds of Phaser 3.
Welcome to another Dev Log. I'm stuck in that frustrating point where I want to write and tell you about what's been going on, but at the same time, doing so will take away hours of time from actually working on getting Phaser 3.12 released! But knowledge is power, so I've written a short overview of what's new in 3.12 this week and also a comprehensive guide on how to create custom builds of Phaser 3 - and why you really should do so.
I really wanted the 3.12 release to be out by now. I had hoped it'd be out last Friday but this didn't happen. Mostly because I had 3 key issues I wanted to resolve first and one of them took far longer than expected. The most significant remaining issue is the way that game input breaks if you have a high-resolution display and invoke camera scrolling. I need to solve this before 3.12 can see the light of day. I'll jump right on it as soon as I've published this newsletter :)
Despite not yet releasing, progress has actually been really good. Last week saw a huge range of issues closed and areas tidied up. Here is a re-cap of some of the most important:
InputManager.inputCandidate method, which determines if a Game Object can be interacted with by a given Pointer and Camera combination, now takes the full camera status into consideration.
This means if a Camera is set to ignore a Game Object you can now no longer interact with it, or if the Camera is ignoring a Container with an interactive Game Object inside it, you cannot interact with the Container children anymore. Previously they would interact regardless of the Camera state.
I recoded the Transform.getWorldTransformMatrix method to iterate the transform parents correctly, applying the matrix multiplications as it goes. This (along with some changes in the Input Manager) fixed an issue with Game Objects inside of Containers failing hit tests but only between certain angles.
Tint Fill + Alpha
setTintFill method would ignore the
alpha value of the Game Object in the shader. The alpha value is now blended with the tint fill, allowing you to properly alpha tint-filled Game Objects, as can be seen in the image below (in 3.11 the sprite on the right would be solid white):
Creating Custom Phaser Builds
To create custom builds you're going to need webpack. If you've no experience with webpack it'd be best to go and learn how it works before carrying on, as Phaser is built specifically with it. Other packagers may also work (like Parcel) but it's up to you to translate this guide into their respective formats.
The important thing to remember is that the Phaser module entry point, as defined in webpack, controls the whole structure of the exposed API. That is, everything it includes is made available under the Phaser namespace. It literally defines which features are included in the library. That's an important distinction you should understand: it controls what is available in the library, it's not meant as an entry point for a project.
There are two choices you can make. You can either create your own custom build of Phaser that only includes the modules you require, and then use that file in your projects, perhaps via a script tag, or bundling it into your final package. Or, you can create a project that pulls in just the modules you need from Phaser via require or import calls. We're going to cover the first approach for this guide.
This guide is based on creating a custom build of Phaser 3.11. When 3.12 ships it'll change slightly, because more things will be available to bundle in, but the core concept will remain the same.
To start with I'd recommend you clone this template repo. It will save a whole bunch of time getting set-up. Clone it then
npm install to get the depdencies downloaded. You're now ready to do a custom build.
If you issue the command
npm run build (or
webpack if you've got it available globally) from the project folder then it'll create a custom build into the dist folder. This file is called
phaser-custom.js. Inside the
test folder you'll find an
index.html file. Open this in a browser via an http server, or with local file permissions enabled, and you should see the following:
If you're wondering where on earth the Star Wars logo is coming from that's a valid question :) Let's break it down.
The webpack config in the template uses the file phaser-custom.js as its entry point. Here's the complete file:
If you look at the file, or the above image, you'll see it defines what's available in the Phaser namespace. It starts by including the standard polyfills and CONSTs. Then it pulls in the 2D Camera system, the Events, the Game, the Graphics object and finally one Math function called Between.
This is wrapped-up by merging in the constants and exporting it globally. Combined with the webpack config this will build into the phaser-custom.js bundle which will have everything Phaser needs to run, plus the extras identified above. By default Phaser doesn't include a camera system or any Game Objects, which makes the 'base' use somewhat limited. So in this case we've added the Graphics object, because at the very least we can render something with that.
GraphicsFactory function is what allows you to use the command
this.add.graphics from within a Scene. You could exclude this to save a couple of KB if you wish, but then you'd have to alter your code to create a Graphics instance directly and add it to the Display List.
test/index.html file you'll see the code for our demo. All it does is create an 800x600 game instance and then renders the Star Wars logo to the Graphics object. It's not exactly a game but it demonstrates that, fundamentally, everything is working.
If you look in the
dist folder you'll see that the
phaser-custom file is 274KB minified. This goes down to 71KB with gzip on the server, so it's significantly smaller than the default Phaser ships with.
Tweaking the Custom Build
So, how do you now edit the custom build to include the ability to do something useful like load images and display them? To do this we need two extra things added to our package: Sprites and the File Loader, otherwise, we can't get the files into Phaser. Here is a tweaked version of the
phaser-custom.js file from above. You can find this in the repo called phaser-custom-sprite.js:
If you look at the file above you'll see we've added in the Image and Sprite Game Objects (and removed Graphics) and also added the Loader module. This pulls in the entire Loader and all possible File Types, which is actually overkill for this bundle, so I'll show you how to refine that shortly. For now, though, it will do what we need. Issue the command
npm run buildsprite and it'll build a new bundle to the
dist folder. Launch the file
test/indexsprite.html and you should get the following:
Voila, working Sprites and image loading! The bundle size is now 303 KB minified (78 KB with gzip), which is bigger than our Graphics only bundle, but that's to be expected as we've added the whole Loader module to our build and a couple of meaty Game Objects too.
Let's refine it a little bit though. We really don't need all of the file types the Loader supports. In fact, for this test, we literally only need one: the Image loader. Let's tweak our entry point so it includes only the LoaderPlugin and the Image File Type:
How do you know which things to include back in the entry point? You can work it out by looking at the
phaser-core.js files in the root src folder of the main Phaser repo. Using those, plus just browing the source folders for yourself, you can quickly find what you need.
There's still quite a lot of modules being included that we may not require though. We can visualize that by creating a webpack profile. Use the command
npm run buildlog. This will build Phaser and also create a JSON file that details the build process. You can upload this JSON file to the online webpack analyzer. I've included a json file in the repo so you can try it out for yourself. Just download it from here, then go to the webpack analyzer and upload it. After a short while it will generate a report. Click on 'modules' to view the module tree:
All of the modules are listed below the interactive tree. Click any node on the tree to see what is requiring that module and how many dependencies it has. Let's pick a particularly busy node:
As we can see, lots of modules include the entire Array Utils package. This isn't a bad thing in itself, because it's a pretty compact and widely used area of the API, but this exploration process did highlight a lot to me. If you look at the Game module you'll see it pulls in plenty of things. The Texture Manager, the Sound Manager, the Animation Manager. All the things it expects to need in order to operate. Yet, the Sound Manager is entirely optional - we could easily hide that behind a custom build flag and it'd stop including 140KB worth of source (that's un-minified, including comments) because if you're literally not using it, why even bother to have it in the API? The same can be said for a number of other systems, such as the Animation Manager and the Device module could be made into a much more compact version that only includes checks that Phaser needs to boot-up.
In short, I'm quite happy that it's really easy to create a significantly smaller version of Phaser 3 with very little effort on your part. However, having now debugged it extensively myself with the module analyzer I can see several places where it could be made even smaller with not much effort. Which will be a nice thing to undertake when I move the codebase to ES6 / TS later this year. In the meantime, use the custom build template and smash away parts of the API you don't need to get your games even leaner. This is especially important for Facebook Instant Games, where time-to-play needs to be as tiny as possible. Cutting from several hundred KB down to 70KB certainly gets you a lot further along that path.