Navigation

Phaser 3 Dev Log - September 2020

Published on 8th September 2020

It's only been a couple of weeks since the previous entry, but so much has happened it's absolutely time for another shiny new Dev Log :) Let's get cracking ...

As with the last log, I've been flat-out on the Phaser 3.50 release. This started out with me hitting the GitHub issues list hard and trying to beat it down from the beast it had grown in to. There were over 200 issues a couple of weeks ago but I've been going through them one-by-one, fixing where possible (which is nearly every case so far) and it's now down to a much more sensible 130 issues.

Of those 130, 16 are Feature Requests, which leaves 114 to tackle. 9 of those are purely "Audio on mobile browser" related, which is frustrating because those are easily the most time-consuming type of issues to test. Otherwise, it's a real mixture of random things, some more support related than actual issues.

I know people are impatient for the 3.50 release, and I appreciate that. Yet I think it's important to clear as many issues as possible, too. Aside from the obvious, of fixing bugs, they can also lead to interesting changes within the code base, such as with the new Animation system that landed last week.

Welcome to the State of Animation

I took a good, long, hard look at the Animation system in Phaser 3 and concluded it was in serious need of some TLC. Fundamentally, it worked. It wasn't a particularly bug-ridden area of the API by any stretch, yet it absolutely was doing a lot of heavy lifting it just didn't need to do. And, I'd wager, was more confusing than it ought to be to actually use.

So, the changes I've made in this area are virtually all about the quality of life for end developers, as well as performance gains.

Although it doesn't impact you very much, I've reorganized the API structure a lot. There is a new class called the Animation State. This replaces the old Animation Component that Sprites used to have. Fundamentally, it performs the same actions, but the new name makes it much easier to find in the documentation. This component _used_ to make lots of calls out to the Animation instance that it was playing, as well as the Animation Manager, just to do simple things like load the animation properties it needed for playback.

This has all changed in 3.50. The Animation State no longer needs to make multiple calls out just to play something. It's far more autonomous and looks after its own house, instead of passing itself up the food chain and having things set on it. The number of internal calls made to play an animation on a Sprite is now significantly smaller than before, which is great because fewer steps mean faster processing. This doesn't directly impact your game code however, just the end results.

A very important change that definitely does give you a lot more flexibility is the new `PlayAnimationConfig` object.

Animations have always had the ability to set various properties on them, such as the frame rate, a delay before starting playback, or the number of times they will repeat. These were set when the animation was created and while the old Animation Component did have some properties to modify them, several of them didn't work and those that did only worked once playback had begun. There needed to be a better way. Enter, the new config object.

You can now pass a config object to the `play` methods, such as:

Here you can see we've created a global animation called 'walk', which has a frame rate of 16fps. But we want a special type of uber-baddie that runs quicker than this, so when we call 'play' we pass in a config object with a new frame rate of 24fps. This overrides the default of 16fps. We also tell it to repeat the animation 7 times. The repeat was never set in the global animation, so this new value is specific to this Sprite instance only.

Any property you can set when creating an animation can now be overridden via any of the 'play' methods. This should help you cut down on the number of variations of global animations required where the only real differences were presentational.

Sprite vs Global

So far I've talked a lot about 'global' animations. That's because in Phaser 3 animations are created in a global cache, that any Game Object can use. This helps dramatically cut down on the total memory consumption of your games, as Sprites that use the same animations just hold a reference to them, rather than creating them every time they're instantiated.

However, sometimes animations don't need to be global, because only a single Game Object needs them. In these cases, you can now create animations directly on Sprites. This code demonstrates the difference:

Here's an example to run:

Aside from being able to use the same key, which is a bonus in itself, it also allows you to compartmentalize your code better. For example, if you've got a Player Sprite, and only they need the 'player' animations, it's perfectly possible for them to create those as part of their construction process and keep them local to it.

60 fps, Baby

Another fix that landed in 3.50 is the ability to specify animation frame rates far higher than the refresh rate the browser is running at. Previously, the animation fps would max out at whatever the Request Animation Frame rate was. So if for some reason, you had a 60fps animation but the display was only pushing 30fps, then the animation wouldn't run fast enough.

This issue never surfaced that often, because it's really quite rare to have animation fps rates that high. However, 3.50 now handles it properly and will keep ticking through the frames until the right one to be displayed is found.

You can see the end result in the above example - with more than a passing nod to Metal Slug 3 :)

Aseprite

Aseprite (https://www.aseprite.org/) is a fantastic animated sprite editor and pixel art tool. It has loads of great features built-in to handle animations, which is why loads of devs use it specifically for that.

It has long been able to export those animations as JSON files and texture atlases. And in Phaser 3.50 you can now use those files directly via the new method `createFromAseprite`.

It will generate an animation for each tag you've set-up to be exported, or you can pass in an array of tags to create animations just for those. Either way, it's now possible to have your game load and use Aseprite JSON / PNG files natively, which is a great win for animators everywhere :)

You can see this working in this example:

Click the animation tags to play them.

Check out the comprehensive method documentation for details on how to configure your export for this new feature.

Mix those Chains

The old Animation component gained the 'chain' method in version 3.16. It allows you to 'chain' an animation onto the end of the current one, so when the current one completes, the next one in the chain starts. This made it slightly easier to create more complex sequences.

The chain feature remains in 3.50 but gains the ability to take a whole array of animations, so it will chain each of them in sequence. More powerful, though, is the new mixing feature. This allows you to define two animations and set a custom delay between them, like in the following code:

And here it is in action:

