Phaser World Issue 218

Hello Hello! It’s been a busy week as we race towards the launch of Phaser v4 and the start of Phaser Editor v5. Thank you to everyone who has been helping test Phaser 4, you’ve found some truly impressive edge cases! And with each one resolved, it gets stronger and stronger. Our aim is to get RC2 out this week and then sit on that for a few weeks, giving us all the time to test it as well as flesh out the new examples and documentation. As for this issue of Phaser World? We’ve got a beefier Dev Log, new tutorials, and, of course, games! Enjoy.

This week:

🚀Studio Releases

» Phaser v4 Release Candidate 1

The latest version of Phaser brings lots of fixes and fully syncs the v3 API. We're in full flow on the path to the final release of Phaser v4. After all of the changes and fixes that landed in Beta 8 today we published Release Candidate 1. This version is special in that not only does it include all the latest fixes, but it also fully syncs all of the 'main' branch changes from Phaser v3.88 as well, making it completely up to date. Hence, the switch to 'Release Candidate' rather than Beta.

You can test v4 RC1 now in the Phaser Sandbox, or download it from GitHub or npm using the beta tag.

🎦 Phaser Showcase

» Hooshang Driver, Online Racing

Ready for a BREATHTAKING driving experience? Well, start your engines and check out Hooshang Driver, an online racing game made by Noyasystem on the Google Play store.

Perform incredible stunts, launch your car into the sky and spin through the air while soaring above stunning, lush landscapes.

» Wasteland Racer

A homage to 80's ZX Spectrum game "Tranz Am". Collect fuel, watch the temperature of your engine and stop your vehicle to quickly cool it down before it blows up!

You have three spare cars lined up for the race - good luck !

👨‍🏫 Tutorials

» How I optimized my Phaser 3 action game — in 2025

In this article, François reveals essential techniques for optimizing web-based games in 2025, focusing on performance improvements across devices. He shares his journey transforming a laggy mobile game into a smooth experience using methods such as implementing object pooling to prevent memory leaks, caching references for faster access, optimizing game loops with selective processing and more.

» Phaser JS and Spine 2D

This article explores using Phaser JS and Spine 2D to create rich and engaging games.

Spine 2D is a tool specifically designed for skeletal animations. The author explains how Spine 2D solves common problems with traditional spritesheet animations, such as large file sizes and long load times.

The article includes code samples for implementing Spine 2D with Phaser JS, covering installation, configuration, asset loading, and creating Spine objects in-game.

🪵Phaser Studio Developer Logs

This is what the Phaser Studio team was up to last week…

» Tales from the Pixel Mines by Ben Richards

2025-04-04

This week we released Phaser 4 release candidate 1. That means the beta is done! This is a big step towards finalizing this version.

The release candidate doesn't add much new stuff; it's mostly fixes, and a big blast of new features from the Phaser 3 branch that had been added over the past months while Phaser 4 was in development. But there are still some tales of adventure in amongst the pixels.

Snapshot Unpremultiplication

We now unpremultiply snapshots. A snapshot is generally taken via RenderTexture#snapshot(), and returns an HTMLImageElement for use outside the game. Unpremultiplication is... more complicated.

Premultiplication is a choice about alpha channel handling. The technical merits are a story for another day. In summary, it multiplies the color channels by the alpha channel, making them darker in transparent regions. Unfortunately, when we get pixels from a WebGL snapshot, they come out premultiplied, which typically renders as a dark fringe around objects. This is because images for display are not generally premultiplied, and they're interpreted wrongly.

Unpremultiplication simply divides the color channels by the alpha channel, restoring the expected channel values and eliminating dark fringes.

This is another one of those things that make texture more complicated than they seem at first, including:

  1. Premultiplied alpha

  2. Linear or sRGB color

  3. Orientation (does the Y axis go up or down?)

For reference, Phaser 4 expects unpremultiplied, sRGB, Y-downward textures. When sent to the GPU, they are automatically converted to premultiplied, linear, Y-upward textures. This is why compressed textures such as PVR must be encoded with premultiplied, linear, Y-downward values: they cannot be converted at runtime.

Scissor Updates: A Moral

I was testing an unrelated issue when I noticed something weird. Cameras with filters were sometimes failing to render.

