changelog/v4/4.0/CHANGELOG-v4.0.0.md
Phaser v4 is a major release built on a brand-new, highly efficient WebGL renderer. The entire rendering pipeline from Phaser v3 has been replaced with a modern render node architecture that manages WebGL state, supports context restoration, and prioritizes performance. Alongside the new renderer, v4 brings new game objects, a unified filter system replacing both FX and Masks, a rewritten camera system, overhauled tint and shader APIs, a new lighting model, and hundreds of fixes and improvements.
For a step-by-step guide on updating your v3 project, see the Migration Guide.
The entire WebGL renderer from Phaser v3 has been replaced. The v3 Pipeline system, where individual pipelines frequently held multiple responsibilities and had to manage WebGL state themselves, has been removed. In its place is a new RenderNode architecture. Each render node handles a single rendering task, making the system more maintainable and reliable. All render nodes have a run method, and some have a batch method to assemble state from several sources before invoking run.
It is generally not necessary to interact with render nodes directly, but game objects maintain defaultRenderNodes and customRenderNodes maps for configuration. Use RenderConfig#renderNodes to register custom render nodes at boot.
The following internal renderer properties have been removed:
WebGLAttribLocationWrapper as it is unused.WebGLRenderer.textureIndexes as glTextureUnits.unitIndices now fills this role.WebGLRenderer.WebGLRenderer#genericVertexBuffer and #genericVertexData removed.
BatchHandlerConfig#createOwnVertexBuffer type property removed.The Canvas renderer is still available but should be considered deprecated. Canvas rendering does not support any of the WebGL techniques used in Phaser v4's advanced rendering features. Many features from Phaser 3 never worked in Canvas, and almost everything new in Phaser 4 is not available in Canvas. As WebGL support is effectively baseline today, we recommend focusing on WebGL.
Canvas does retain one advantage: a wider range of blend modes (27 modes vs WebGL's 4 native modes of NORMAL, ADD, MULTIPLY and SCREEN). The new Blend filter can recreate all of these Canvas blend modes in WebGL, though it requires indirection through a CaptureFrame, DynamicTexture, or similar.
FX and Masks from Phaser v3 have been unified under a single system called Filters. A Filter takes an input image and creates an output image, usually via a single shader program. This ensures all filters are compatible with one another, even if they have no knowledge of each other.
Filters can be applied to any game object or scene camera. Phaser 3 had restrictions on which objects supported FX and whether preFX and postFX were available. Phaser 4 removes this restriction entirely. You can even apply filters to Extern objects.
Filters are divided into internal and external lists. Internal filters affect just the object itself. External filters affect the object in its rendering context, usually the full screen. Internal filters are a good way to have filters match the position of the object. Some objects cannot define the internal space (objects without width or height, and Shape objects whose stroke may extend beyond reported bounds), so they use the external space instead.
Removed FX (replaced by Actions or Game Objects):
The following derived FX from v3 have been removed and replaced:
Bloom FX is now Phaser.Actions.AddEffectBloom(), which creates a set of filters applying bloom to a target Camera or GameObject.Shine FX is now Phaser.Actions.AddEffectShine(), which creates a Gradient and blends a shine across the target.Circle FX is now a case of Phaser.Actions.AddMaskShape(), which creates a Shape and uses it to add a mask.Gradient FX is replaced by the new Gradient game object, as it replaces the image entirely rather than altering it.Removed masking classes:
BitmapMask has been removed. It was only used in WebGL, which now has the Mask filter for more powerful masking operations.GeometryMask remains available in the Canvas renderer but is not used in WebGL.ColorMatrix filter change:
The existing ColorMatrix filter shifted its color management methods onto a colorMatrix property. For example, you now call colorMatrix.colorMatrix.sepia() instead of colorMatrix.sepia().
The tint API has been overhauled with a new mode-based system and additional blend modes.
Tint is overhauled.
tint and setTint() now purely affect the color settings.
tintFill and setTintFill() are removed.tintMode and new method setTintMode() now set the tint fill mode.Phaser.TintModes enumerates valid tint modes.
MULTIPLYFILLADDSCREENOVERLAYHARD_LIGHTfoo.setTintFill(color) becomes foo.setTint(color).setTintMode(Phaser.TintModes.FILL).The camera matrix system has been rewritten. If you only use standard camera properties (scrollX, scrollY, zoom, rotation), your code should work without changes. If you access camera matrices directly, you must update your code.
Camera#matrix now includes scroll, and excludes position.Camera#matrixExternal is a new matrix, which includes the position.Camera#matrixCombined is the multiplication of matrix and matrixExternal. This is sometimes relevant.GetCalcMatrix(src, camera, parentMatrix, ignoreCameraPosition) method now takes ignoreCameraPosition, causing its return value to use the identity matrix instead of the camera's position.GetCalcMatrixResults now includes a matrixExternal property, and factors scroll into the camera and calc matrices.TransformMatrix#copyWithScrollFactorFrom(matrix, scrollX, scrollY, scrollFactorX, scrollFactorY). This generally replaces cases where phrases such as spriteMatrix.e -= camera.scrollX * src.scrollFactorX were used.Phaser v3 represented textures using top-left orientation, which led to mismatches: framebuffers would be drawn upside-down, then flipped to draw to the screen. Phaser v4 has switched to using GL orientation throughout. This is largely invisible to the user as Phaser handles texture coordinate translation automatically, but some shader code may need to be revised as top and bottom may have switched.
Texture coordinates now match WebGL standards. This should bring greater compatibility with other technologies. Note that compressed textures must be re-compressed to work with this system: ensure that the Y axis starts at the bottom and increases upwards.
In Phaser v3, DynamicTexture allowed you to define batches and perform intricate drawing operations directly. While efficient, this was too technical for most uses and it used its own drawing logic, creating compatibility issues. In Phaser v4, many of these complex methods have been removed. Instead, the basic rendering system is used, which supports batching automatically.
DynamicTexture and RenderTexture must call render() to actually draw.The Shader game object has been significantly rewritten for Phaser v4. Existing Shader objects from v3 will need to be updated.
ShaderQuadConfig) which allows you to configure the way the shader executes.The way Phaser loads GLSL code has changed:
#pragma preprocessor directives, which are valid GLSL and work with automated syntax checkers. The pragmas are removed before compilation and serve merely as identifiers for custom templates.In Phaser v3, lighting was added to objects by assigning a new pipeline. In Phaser v4, simply call gameObject.setLighting(true). You don't need to worry about how lighting is applied internally.
In Phaser v3, lights had an implicit height based on the game resolution. In Phaser v4, lights have a z value to set height explicitly.
Note that lighting changes the shader, which breaks batches.
In Phaser v3, TileSprite used WebGL texture wrapping parameters to repeat the texture. This approach only repeated the entire texture file and had problems with compressed textures, non-power-of-two textures, and DynamicTextures.
In Phaser v4, TileSprite uses a different shader that manually controls texture coordinate wrapping. This now supports any texture and can use frames within that texture, enabling texture atlas and spritesheet support.
TileSprite no longer supports texture cropping.Grid has changed property names to follow the conventions of other Shapes: it has a stroke instead of an outline. Grid also has controls for how to render the gutters between grid cells, and whether to draw outlines on the outside of the grid or just between cells.The Geom.Point class and all related functions have been removed. All functionality can be found in the existing Vector2 math classes. All Geometry classes that previously created and returned Point objects now return Vector2 objects instead.
Method mapping:
| Removed | Replacement |
|---|---|
Point.Ceil | Vector2.ceil |
Point.Floor | Vector2.floor |
Point.Clone | Vector2.clone |
Point.CopyFrom(src, dest) | dest.copy(src) |
Point.Equals | Vector2.equals |
Point.GetCentroid | Math.GetCentroid |
Point.GetMagnitude | Vector2.length |
Point.GetMagnitudeSq | Vector2.lengthSq |
Point.Invert | Vector2.invert |
Point.Negative | Vector2.negate |
Point.SetMagnitude | Vector2.setLength |
Point.Project | Vector2.project |
Point.ProjectUnit | Vector2.projectUnit |
Point.Interpolate | Math.LinearXY |
Point.GetRectangleFromPoints | Math.GetVec2Bounds |
New Vector2 and Math methods:
Vector2.ceil is a new method that will apply Math.ceil to the x and y components of the vector. Use as a replacement for Geom.Point.Ceil.Vector2.floor is a new method that will apply Math.floor to the x and y components of the vector. Use as a replacement for Geom.Point.Floor.Vector2.invert is a new method that will swap the x and y components of the vector. Use as a replacement for Geom.Point.Invert.Vector2.projectUnit is a new method that will calculate the vector projection onto a non-zero target vector. Use as a replacement for Geom.Point.ProjectUnit.Math.GetCentroid is a new function that will get the centroid, or geometric center, of a plane figure from an array of Vector2 like objects. Use as a replacement for Geom.Point.GetCentroid.Math.GetVec2Bounds is a new function that will get the AABB bounds as a Geom.Rectangle from an array of Vector2 objects. Use as a replacement for Geom.Point.GetRectangleFromPoints.Geometry classes updated to return Vector2:
Geom.Circle.getPoint, getPoints and getRandomPoint now all return Vector2 objects instead of Point.Geom.Circle.CircumferencePoint, Circle.CircumferencePoint, Circle.GetPoint, Circle.GetPoints, Circle.OffsetPoint and Circle.Random all now take and in some cases return Vector2 instances instead of Point objects.Geom.Ellipse.getPoint, getPoints and getRandomPoint now all return Vector2 objects instead of Point.Geom.Ellipse.CircumferencePoint, Ellipse.CircumferencePoint, Ellipse.GetPoint, Ellipse.GetPoints, Ellipse.OffsetPoint and Ellipse.Random all now take and in some cases return Vector2 instances instead of Point objects.Geom.Line.getPoint, getPoints and getRandomPoint now all return Vector2 objects instead of Point.Geom.Line.GetEasedPoint, Line.GetMidPoint, Line.GetNearestPoint, Line.GetNormal, Line.GetPoint, Line.GetPoints, Line.Random and Line.RotateAroundPoint all now take and in some cases return Vector2 instances instead of Point objects.Geom.Polygon.getPoints method now returns Vector2 objects instead of Point.Geom.Polygon.ContainsPoint and Polygon.GetPoints all now take and in some cases return Vector2 instances instead of Point objects.Geom.Rectangle.getPoint, getPoints and getRandomPoint now all return Vector2 objects instead of Point.Geom.Rectangle.ContainsPoint, Rectangle.GetCenter, Rectangle.GetPoint, Rectangle.GetPoints, Rectangle.GetSize, Rectangle.MarchingAnts, Rectangle.MergePoints, Rectangle.OffsetPoint, Rectangle.PerimeterPoint, Rectangle.Random and Rectangle.RandomOutside all now take and in some cases return Vector2 instances instead of Point objects.Geom.Triangle.getPoint, getPoints and getRandomPoint now all return Vector2 objects instead of Point.Geom.Triangle.Centroid, Triangle.CircumCenter, Triangle.ContainsArray, Triangle.ContainsPoint, Triangle.GetPoint, Triangle.GetPoints, Triangle.InCenter, Triangle.Random and Triangle.RotateAroundPoint all now take and in some cases return Vector2 instances instead of Point objects.Math.TAU is now actually the value of tau! (i.e. PI * 2) instead of being PI / 2.Math.PI2 has been removed. You can use Math.TAU instead. All internal use of PI2 has been replaced with TAU.Math.PI_OVER_2 is a new constant for PI / 2 and all internal use of TAU has been updated to this new constant.Phaser.Struct.Set has been replaced with a native JavaScript Set. Methods like iterateLocal are gone. Use standard Set methods instead.Phaser.Struct.Map has been replaced with a native JavaScript Map. Methods like contains and setAll are gone. Use standard Map methods instead.We have removed the ability for Phaser v4 to load Wavefront OBJ files and render them via the very limited Mesh Game Object. Proper 3D support is planned for the future. As a result, the following has been removed:
Mesh Game Object.Plane Game Object.OBJ File Type Loader.geom/mesh folder, including: Face, GenerateGridVerts, GenerateObjVerts, ParseObj, ParseObjMaterial, RotateFace and Vertex.obj global BaseCache entry.The following have been removed entirely:
phaser-ie9.js entry point.Create.GenerateTexture function and all Create Palettes and the create folder.TextureManager.generate (as a result of the GenerateTexture removal).Math.SinCosTableGenerator.Shader#setTextures() now replaces the texture array, rather than adding to it.Camera#preRender().TransformMatrix#setQuad parameter roundPixels, as it is no longer used.All new game objects are WebGL-only unless otherwise noted.
GameObjects.Gradient is a new game object which renders gradients.
LINEARBILINEARRADIALCONIC_SYMMETRICCONIC_ASYMMETRICEXTEND: flat colors extend from start and end.TRUNCATE: transparency extends from start and end.SAWTOOTH: gradient starts over every time it completes.TRIANGULAR: gradient reverses direction every time it gets to the end or start.ColorRamp containing ColorBand objects. Every number between 0 and 1 corresponds to a precise color from the gradient ramp, accessible through code.GameObjects.Noise renders noise patterns.
GameObjects.NoiseCell2D, NoiseCell3D and NoiseCell4D provide cellular/Worley/Voronoi noise.
GameObjects.NoiseSimplex2D and NoiseSimplex3D provide simplex noise.
GameObjects.NineSlice has two new parameters: tileX, tileY, which allow non-corner regions of the NineSlice to tile instead of stretch. Some stretching is still applied to keep the tile count a whole number. Thanks to @skhoroshavin for this contribution!GameObjects.SpriteGPULayer is a new high-performance game object optimized for rendering very large numbers of quads. It is suited to complex animated backgrounds and particle-like effects. It stores rendering data in a GPU buffer and renders all members in a single draw call. Because it only updates the GPU buffer when necessary, it is up to 100 times faster than rendering the same objects individually. The layer can generally perform well with a million small quads. See the dedicated SpriteGPULayer section below for full details.GameObjects.TilemapGPULayer is a new high-performance tilemap renderer. It renders an entire tilemap layer as a single quad via a specialized shader, with a fixed cost per pixel on screen regardless of how many tiles are visible. See the dedicated TilemapGPULayer section below for full details.GameObjects.Stamp renders a quad without any reference to the camera. This is mostly used for DynamicTexture operations. Available in both WebGL and Canvas.CaptureFrame game object, which copies the current framebuffer to a texture when it renders. This is useful for applying post-processing prior to post.GameObject#isDestroyed flag helps you avoid errors when accessing an object that might have removed expected properties during destruction.Filters can be applied to any game object or scene camera.
Blend filter combines the input with a texture. This is similar to blend modes, but supports all the blend modes available in the Canvas renderer (not just WebGL's 4 native modes). Also supports overdriving, mixing outside the usual 0-1 range, for useful color effects.Blocky filter added. This is similar to Pixelate, but it picks just a single color from the image, preserving the palette of pixel art. You can also configure the pixel width and height, and offset. This is a good option for pixelating a retro game at high resolution, setting up for additional filters such as CRT emulation.CombineColorMatrix filter for remixing alpha and other channels between images.GradientMap filter for recoloring images using a gradient and their own brightness.ImageLight filter for image-based lighting, a soft, highly realistic form of illumination. Supports 360 degree panoramas for realistic reflections, or simpler gradients and images for the impression of a natural scene.Key filter for removing or isolating colors.Mask filter takes the place of masks from Phaser 3. It can take a texture, or a game object which it draws to a DynamicTexture. A Container with other objects, even objects with their own filters and masks, is a valid mask source. Supports scaleFactor parameter for creating scaled-down framebuffers to save memory in large games. Thanks to kimdanielarthur-cowlabs for developing the initial solution.NormalTools filter for manipulating normal maps. Supports rotation, squishing, and "ratio" output measuring how closely the surface faces the viewpoint.PanoramaBlur filter for adjusting images for ImageLight. Runs in spherical space to average out 360 degree panorama files for diffuse environment maps.Parallel Filters filter passes the input image through two different filter lists and combines them at the end. Useful when you want some memory in a complex stack of filters.Quantize filter for reducing colors and dithering. Implements dithering with Interleaved Gradient Noise for excellent quality even with very few colors.Sampler filter extracts data from the WebGL texture and sends it back to the CPU for use in a callback. Similar to the snapshot functions available on DynamicTexture.Threshold filter applies a soft or hard threshold to the colors in the image.Vignette filter returns from Phaser 3.
Wipe filter returns from Phaser 3.
Layer.Filter component: setFiltersAutoFocus, setFiltersFocusContext, setFiltersForceComposite, setRenderFilters.Actions.AddEffectBloom allows you to quickly set up a bloom effect, using several filters, on a target Camera or GameObject.Actions.AddEffectShine allows you to quickly set up a shine effect, using a new Gradient and filters, on a target Camera or GameObject.Actions.AddMaskShape allows you to quickly add shapes to a target Camera or GameObject as Masks. Blurred edges and inversion are supported.Actions.FitToRegion transforms an object to fit a region, such as the screen.gameObject.setLighting(true) instead of assigning a pipeline.z value to set height explicitly, replacing the implicit height based on game resolution from Phaser v3.WebGLSnapshot (used in snapshot functions) supports unpremultiplication, which is on by default. This removes dark fringes on text and objects with alpha.RenderConfig#renderNodes allows you to add render nodes at game boot.ShaderQuadConfig#initialUniforms lets you initialize a Shader with uniforms on creation.Shader#setUniform(name, value) lets you set shader program uniforms just once, instead of putting them all into the setupUniforms() method, where some uniforms might be set redundantly after init. This wraps Shader#renderNode.programManager.setUniform.BatchHandlerQuadSingle render node added.
BatchHandlerQuad with space for 1 quad.Camera has the new property isObjectInversion, used internally to support special transforms for filters.Shader has the new method renderImmediate, which makes it straightforward to use renderToTexture when the object is not part of a display list, or otherwise needs updating outside the regular render loop.RenderWebGLStep to take the currently rendering object list and index as parameters. This allows render methods to know their context in the display list, which can be useful for optimizing third-party renderers.
nextTypeMatch from Phaser v3, but is much more flexible.GameObject#vertexRoundMode added to control vertex pixel rounding on a per-object basis.
"off": Never round vertex positions."safe": Round vertex positions if the object is "safe": it is rendering with a transform matrix which only affects the position, not other properties such as scale or rotation."safeAuto" (default): Like "safe", but only if rendering through a camera where roundPixels is enabled."full": Always round vertex positions. This can cause sprites to wobble if their vertices are not safely aligned with the pixel resolution, e.g. during rotations. This is good for a touch of PlayStation 1 style jank."fullAuto": Like "full", but only if rendering through a camera where roundPixels is enabled.GameObject#willRoundVertices(camera, onlyTranslated) returns whether vertices should be rounded. In the unlikely event that you need to control vertex rounding even more precisely, you are intended to override this method.smoothPixelArt config option supports antialiasing while preserving sharp texels when scaled up. This is usually the correct choice for retro graphics with big pixels that need to rotate or scale smoothly.Display.Color: several helper methods now support modifying an existing Color object instead of creating a new one.
HSLToColorHexStringToColorIntegerToColorObjectToColorRGBStringToColorValueToColorDisplay.Color.Interpolate: an extra interpolation mode is available.
HSVWithHSV: new method to interpolate HSV values, in HSV space.ColorWithColor has new parameters to allow it to operate in HSV space.
hsv flag sets it to operate in HSV space.hsvSign flag can force it to interpolate hue either ascending or descending. Default behavior picks the shortest angle.Display.ColorBand describes a transition between two colors. Intended for use in gradients.Display.ColorRamp describes a range of colors using ColorBands. Intended for use in gradients.Math.Hash provides fast hashes of 1, 2, 3, or 4 dimensional input, using trigonometric or PCG methods.Math.HashCell provides hashes of 1, 2, 3, or 4 dimensional input, using hash results in a Worley noise field. This produces a continuous but lumpy field.Math.HashSimplex provides hashes of 1, 2, or 3 dimensional input, using a simplex noise implementation. This produces a continuous, smooth field.Texture#setWrap() provides easy access to texture wrap mode in WebGL, which would otherwise be very technical to alter on WebGLTextureWrapper objects. This is probably of most use to shader authors. Thanks @Legend-Master for raising an issue where power-of-two sprites had unexpected wrapping artifacts.Phaser.Textures.WrapMode.CLAMP_TO_EDGE is always available.Phaser.Textures.WrapMode.REPEAT will only be applied to textures with width and height equal to powers of 2.Phaser.Textures.WrapMode.MIRRORED_REPEAT likewise requires powers of 2.Texture#setSource method for updating the source of a texture. Note that, while the source will update, derived values such as object sizes will not. It's advisable to switch between textures of identical size to avoid unexpected transforms.Texture#setDataSource method already existed, but has been changed to be more useful like setSource.TextureManager#addFlatColor method for creating a flat texture with custom color, alpha, width, and height. This is intended to act as a temporary stand-in for textures you might not have loaded yet.TextureSource#updateSource method for switching sources directly.Phaser.Types.Textures.TextureSource and Phaser.Types.Textures.TextureSourceElement types to simplify the increasing number of sources for a texture.Phaser v4 introduces a new texture atlas format called the Phaser Compact Texture (PCT) atlas. It is a line-oriented text descriptor designed as a drop-in replacement for verbose JSON or XML based atlas files, while remaining trivially parsable at runtime. A single .pct file can describe one or many atlas pages and is supported throughout the Loader, Texture Manager, and Atlas Cache.
Why use it: PCT files are typically 90-95% smaller than equivalent JSON atlas descriptors. For an atlas with hundreds of frames this can mean a multi-kilobyte JSON file collapsing to a few hundred bytes of plain text — important for fast cold-start loads, mobile data budgets, and games with many atlases.
How to create PCT files: A free on-line texture packer tool will be available on the Phaser website at https://phaser.io/tools/ or you can feed the specification file to your favorite AI agent and have it create one.
How it works: A .pct file is plain UTF-8 text. Each line is a record identified by a short prefix (PCT:, P:, F:, #, B:, A:, or an individual frame line). The format includes:
B: record plus one names line, instead of one record per frame.walk_01 through walk_24 collapse to walk_#01-24.F: table and referenced by index..png, .webp, .jpg, .jpeg, .gif) are encoded as a single trailing digit.A: records..pct file can declare multiple texture pages and route frames to the correct one with #N page selectors.The format is versioned (PCT:1.0) using major.minor semver-style numbering, so future minor revisions can add features without breaking older parsers.
How to use it: Load a PCT atlas exactly the same way you would load any other Phaser asset, using the new LoaderPlugin#atlasPCT method:
function preload ()
{
this.load.atlasPCT('level1', 'images/Level1.pct', 'images');
}
function create ()
{
this.add.image(x, y, 'level1', 'warrior/idle_01.png');
// The decoded structure is also available from the Atlas Cache
var data = this.cache.atlas.get('level1');
}
The Loader reads the .pct file, decodes it, queues each referenced texture page as a separate image, and once everything has loaded, assembles a single multi-source Texture in the Texture Manager. The decoded { pages, folders, frames } object is also stored in a new Phaser.Cache.CacheManager#atlas cache so you can inspect the page and folder metadata at runtime.
Specification: A complete description of the format — record types, decoding algorithm, helper functions, and worked examples — lives at docs/Phaser Compact Texture Atlas Format Specification/Phaser Compact Texture Atlas Format Specification.md. Anyone wanting to write a PCT exporter for a different tool can implement the loader directly from that document.
API surface:
LoaderPlugin#atlasPCT(key, url) method registered via FileTypesManager. Accepts a string key and URL, an array of file definitions, or a config object with key, atlasURL, path, baseURL, and xhrSettings.Phaser.Loader.FileTypes.PCTAtlasFile MultiFile class which loads the .pct data file, dynamically queues an ImageFile for each page declared inside it, and adds the assembled atlas to the Texture Manager when all of its children are loaded.Phaser.Textures.TextureManager#addAtlasPCT(key, source, data, dataSource) method for adding a decoded PCT atlas to a Texture. The existing addAtlas method now auto-detects PCT data by shape and dispatches to it, alongside the existing JSON Array and JSON Hash dispatch.Phaser.Textures.Parsers.PCT(texture, decoded) parser which iterates the decoded frames and creates Frame instances on the matching TextureSource, including trim and rotation handling. The PCT page and folder metadata is copied onto Texture#customData.pct for later inspection.Phaser.Textures.Parsers.PCTDecode(text) standalone helper which converts raw PCT text into the structured { pages, folders, frames } object, validates the version header, and is safe to call directly if you have PCT text from a non-Loader source.Phaser.Cache.CacheManager#atlas BaseCache instance, used to store decoded PCT data alongside its texture entry in the Texture Manager. Accessible from a Scene as this.cache.atlas.SpriteGPULayer is a new WebGL-only game object optimized for rendering very large numbers of quads following simple tween-style animations. It is suited to complex animated backgrounds, particle-like effects, and any scenario where you need to render far more sprites than the standard rendering path allows.
How it works: SpriteGPULayer stores rendering data for all its member quads in a GPU buffer and renders them in a single draw call. Because it only updates the GPU buffer when the data actually changes, it is up to 100 times faster than rendering the same objects individually. Standard Phaser rendering can handle tens of thousands of sprites with good performance; SpriteGPULayer can handle a million or more.
Why it's fast: Regular sprites compute their properties on the CPU every frame and upload the results to the GPU -- that per-frame upload is the main performance bottleneck. SpriteGPULayer skips this entirely by keeping a static buffer on the GPU. The trade-off is memory (168 bytes per member on both CPU and GPU) and reduced flexibility for runtime changes.
Member capabilities: Each member in the layer supports:
loop: false) for one-off particle effects and dynamic sources.Animation easing: Members support a comprehensive set of GPU-computed easing functions: Linear, Gravity, Quad, Cubic, Quart, Quint, Sine, Expo, Circ, Back, Bounce, Stepped, and Smoothstep -- each with easeIn, easeOut, and easeInOut variants. Animations support yoyo and delay. The Gravity ease mode provides physics-style acceleration with configurable velocity and gravity factor.
Texture requirements: SpriteGPULayer uses a single texture image (not a multi-atlas). For pixel art or round pixels, use a power-of-two texture to avoid seaming. For smooth mode, add padding around each frame. Single-image textures or textures where frames don't need to tile are unaffected.
Populating efficiently: Rather than creating a new SpriteGPULayer.Member config object for each addMember call, you should reuse the same object and edit its properties between calls. Creating millions of JavaScript objects has a significant allocation and garbage collection cost, so reusing a single config can reduce initialization time from tens of seconds to under a second.
Modifying the layer: The following operations require buffer updates and are expensive: addData, addMember, editMember, patchMember, resize, removeMembers, insertMembers, insertMembersData. The buffer is split into segments so that edits to a small region only update the affected segment. If you need to "remove" a member without costly buffer splicing, set its scaleX, scaleY, and alpha to 0 instead -- it will still be rendered but will fill no pixels.
API surface:
addMember(member) -- Add a member to the layer. This is the easiest way to populate it.addData(data) -- Add raw Float32Array data to the buffer for maximum efficiency.editMember(index, member) -- Replace a member's data at a given index.patchMember(index, data, mask) -- Update specific properties of a member using raw data and an optional mask.getMember(index) -- Get a copy of a member's data as a readable object.getMemberData(index, out) -- Get the raw Uint32Array data of a member for efficient editing.insertMembers(index, members) -- Insert one or more members at a specific position.insertMembersData(index, data) -- Insert raw data at a specific position.removeMembers(index, count) -- Remove members from the layer (causes full buffer update).resize(count, clear) -- Resize the layer buffer.setAnimations(animations) -- Define frame animations available to members.setAnimationEnabled(name, enabled) -- Enable or disable an easing function in the shader. Every enabled animation has a shader cost; low-end devices may be unable to compile many simultaneously.getDataByteSize() -- Get the byte stride per member for direct buffer manipulation.SpriteGPULayer efficiently.SpriteGPULayer#insertMembers method.SpriteGPULayer#insertMembersData method.SpriteGPULayer#getDataByteSize method.SpriteGPULayer (set animation to loop: false) to support one-time particle effects and dynamic sources.SpriteGPULayer members.TilemapGPULayer is a new WebGL-only tilemap renderer that renders an entire tilemap layer as a single quad via a specialized shader. It is optimized for speed and visual quality over flexibility.
How it works: The layer encodes its tile data and any tile animations into GPU textures, then renders the full layer in a single draw call. Because the shader has knowledge of the full layer, it can accurately blend across tile boundaries, producing perfect texture filtering with no seams or bleeding when antialiasing is enabled -- something a regular TilemapLayer cannot achieve. In LINEAR filter mode, borders between tiles are rendered smoothly. In NEAREST mode, sharp pixel edges are preserved.
Performance: The rendering cost is fixed per pixel on screen, regardless of how many tiles are visible. It suffers no performance loss when many tiles are visible. It can render a layer up to 4096 x 4096 tiles and will render the entire layer just as quickly if the camera zooms out to see all 16 million tiles. This makes it a superior choice when large numbers of tiles need to be on screen at once, particularly on mobile platforms. It is almost entirely GPU-bound, freeing up CPU resources for other game code.
How to create one: Add the gpu flag to a call to Tilemap.createLayer(). This returns a TilemapGPULayer instance instead of a regular TilemapLayer.
Capabilities and restrictions:
Editing: The layer can be edited after creation, but changes do not apply automatically. Call generateLayerDataTexture() to regenerate the tile data texture after making edits.
TilemapLayer and TilemapGPULayer now support a parent matrix during rendering.TileSprite now uses a new shader that manually controls texture coordinate wrapping, replacing the old approach of relying on WebGL texture wrapping parameters. This enables several new capabilities:
tileRotation property allows you to rotate the repeating texture.repeat() method on DynamicTexture now uses TileSprite behind the scenes, extending its capabilities.RenderTexture to automatically re-render.
DynamicTexture#preserve() allows you to keep the command buffer for reuse after rendering.DynamicTexture#callback() allows you to run callbacks during command buffer execution.RenderTexture.setRenderMode() allows you to set the RenderTexture to automatically re-render during the render loop.RenderTexture has a new renderMode property. When set to "render", it draws like an ordinary Image. When set to "redraw", it runs render() to update its texture during the render loop but does not draw itself. When set to "all", it does both. The "redraw" mode allows updating a texture during the render loop, enabling you to draw things that have only just updated, such as same-frame shader outputs.DynamicTexture#capture, for rendering game objects more accurately and with greater control than draw.TextureManager#addDynamicTexture now has forceEven parameter.TilemapLayer and TilemapGPULayer now support a parent matrix during rendering.sortByY parameter to the Tilemap createFromObjects method (thanks @saintflow47)All enhancements from late Phaser v3 development have been merged into v4. This includes:
Transform#getWorldPointLayer#getDisplayListDynamicTexture and RenderTexture changes:
forceEven parameter forces resolution to be divisible by 2.clear(x, y, width, height) method now takes the listed optional parameters.Rectangle now supports rounded corners.Physics.Matter.Components.Transform#scale for setting scaleX and scaleY together.WebGLRenderer reveals functions around context loss:
setExtensionssetContextHandlersdispatchContextLostdispatchContextRestoredTween#isNumberTweenExtern#render function.Shape now sets filtersFocusContext = true by default, to prevent clipping stroke off at the edges.Graphics has a new pathDetailThreshold property (also available as a game config option) that skips vertices within a certain distance of one another, greatly improving performance on complex curves displayed in small areas.BatchHandler render nodes now create their own WebGL data buffers.
roundPixels game option to false by default. It's very easy to get messy results with this option, but it remains available for use cases where it is necessary.roundPixels to only operate when objects are axis-aligned and unscaled. This prevents flicker on transforming objects.ignoreDestroy is enabled. This supports multi-owner Filter controllers.GameObject#enableLighting now works even if the scene light manager is not enabled. The light manager must still be enabled for lights to render, but the game object flag can be set at any time.YieldContext and RebindContext render nodes now unbind all texture units. These nodes are used for external renderer compatibility. An external renderer could change texture bindings, leading to unexpected textures being used, so we force texture rebind.Button class now has a new optional isPressed boolean parameter which the Gamepad class uses to resolve this, initializing the current pressed state of the Button (thanks @cryonautlex)Tilemap.createLayer() with gpu flag enabled only works with orthographic layers, not hexagonal or isometric. Thanks @amirking59!PhysicsGroup.add and StaticPhysicsGroup.add will now check to see if the incoming child already has a body of the wrong type, and if so, will destroy it so the new correct type can be assigned. Fix #7179 (thanks @bvanderdrift)WebGLSnapshot orientation.WebGLSnapshot and snapshot functions based on it now return the correct pixel, instead of the one above it (or nothing if they're at the top of the image).RenderSteps parameter propagation into Layer and Container. This resolves some missing render operations in complex situations.DrawingContext now takes screen coordinates, and sets GL coordinates in the WebGLGlobalWrapper.Filters#focusFilters setting camera resolution too late, leading to unexpected results on the first frame.FillCamera node being misaligned/missing in cameras rendering to framebuffers.Shape not respecting lights even though it had the lighting component.Container. Thanks to @saintflow47, @tickle-monster and @leemanhopeter for reporting this.Container now updates the blend mode it passes to children more accurately, preventing blend modes from leaking from one child into another child's filters. Thanks @leemanhopeter!Blend filter parameter texture now correctly documented as string.ColorMatrix filter correctly blends input alpha.Filters now correctly handles non-central object origins when the object is flipped. Thanks @ChrisCPI!Glow filter acts consistently when knockout is active.Mask filter now correctly resizes and clears when the game resizes to an odd width or height, fixing a bug where masks might overdraw themselves over time. Thanks @leemanhopeter!ParallelFilters filter memory leak eliminated (this would occur when both passes had active filters).Blocky filter now has a minimum size of 1, which prevents the object from disappearing.flipX/flipY in Filter#focusFilters.BatchHandlerQuad#run() parameter tintFill, which was set as a Number but should be used as a Boolean.Shader.CaptureFrame compatibility with Layer and Container.displayList passed to RenderWebGLSteps.Container/Layer objects are correctly added to the current camera's renderList. This fixes an issue with input on overlapping interactive objects.DynamicTexture method startCapture now handles nested parent transforms correctly. This is used in Mask, so masks within Container objects should behave correctly too.nearest and furthest, and static group refresh (thanks @samme)ArcadePhysics#closest() and #furthest() are properly defined (thanks @samme)PhysicsGroup.add and StaticPhysicsGroup.add will now check to see if the incoming child already has a body of the wrong type, and if so, will destroy it so the new correct type can be assigned.MatterTileBody scope issue that caused a crash when processing flipped tiles because Body.scale() was called with null (thanks @cyphercodes)TilemapGPULayer shader, introduced after switching to GL standard texture orientation.TilemapGPULayer now respects camera translation (thanks @aroman)TilemapGPULayer now takes the first tileset if it receives an array of tilesets, which is valid for Tilemaps but not for TilemapGPULayer (thanks @ChrisCPI)createFromTiles to handle multiple tilesets when using sprite sheets. Fix #7122 (thanks @vikerman)DynamicTexture errors when rendering Masks.RenderTexture from rendering while it's rendering, thus preventing infinite loops.DynamicTexture using a camera without the required methods.DynamicTexture#draw.DynamicTexture turning black if it initially has a power-of-two resolution and is resized to a non-power-of-two resolution. Now any WebGL texture resize will wrap with REPEAT if it is power of two, or CLAMP_TO_EDGE if not. Thanks to @x-wk for reporting this.Grid using old methods. It was supposed to use 'stroke' just like other Shape objects, not a unique 'outline'.Grid shape now sets stroke correctly from optional initialization parameters, at 1px wide. Use Grid#setStrokeStyle() to customize it further (thanks @Grimshad)DOMElement has no container.TileSprite applying smoothPixelArt game option incorrectly.BatchHandler (thanks @mikuso)WebGLProgramWrapper now correctly recognizes uniforms with a value of undefined and can recognize if they have not changed and do not need updates.TextureSource.resolution being ignored in WebGL.
TextureSource#setFlipY to affect all textures (except compressed textures, which have fixed orientation).addBase64().ParseXMLBitmapFont (thanks @leemanhopeter)TextureManager.addUint8Array method, which got premultiplied alpha wrong and flipY wrong.Textures.Parsers.AtlasXML passing trimmed and untrimmed dimensions in the wrong order to setTrim(), causing frame.realWidth to return the trimmed size instead of the original size. This made setOrigin() compute incorrect pivots for any non-default origin. Fix #7245 (thanks @cmnemoi)Texture#getFrameBounds no longer includes the last frame (__BASE), as it caused an incorrect calculation of the bounds (always {x:0, y:0, w: textureWidth, h: textureHight}) (thanks @jjcapellan)SpriteGPULayer segment handling (segments changed from 32 to 24 to avoid problems with 32-bit number processing)SpriteGPULayer member animations using Gravity.SpriteGPULayer data encoding.SpriteGPULayer failing to generate frame animations from config objects.SpriteGPULayer#getMember(), which previously multiplied the index by 4.SpriteGPULayer creation time handling getting confused by 0.GamepadPlugin.stopListeners and GamepadPlugin.disconnectAll now have guards around them so they won't try to invoke functions on potentially undefined gamepads (thanks @cryonautlex)GetURL function did not treat file:// URLs as absolute. When a baseURL is set, it gets prepended to an already-absolute path, producing double-prefixed URLs (thanks @aomsir)TweenBuilder when the targets array contains null or undefined elements (thanks @aomsir)Timeline events with once set to true would silently break the timeline and prevent all future events from firing. Fix #7147 (thanks @TomorrowToday)TimeStep#stepLimitFPS to drop fewer frames, running much more smoothly at the target frame rate. Thanks to @Flow and @Antriel for discussing the topic.
FPSConfig#limit now clarifies that frame limits are only necessary when artificially slowing the game below the display refresh rate.TransformMatrix.setQuad documentation.ColorMatrix.desaturate is no longer documented as saturation.@return tag to FilterList#addBlend (thanks @phasereditor2d!).{ internal, external } structure of Camera#filters (and GameObject#filters).FilterList#addMask docs.Scenes.Systems#destroy not removing the cameras plugin correctly.Fixes to TypeScript documentation: thanks to SBCGames and mikuso for contributions!
Add documentation for writing a Extern#render function.
Phaser v4 would not be possible without the community. Thank you to everyone who reported bugs, submitted fixes, contributed to the documentation and TypeScript definitions, and helped with beta testing:
@amirking59, @Antriel, @aomsir, @aroman, @bagyoni, @bvanderdrift, @captain-something, @chavaenc, @ChrisCPI, @cryonautlex, @DayKev, @Flow, @Grimshad, @ixonstater, @justin-calleja, @leemanhopeter, @Legend-Master, @mikuso, @ospira, @OuttaBounds, @phasereditor2d, @raaaahman, @saintflow47, @samme, @SBCGames, @skhoroshavin, @TadejZupancic, @tickle-monster, @TomorrowToday, @Urantij, @vikerman, @x-wk
And special thanks to kimdanielarthur-cowlabs for filter improvements.