It works on a similar principle to software like Spine where instead of just cutting directly from one animation to another, it lets you blend based on duration instead.

I'm also considering adding the ability to mix based on a specific frame because sometimes time isn't always accurate enough (especially for very pixel-art style animations).

Public Highway

Another big change in 3.50 is that I have exposed nearly all of the Animation properties, such as 'yoyo', 'repeat', and 'delay'. Previously, these were private and you had to go via set and get functions, which made doing things like tweening the repeatDelay impossible. Most of those get and set methods have now been removed and instead, you can hit the properties directly where it's safe to do so.

I've also overhauled the documentation. Although it already covered all methods and properties, what is there is now far more extensive. Classes, events, and prominent methods have far bigger descriptions with embedded example code and external references. I've taken a long time making the documentation much better in these areas, to help reduce confusion and speed-up your dev time.

I've also rebuilt every single Animation example in the Labs. I removed out-dated ones, fixed others, and in most cases totally recoded them. They're much more fun to look at, more descriptive, and will actually help you learn about animations :)

You can access all of these at labs.phaser.io - just be sure to change the Phaser Version to 'dev' on the drop-down when trying one of the examples that use new 3.50 features.

Massive Spine Update

It's not just Animations that have received some much-needed attention. I've also worked hard on the Spine Plugin which now includes some smart new features worth talking about here.

The first change is that I've updated to the latest Spine runtimes (3.8.95) which are now packaged into the plugin. Thankfully, you won't need to re-export your Spine animations as long as they were created in Spine 3.8.20 or above. I tidied up the plugin build process, so it's a lot easier to use now, should you want to upgrade the runtimes beyond this release.

I went through every single Spine issue in GitHub and fixed them all! Bugs like not being able to use more than 128 Spine objects in a Container, issues with performance when a mixture of Game Objects was present in the display list, patches for HEADLESS mode, and lots more.

The most important new feature, however, is the new Spine Container Game Object.

While previously it was possible to add Spine objects into regular Containers, they were not very efficient when it came to batching in WebGL due to the mixed nature of their contents. The Spine Container avoids this and, if your Container only needs Spine objects in it, you should absolutely use it in place of a regular Container. The code is almost identical:

The difference is remarkable, though. Running the above example creates 4 Spine Containers and adds 32 Spine objects to each one. Under Phaser 3.24 using regular Containers, this would consume 278 GL commands with 4 draw calls and 4 clears. Under 3.50 using Spine Containers, it's 43 GL commands and just 1 draw call and 1 clear. Use the cursor keys to move around in the example.

If you use Spine in your game and you have to use Containers, I strongly recommend swapping over.

Update that List

Another new change in 3.50 is the way in which Game Objects add themselves to the Scene Update List. Previously, it was the Factory methods that were responsible for ensuring that a Sprite, for example, would end up on both the Display List and Update List. This was fine if you used a Factory, but if you wanted to create a Sprite directly (or a class that extended it), it meant you had to then make sure the Sprite was added to the Update List as part of its constructor.

Under 3.50 you no longer needed to do this. There are a whole bunch of new events and callbacks that manage the addition and removal of objects from the Scene. The simple act of adding your Sprite to the Scene will be enough for its 'ADDED_TO_SCENE' handler to be invoked, which adds itself to the Scene's Update List.

This also means you can now add Sprite instances directly into a Container, without first adding them to the Scene, and they'll still update properly. It makes a lot more sense this way around and allows you to simplify your code. The key changes are as follows:

* GameObjects.Events.ADDED_TO_SCENE is a new event, emitted by a Game Object when it is added to a Scene, or a Container that is part of the Scene.

* GameObjects.Events.REMOVED_FROM_SCENE is a new event, emitted by a Game Object when it is removed from a Scene, or a Container that is part of the Scene.

* Scenes.Events.ADDED_TO_SCENE is a new event, emitted by a Scene, when a new Game Object is added to the display list in the Scene, or a Container that is on the display list.

* Scenes.Events.REMOVED_FROM_SCENE is a new event, emitted by a Scene, when it a Game Object is removed from the display list in the Scene, or a Container that is on the display list.

* GameObject.addedToScene is a new method that custom Game Objects can use to perform additional set-up when a Game Object is added to a Scene. For example, Sprite uses this to add itself to the Update List.

* GameObject.removedFromScene is a new method that custom Game Objects can use to perform additional tear-down when a Game Object is removed from a Scene. For example, Sprite uses this to remove themselves from the Update List.

Download v3.50 Beta 4

As you can see, Beta 4 contains masses more changes than the previous beta. There's lots more than I've covered above! I could really do with your help testing it, though. The more eyes-on it gets, the more solid the final release will be.

The new v3.50 Beta 4 is available from both npm and GitHub.

You can get it from npm using the beta tag:

Or download it from GitHub.

You'll find pre-built bundles to download in the dist folder, or you can check out the master branch and build yourself.

Note that it does not have updated TypeScript defs yet, so if you want to use those, please pull down the repo and use `npm run tsgen` to build new ones locally.

If you find an issue report it to me either on Discord, or (even better) open it as an issue on GitHub and tag it 3.50 Beta 4.

This should be the final beta of 3.50 before the full release in the coming days. If I had to put an ETA on it, I'd predict a release around the 18th of September. But, the more feedback and help with testing I get, the quicker I can achieve this.

Thank you to everyone who has been a part of 3.50. It's the biggest update Phaser has ever had and I'm really pleased with it so far. If you helped make it this good, via a GitHub issue, or even a suggestion on Discord, then thank you!