Upon investigation, the problem was with WebGL's scissor settings: a rectangular area outside which nothing is drawn. Suppose we're drawing several cameras, each 100x150 pixels. Ordinarily, we'd set the scissor to [ 0, 0, 100, 150 ] for the first one, then [ 100, 0, 100, 150 ] for the one to the right, etc. But when we're drawing filters, we don't add the offset, as they all draw to separate framebuffers, instead of different places on the game canvas. Thus, all the cameras want the scissor to be [ 0, 0, 100, 150 ].

That seems straightforward! We can just leave the scissor in that state, right?

Not so fast. Those coordinates are in screen space. WebGL uses GL space, which - of course - measures from the bottom up. So we have to flip the Y coordinate upside-down.

For the framebuffers, this is simple. We're setting the scissor to the full height of the framebuffer, so it doesn't matter if we start from the top or the bottom. But for the main camera, drawing to the larger game canvas, we do need to change the coordinates. As this game is 600 pixels tall, the scissor we send to WebGL must be [ 0, 450, 100, 150 ].

Here's where I messed up with the original implementation. Our WebGL state management system recorded the screen coordinates, which were all [ 0, 0, 100, 150 ]. So when it saw this value twice in a row, it didn't change anything. And now the scissor is set to [ 0, 450, 100, 150 ]. That's outside the framebuffer, so nothing gets drawn.

The solution is simple: we should record the actual values we're sending to WebGL. We shouldn't alter them after caching them! This is a weird situation, but we were supposed to be caching the WebGL values, not intermediate values. Let this be a lesson: always cache the most directly relevant thing.

Spine Plugin Adaptation

Spine, from Esoteric Software, is a skeletal sprite animation tool and runtime. Phaser has supported it for a long time, so it's important that we get it running for Phaser 4.

I spent some time this week hacking Esoteric's Phaser 3 plugin to get it running in the new renderer. This largely revolves around the YieldContext and RebindContext render nodes, used in the Extern game object for integrating external renderers into the Phaser render system.

The challenges of this adaptation were mainly around WebGL state management. For example, the textures initially appeared garbled. This is because they were upside-down. Recall above, where I mentioned texture orientation? This is one of those situations. Spine assumes that WebGL is set to NOT flip the Y axis - but in Phaser 4, it is. Thus the texture coordinates are all effectively inverted, creating a scrambled image. Similar issues existed around which textures were bound to texture units.

This is all state management business. WebGL does a lot of things by changing its internal state; for example, we don't upload textures with a flag describing their orientation. Instead, we set the WebGL state to always flip orientation, then we upload a texture.

It's crucial to always know exactly what state WebGL is in. That's why I've created the state management system for Phaser 4. Our systems describe what state they want, and the state management system updates things to efficiently match these settings. It leaves nothing to chance. Unless somebody starts changing GL settings themselves, which is why we have the YieldContext and RebindContext nodes to force everything to match up, whatever changes have been made.

Esoteric are looking over my crude hacks now, so hopefully they have a formal Spine plugin for Phaser 4 soon! (I am not affiliated with Esoteric and I have no idea how busy they are. But if you're reading, I'm happy to offer guidance!)

Coming Up

Well, hopefully the Phaser 4 release, of course. We've still got a few things to check and polish, but anything remaining at this point is just a bug to fix.

It's been a long and challenging journey to get to this point. But now the starting line is finally in sight. With Phaser 4, we have a foundation to make amazing new things.

»👨‍💻Arian - Phaser Editor

Hello!

Over the past few days, I've been working on the next major version of Phaser Editor. As many of you know, Phaser 4 is almost ready for release, and the team is working to make Phaser Editor fully compatible with this version of Phaser.

Phaser 4 is a big update, especially regarding the renderers, but the API is practically compatible with Phaser 3. There are only a few points, such as Filters, formerly known as FX. So this has been an important point when updating Phaser Editor. Many of the FX we had in Phaser Editor 3 are compatible with the new filters, other FX are unmatched in Phaser 4, and some filters, such as the Mask filter, are new.

Fortunately, this week we completed the migration of the FX to the new filters. Here's how we applied the new mask filter to an image:

