Navigation

Phaser 4 Dev Log 5

Published on 31st March 2020

Wow, what a difference a month can make! As we come to the end of March the world is in a very strange place. One that we could never have anticipated.

Truth be told, I lost over a week of dev time as I was dealing with the changing environment around me and spending too long glued to the news, wondering what fresh new horror it would bring. However, now lockdown has become part of everyday life and there are fewer surprises to be had, my mind has settled back down and I'm full-steam ahead in the development of Phaser 4.

So, let's dive in and see what's new.

From small acorns

First a word about the code structure. I cannot stress enough that the whole point of Phaser 4 is to both use and embrace the new ES standards. So, code is going to look very different in all of the following examples. And that's ok because it's about time Phaser grew up. Here is an example of a really simple Scene:

All we have here is a single Text Game Object added to the World:

If we build the code using Rollup, the output shows us the bundle size:

Less than 10 KB for the entire core framework and a Text Game Object. Had I swapped Text for a Sprite in the code above, it would be even smaller, as Text actually extends the Sprite class, adding in a few extra helper functions.

There's quite a lot to unpack in the code above, even though it's really simple. First of all: where have all of the methods like 'preload' and 'create' gone? Secondly, what is the 'World'?

The base Scene in Phaser 4 is an extremely slim class that consists of just two properties. The first is a reference to the Game to which it belongs. The second is a World instance. Every Scene has its own World, which is fundamentally the root of the Display List. A World also has a Camera, with which you can move around the World. Each World belongs to its Scene and you can add, swap and remove Game Objects from it, as required.

Those of you familiar with Phaser 2 will remember it had a Stage object, which was inherited from Pixi 2, who in turn bought that convention over from Adobe Flash. The Stage and the notion of a 'world' were removed in Phaser 3 because the original design was for a completely flat display-list, so it was pointless to have a 'root' container. Now that Containers are first-class citizens in Phaser 4 again, it made sense to resurrect the World object with it. Therefore, if you want a Game Object to render, you have to add it to the World, or to another object that is part of the world already.

So where have 'preload' and 'create' gone? The simple answer is that if you don't need to load any assets, or, and this is more likely, those assets are included in your build via your bundler, then you don't actually need a Loader at all. And if you don't need a Loader, it doesn't need 'preload', which in turn means there's no need to split things up into a 'create' function either. This is how we're able to create our Text object immediately in the Scene constructor and start using it right away.

That doesn't mean you can't have a Loader, though. Phaser 4 retains the way in which File Types work from Phaser 3 but improves it by making each File a self-contained loader in its own right. This means you can do the following:

And of course, you get the Sprite loaded:

Here, we've imported just the ImageFile object, given it the details it needs and called its load method. This returns a Promise and on a successful resolution, our Sprite is created.

As before, you can see the file size is nice and compact:

The File types pull in whatever functions they require to fully resolve themselves. In this case the ImageFile uses the Image Tag loader and the resulting image will be added to the global Texture Manager.

Whereas, for example, the Texture Atlas loader is responsible for both loading and parsing the atlas data, generating the frames for the textures. In previous versions of Phaser, it was the Texture Manager that was responsible for doing this, so it would have lots of functions such as atlas and bitmap font parsers hanging off of it, even if you never actually loaded an atlas in your game. This added to the filesize and API overhead. By making the file types themselves responsible, we can make sure that only what is needed gets pulled in. And if more than one file type shares the same function, such as a JSON parser, that is fine, the bundler is intelligent enough to know that is the case and only one parser gets included.

Phaser 4 Nano currently includes file type loaders for the following: Texture Atlas (both JSON hash and array format, new and previous Texture Packer generations), Bitmap Text Files, CSV Files, Image Files, JSON Files, Sprite Sheets, and XML Files.

More file types will be added, of course, but those are what exist today.

All file types are also capable of using base64 and image data directly. Which means you can now do the following:

As I said previously, if you don't need the Loader, because you're importing assets like above, there is no need to include it into the build. The textures can just be added directly to the texture manager. For the vast majority of games, this won't be the case, of course, but for those in byte-sensitive environments such as playables and banner-ads, it's a really useful feature.

What if you want to load more than a couple of files? and have a nice progress bar and all the usual trimmings? Well, you import the Loader, of course :)

Here you can see the creation of a Loader, where you'll find methods you would expect such as 'setBaseURL', 'setPath' and so on. You add File instances into the Loader and they form the load queue. Here we're adding a few Image files, but this can be a mixture of whatever file types you need. You can add them in advance, or even during load and it'll recalculate accordingly.

