DOM Elements leave beta and Geometry Masks get a huge overhaul.
Welcome to Dev Log 145. Work has been progressing at a rapid rate these past few weeks. I've merged a large number of Pull Requests and have been fixing and closing down issues too.
Thanks to a really neat PR from @florianvazelle the Geometry functions have gained a whole bunch of new Intersects tests, such as Circle to Circle, Line to Circle, Triangle to Triangle and many more. Although these existed previously, all they used to return was a boolean. Now thanks to the PR, the new functions will return the point of intersection as well, which is extremely useful in all kinds of situations. Thanks also to @rexrainbow for helping debug these.
Phaser Tutorial Translations
Several issues ago I posted asking for help translating the core Phaser tutorials into languages other than English. I had a good response and am pleased to say that the first 8 translations of the Making your First Phaser 3 Game tutorial have now gone live onto the site.
We've translations into Bulgarian, Chinese, Czech, Danish, Polish, Portuguese, Slovakian and Spanish uploaded. My thanks to Tomas, Telinc1, Fernando, Huang, Adriano, Juraj, Frederik and Konrad for sending me those translations. I'm still hopeful we'll have more translations available soon, as other people did get in touch last time, they just haven't sent the files yet.
If you would like to translate this tutorial, and are happy editing MarkDown files, please email firstname.lastname@example.org and I'll send you more details. The more translations we have, the better :)
I'll be looking at the site stats in a few weeks to see which translations are proving to be the most popular, or indeed if they are being used at all. I would hope they are, but the stats will tell!
DOM Elements leave Experimental
DOM Elements were added to Phaser nearly a year ago, back in July 2018. I wrote about them at the time and published some examples of how they work. Because they hadn't had much testing I kept them under the EXPERIMENTAL flag, which meant they were excluded from the dist builds of Phaser unless specifically enabled by you in the webpack config.
A lot of you still used them though. Which isn't really surprising I guess, what with the on-going rise of IO games and the need for login forms, leaderboards and such-like. I mean, sure, you can do that in Phaser using custom UI components, but really, it's what the browser was created for! So it makes complete sense to use it.
I spent a few days refining how they work, adding in some more options to the constructors and hooking them to the Scale Manager. There is still one lingering bug to do with the Scale Manager auto centering content and initial sizes, but otherwise, I'm happy with how they're progressing, so they'll be available fully in 3.17. My thanks to the team at Quest AI for helping test these.
One of the new features in 3.17 is the ability to load in CSS files to the browser via the normal Phaser Loader. You can also now set the class name of a DOM Element directly, so it's a lot quicker and cleaner to create them than when they were experimental. Here's an example demo:
In the above code, you can see the new load.css call being made. This loads in the CSS file, adds it into a style element and appends it to the document head, causing all styles defined in there to be applied immediately.
In the create function I'm creating two DOM Elements. The H1, with the class of 'chrome' and the H2 with the class of 'dreams', and a slight angle for effect. The elements are then tweened up and down just as you would any other Phaser Game Object. It looks like this when running:
You'll notice a 'flash of content' when this example starts up, because the CSS file in turn loads in some web fonts that the demo uses. This is a common problem faced by web developers (the flash of unstyled content), so there are plenty of documented ways to handle it. For the sake of this simple demo, I'm happy to leave that up to you.
You can find more examples in the Labs.
I of the Mask
Another issue I had to address this week revolved around masking. Masks have always been a bit of a mixed bag in Phaser 3. In v2 you could only ever create Geometry Masks, which internally used a Graphics object per mask and created a stencil buffer for each mask as they were processed. However, in v2 they could be stacked up to a huge depth (255 children deep). In v3 I wanted more powerful masks, which meant proper bitmap based masks as well as geometry ones. We implemented this from the very first release, with geometry masks using Graphics objects and a single stencil buffer, or path operations under canvas, and bitmap masks using a frame buffer, making them WebGL only.
Originally though, v3 only had a single-depth display list. None of the Game Objects had children and there was no need to carry masks on down the list. This kept things simple and extremely fast and the masks worked fine under these restrictions. However, there was so much demand for Containers we had to fit them in retrospectively, and as well as introducing a number of issues re: branching, it also meant that masks no longer worked properly on them. You could mask a top-level Container, but any masks a descendant, of any depth, may have, would be ignored. What's more, one of the things I'd always wanted to do, which was to allow a Camera to have a mask, couldn't work either.
With a number of mask-related bugs lingering unresolved in GitHub I started digging into them. As suspected, the Geometry masks stencil buffer was hard coded at only one level deep, and the Bitmap masks frame buffer didn't handle passing control back to another framebuffer, meaning they couldn't be stacked either.
I tackled Geometry Masks first.
I actually took inspiration from the way in which the stencil queue had been handled in Phaser 2. I backported some of that code, merged it with the new structure and after a lot of testing geometry masks were finally working again at multiple depths. Here's an example of a Scene using multiple masks:
It's better to see it running for the full effect. Click the above image to do that. The code is no different from before:
Here I simply create three different Graphics objects that function as the masks. Create Geometry Masks from them and then apply them to the various Game Objects. In this Scene, there is a single Particle Emitter Manager (called particles in the source) and two Particle Emitters belonging to it (emitter1 and emitter2).
You'll notice that geomask2 is applied to the Particle Emitter Manager itself. This means that any emitter this manager is handling will be masked. This has always worked in Phaser 3. What's new, though, is the way I've also set geomask3 on emitter2. Previously, you couldn't have a mask applied to an emitter directly, only to the manager. Now in 3.17, you can have both. If emitter2 wasn't using geomask3 it would use geomask2 instead because that has been applied to its parent, but in this case, it overrides the parent mask.
If you run the demo above you can move the camera with the cursor keys. Notice that the masks are now applied in camera space, so will move with the camera.
Previously, a Container would ignore the fact that any of its children may be masked. You could mask the Container itself of course, just not its children, no matter what type of Game Object they were.
This has been fixed for Geometry Masks in 3.17. You can now mask a child, of any depth, and the parent Container, and it'll use the correct mask accordingly. Again, it's quite hard to see in a static screenshot, but if you click the image below you can see it running:
The final piece of the puzzle was something I'd wanted to add for a long time: the ability to mask Cameras.
This is now possible in 3.17. I've added a new method to the Camera class called
setMask and the related
clearMask method. You can set either a Geometry or Bitmap Mask on a Camera and, as you'd expect, it will impact anything it renders. Here's a small snippet showing the creation of a Bitmap Mask, made from an image loaded in the Loader:
When run, it looks like this (as usual, click the image to run the demo):
The mask is applied to the top-level, impacting the images and particles in this Scene. If you were to add another Camera, it would of course not be affected by this mask. Which makes this an interesting way to apply masks selectively.
A special feature of Camera Masks that I added is the ability for them to remain fixed in place. If you use the cursor keys in the above demo you'll notice that the mask moves as the camera moves. This is optional. You can also create a static mask on a camera that ignores the camera scrolling. The following demo shows the difference:
Here you can see the mask remain firmly in place, even though the camera is scrolling around a tilemap. In the first demo try pressing the Q and E keys to zoom in and out of the Scene. With the fixed camera, the mask doesn't zoom. With the flexible one, it does. Also, press Z and X to rotate the Camera. Again, in the first demo, the mask rotates with it, but it won't do that with a fixed mask.
I believe that having these two options will give you a lot of flexibility when it comes to masking your games.
I still need to work on the mask stack and see if the same process can be applied to Bitmap Masks. It's working as intended for Geometry Masks, because I've recoded how the stencil buffers are used, but I've not yet done this for Bitmap Masks. They need stacked framebuffers, which are a whole different kettle of fish. I'll spend a day on it and see how things go. I would be happy to publish 3.17 with just Geometry Masks working at any depth and extend this to Bitmap Masks in a later release, but if it turns out to be quite easy to solve, I will, of course, do so.
My focus right now is on getting 3.17 ready for release. Masks were a bit of a diversion for me really. The aim was simply to close down some old issues in GitHub, but as is often the case, the act of doing this uncovered a legacy structural flaw, rather than a code one, which is always harder to fix.
With Easter break over, I'll be working on 3.17 solidly now, so look out for constant commits to GitHub! If you have time to help then I'd appreciate it, either by testing new builds, or reporting issues or seeing if there are any you could help close. Every little bit helps.