In record time, Ben made the necessary adjustments to Phaser 4 to make it compatible with the latest version of the Spine runtime (4.2). Thanks to this, Spine also has full support in Phaser Editor 5, with virtually no changes. We had some issues, and the Spine team will likely merge some of Ben's modifications, but overall everything went well. Many of you use Spine in your projects, so this is great news.

Creating a major version is hard work, as many elements of the editor need to be migrated: the documentation, the project templates, the Node Script libraries. But most of this is ready. Practically, all that remains is to update the documentation, which we're already working on.

The Node Script libraries are kept in the same repo. The Phaser 3 versions are in the phaser3 branch. You can also install them via NPM, for example:

npm install @phaserjs/editor-scripts-base@latest

The project templates were previously scattered across different repositories, each in its own repo. Now they are all in a single repo (https://github.com/phaserjs/phaser-editor-v5-starter-templates). This will help us keep them synchronized with future Phaser versions and update the metadata the editor uses to create projects. A process that previously had to be done manually is now automated.

Other issues related to a new major version of the editor, such as build scripts, digital signing, installer creation, etc., are ready. This means that as soon as Phaser 4 is released, we will be able to launch Phaser Editor 5 almost in parallel.

»👨‍💻Zeke's weekly Development Log

2025-04-04

Another busy week of updates as we prepare for the upcoming Phaser 3.89 release, codenamed "Tsugumi". We’ve got a mix of new features and important bug fixes based on your feedback.

Rectangle Game Objects Get Rounded!

One of the most exciting additions this week is the ability to create rectangles with rounded corners. This has been a frequently requested feature, and I'm happy to share we've added three new properties to make this possible:

  1. GameObjects.Rectangle.setRounded - Set the radius for rounded corners (or pass zero to disable)

  2. GameObjects.Rectangle.isRounded - A read-only boolean to check if corners are rounded

  3. GameObjects.Rectangle.radius - A read-only property for the corner radius size

The implementation is straightforward - just call setRounded(radius) on any Rectangle Shape Game Object to apply the effect!

New Math Angle Functions

Thanks to @samme for contributing three new useful angle calculation methods in the Math class:

  1. Phaser.Math.Angle.GetClockwiseDistance() - Gets the shortest nonnegative angular distance between angles

  2. Phaser.Math.Angle.GetCounterClockwiseDistance() - Gets the shortest nonpositive angular distance

  3. Phaser.Math.Angle.GetShortestDistance() - Gets the shortest signed angular distance (like ShortestBetween but in radians)

These functions make working with angles much more convenient and solve some common challenges when implementing rotation behaviors.

BitmapText Display Size

Thanks @samme for adding a new setDisplaySize method to the BitmapText Game Object, making it easier to scale these text objects while maintaining the correct proportions. This helps create consistent UI elements across different screen sizes.

Firefox Web Audio Support

Firefox users will be happy to know we've added a fallback for Web Audio. Firefox doesn't currently implement positionX, positionY, and positionZ properties on AudioListener instances, which previously prevented the follow feature from working. Thanks to @raaaahman for implementing this fix!

Bug Fixes

We've resolved several important issues:

  1. Fixed the EXPAND Scale Mode to prevent canvas from growing too large on ultra-wide displays

  2. Fixed particle emitter color RGB arrays by properly clearing them before repopulating

  3. Fixed animation frame durations

  4. Fixed particle emitter custom moveTo functions

  5. Fixed ImageCollections default Tileset values

  6. Fixed chained tweens to persist correctly after stopping

  7. Added direction fixes for Text Game Objects (resolving Chrome/Edge 134 rendering issues)

  8. Fixed Grid Game Objects to render lineWidth correctly in WebGL mode

  9. Fixed collision handling for physics objects with unique collision categories

  10. Fixed Arcade Physics bug where immovable circle objects moved when pushed by polygons

Next week

More polishing and resolving issues for the 3.89 release and starting documentation updates for new features. As always, keep those GitHub issues and pull requests coming - they're incredibly valuable for making Phaser better for everyone!

Have a great week ahead!

Share your content with 18,000+ readers

Have you created a game, tutorial, code snippet, video, or anything you feel our readers would like?

Please send it to us!

Until the next issue, happy coding!