There is now no enforced parallel download limit, either. In a world where HTTP/2 servers are common-place, it made sense to remove this restriction. So Phaser 4 will now leave it up to the browser to regulate how many network connections are opened and resolved at a time. In testing, I've seen dramatic increases in load times as a result. As a fall-back, you can still set a maximum parallel download limit, but it's disabled by default.

The Loader emits events, including 'progress', which can be used to draw a loading bar, and finally, we tell it to start. The parameter passed being an on completion callback, which, for old-times sake, we'll call 'create' :) With a little more structure to your Scene you could easily recreate the old 'preload' and 'create' flow that existed in previous versions. Indeed, I will be sure to bundle a 'pimped out' Scene, that you can use, which provides exactly this. In Phaser 4 those magic methods won't be forced upon you, however.

Render Cache

One thing that was really important to me in the design of Phaser 4 was being careful about over-rendering and consequently draining device battery-life. In Phaser 3 making devices run hot is quite trivial to do, unfortunately, as even, an entirely static Scene is still constantly clearing itself and re-rendering everything, every frame, even if it hasn't updated.

I changed the way this works for Phaser 4 and I'm very pleased with the results so far.

Run this example here

In the above example, there is a farm scene playing out. Imagine there being a big 'Play' button and it'd be a typical example of a game title screen. You've got a logo, which has a repeating tween on it that makes it scale up and down every few seconds, a background and a couple of fully animated chickens, which wander back and forth, idling or pecking at the ground in a random sequence.

Below the demo, you'll notice a couple of stats logged. These are being recorded using the new Phaser 4 Stats class. The left graph is, clearly, the FPS. The other two, however, are where things get interesting.

If you let the demo run for a few minutes you'll see the graphs fill-up. The graph on the right, the "Cached Sprites" graph, is showing how many sprites were flagged as being dirty during that frame. A 'dirty' sprite is one that has had its transform or texture changed. So as the chicken walks, it is both animating and translating on the x coordinate, and it's considered 'dirty' for every frame such a change takes place. Equally, when the logo is tweening, it is 'dirty' too.

Internally, Phaser 4 Game Objects keep track of how dirty they are and when they were last cleaned. Because if a Game Object hasn't had its transform updated, we can skip a whole load of internal processing. The renderer doesn't need to do any matrix calculations at all, it can literally dump the data of the previous frame directly into the vertex buffer, saving us pointless CPU calls that would otherwise be performed. Equally, if a texture frame doesn't change, we don't need to retrieve and send new UV coordinates. All in all, the higher the percentage in the Cached Sprites graph, the less work the CPU is having to do each frame preparing render data. A reading of 100% means for that frame, nothing had to be recalculated, it was just a pure data copy.

However, even in a visually fun Scene like our farmyard, not everything is 'dirty' every frame. And this is where the middle graph, "Cached Frames", comes in to play. Because if, during a frame, the renderer detects that actually, nothing was dirty that frame, then it literally doesn't do a thing. Not one single WebGL operation happens, it just bails out, leaving what was previously on the canvas still in view.

And this is an absolutely perfect position to be in when it comes to battery life and device heat. I've left the farm scene running for the past 12 minutes while I've been writing this part of the dev report, recording the stats every 5 seconds and this is what I've got:

What this says is that over the course of 42300 frames, 8560 of them (which is over 20%) were 100% cached. In other words, the GPU was 20% idle. That's 2.4 minutes out of those 12. Yet, looking at the demo running, you'd be hard pushed to notice it, right?

This kind of optimization is perfect for more casual games, especially card games, where you've often got a quite static game screen, perhaps with some incidental animations going on, but the majority of the time the GPU could be idling if it was allowed to.

I fully intend to push the renderer cache further by including camera culling as standard, so if you've got updating objects outside of the viewport, the renderer doesn't need to care about them at all and again could skip rendering even if they are dirty. All of these cache features can be toggled off via the config object, but for sake of your players, I'd hope you keep them enabled.

By using the Stats tool, which will evolve further the more work I do on it, you will be able to easily see how complex your render tree is at any point in your game. Perhaps making an animation a few ms slower, or a tween slightly less frequent, will literally save battery life for your players.

Input Mapping

As you can probably tell by now, the whole point of Phaser 4 is that very little is included unless you actually need it. The Input system is no exception here. It's fair to say that the Input Manager in Phaser 3 was a massively complex beast. It had a lot of features in it and pretty much tried to do everything for you, which it managed to achieve at the expense of quite often getting in your way.

I've stripped it all right back to basics for Phaser 4. If you want to support Mouse (or Pointer) input, then you include that class. If you want Keyboard support, you include that class as well. If you want Multi-Touch support, there is a dedicated class for that, too. The reason for this separation is simply that it's very often you don't need them all. Lots of casual games have no keyboard element at all. And even more have no need for multi-touch at all, either. All they really care about is a single finger interacting with the screen at once. Yes, you may swap to a different finger, but unless you need to actively track the progress of more than one at a time, it's pointless having all of the (pretty complex) code required to do it.

So input handling has been recoded entirely with this in mind. The upshot is that the respective managers are significantly smaller, which is always a good thing. And far less complex, which is a great thing.

The Mouse class will emit pointer events as it receives them (no queue is employed). Here you can see the results in this painting demo, which also uses a Render Texture to draw on:

In the code above we're simply creating the Mouse class and a couple of sprites, and on the pointer movement event we draw the sprite to the Render Texture:

Run this demo here

Have a play with it yourself!

How about a more common task, like clicking on a Game Object?

To flag a Game Object as being interactive you use a similar approach to Phaser 3:

Calling 'setInteractive' flags the Game Object as being receptive to input. At the most basic, you can then call the 'Mouse.hitTest' method to get a boolean response back again and perform an action. There are other, more complex methods available, allowing you to check a whole range of Game Objects, which can include checking the World object to get every possible interactive object back again, as well as controlling the ability to only return the top-most object. You can also flag a Container (which all Game Objects are in Phaser 4) as having non-interactive children, which allows it to skip over potentially deeply-nested input iteration scans.

What's more, the input detection is perfect, no matter how deep the Game Object is in the display list, or how transformed it is. It'll work regardless of position, scale, rotation or skew, on any level of any parent, including the World.

While recoding the Input API, rather than take the Phaser 3 code and re-use it, I started from scratch and went right back to the drawing board. Digging through MDN event documentation I realized there was a much cleaner way of getting the event input coordinates and tested it out. Sure enough, it worked wonderfully. And what's more, it works regardless of whatever CSS transform has been applied to the Phaser canvas or any of its parents!

Have a look at the video below to see what I mean. You may need to full-screen it to see the dev tools changes that I make.

Click here to open the video or you can run the demo for yourself.

Note that this demo is mouse input only, not touch, at the moment.

There are some more tweaks I need to make to the input classes, yet on the whole, they do what is required of them and, dare I say it, more accurately than they ever have before.

Game Object Components

As of today all of the following classes have been added to the repo, most of which are completely finished:

  • Animated Sprite
  • Camera
  • Container
  • Game Object (the base class)
  • Sprite
  • SpriteBuffer
  • Text
  • World

All Game Objects are constructed from a set of components. Each component provides a different set of properties and methods, for example, the Origin component allows you to get and set the origin of a Game Object. So far there are 20 different components and it's easy to add more of them as needed because objects only pull in those they require when the base classes are being created by the VM.

Components can also enforce that they are only applied to objects that already meet pre-existing conditions, such as having other components. For example, here is the Renderable component:

In the component code above it will only apply the Renderable component to an object that already has the Alpha, Texture and Visible components. Enforcing this in the TBase ensures that you can't accidentally apply it to a Game Object lacking any of those requirements.

This also means that Game Objects share the same code, with no duplicate functions between them, making the builds smaller. They can still override a function if required, but on the whole, they pull in what they need, add in a few special extras and that's it.

Creating your own custom components is trivial and an Install function is provided so they're easy to merge together. I'll look forward to seeing what creative components the community comes up with.

What next?

My next task is to run a bunch of tests and write code with regard to handling the destruction of Game Objects and game instances. After, I can go through and fill out the JSDocs and generate the first release.

I wanted to be a little ahead of where I am today, but there's no getting that time back now, all I can do is keep on coding and spend a little less of my days hooked to the news.

I fully intend for the next post I make here to be the first Phaser 4 Nano release, ready for all backers to play with. It will be documented, have lots of examples and a template to get started quickly with. Hopefully, something exciting to look forward to, given the state of things at the moment.

If you've any questions (or sensible requests!), feel free to ask in the comments.