Phaser 4.0 Beta 7 相机矩阵系统重写与渲染管线变更深度解析
2026/9/19 5:51:55 网站建设 项目流程

Phaser 4.0 Beta 7 相机矩阵系统重写与渲染管线变更深度解析

【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser

导读

本文围绕 Phaser 4.0 beta 7 的核心变更展开:相机系统矩阵计算被整体重写,从单矩阵模型演进为"视图矩阵 + 外部矩阵"双矩阵模型,同时引入matrixExternalmatrixCombinedcopyWithScrollFactorFrom等新 API。文章将结合仓库源码(Camera.js、GetCalcMatrix.js、TransformMatrix.js)与单元测试(GetCalcMatrix.test.js)逐项拆解这次重写的设计动机、实现细节与迁移影响,并同步梳理SpriteGPULayer批量渲染增强、roundPixels默认值调整等一系列修复与优化,帮助你准确评估升级风险并适配新 API。


一、为什么重写相机矩阵系统

Phaser 3 时代,相机将位置(position)、旋转(rotation)与缩放(zoom)统一合并进Camera#matrix一个矩阵中,滚动偏移(scroll)则在渲染流程的后续阶段被"追加"进去。这种单矩阵方案在常规场景下工作良好,但一旦涉及嵌套变换(nested transforms)、滤镜(filters)以及其他依赖变换矩阵的内部系统,就会出现难以排查的错位问题——因为"相机在哪"和"相机看到什么"被耦合在同一个矩阵里,无法干净地分离。

beta 7 的核心思路是把这两种语义拆开:

  • 视图(View):相机的旋转、缩放与滚动 —— 决定"世界被如何观察";
  • 位置(Position):相机在屏幕上的平移 —— 决定"相机本身在哪里"。

从源码可以看到这一拆分的落点。在 Camera.js 的preRender流程中,matrix只承载视图变换(ITRS 顺序应用 origin、rotation、zoom 与滚动平移),而matrixExternal通过applyITRS(this.x, this.y, 0, 1, 1)单独记录相机位置,最后用matrixExternal.multiply(matrix, this.matrixCombined)合成出组合矩阵:

// src/cameras/2d/Camera.js(节选) matrix.applyITRS(originX, originY, this.rotation, zoomX, zoomY); matrix.translate(-sx - originX, -sy - originY); matrixExternal.applyITRS(this.x, this.y, 0, 1, 1); this.shakeEffect.preRender(); matrixExternal.multiply(matrix, this.matrixCombined);

这个重写不改变开发者设置相机属性的方式setScrollsetZoomsetRotation等用法不变),它主要影响内部渲染系统。只有当你直接读取相机矩阵时,才会感知到差异。

二、双矩阵模型:matrix / matrixExternal / matrixCombined

beta 7 之后,相机持有三份矩阵,语义截然不同:

矩阵属性包含内容典型用途
Camera#matrix旋转 + 缩放 + 滚动(不含位置)渲染到 framebuffer / 滤镜合成场景下的视图矩阵
Camera#matrixExternal相机位置(新增)将相机"摆放"到屏幕上的外部变换
Camera#matrixCombinedmatrix × matrixExternal(两者相乘)常规渲染时使用的最终视图矩阵

选择逻辑集中体现在新增的Camera#getViewMatrix(forceComposite)方法(Camera.js):

  • 当相机需要渲染到 framebuffer,或挂载了内部/外部滤镜(filters.external.length > 0 || filters.internal.length > 0),或forceCompositetrue时,返回不包含位置matrix
  • 否则返回组合矩阵matrixCombined
getViewMatrix: function (forceComposite) { if ( forceComposite || this.forceComposite || this.filters.external.length > 0 || this.filters.internal.length > 0 ) { return this.matrix; } else { return this.matrixCombined; } }

之所以在"渲染到 framebuffer"场景下要剔除位置,是因为 framebuffer 是一个离屏目标,相机位置此时属于"屏幕空间"的概念,不应被带入纹理坐标空间。这正是ignoreCameraPosition参数的用途(详见下一节)。

三、GetCalcMatrix 的变化与 GetCalcMatrixResults 新成员

GetCalcMatrix(src, camera, parentMatrix, ignoreCameraPosition)是 Phaser 渲染管线中计算游戏对象最终变换矩阵的核心入口,几乎所有 WebGL/Canvas 渲染器都会调用它。beta 7 给它增加了一个新参数:

  • ignoreCameraPosition(布尔值,默认false):为true时,返回结果中的"外部矩阵"使用单位矩阵而不是相机的matrixExternal,从而让对象变换完全脱离相机位置影响。这个标志在相机渲染到 framebuffer(滤镜、RenderTexture 内部)时非常关键。

对应实现见 GetCalcMatrix.js:

var GetCalcMatrix = function (src, camera, parentMatrix, ignoreCameraPosition) { if (ignoreCameraPosition) { camExternalMatrix.loadIdentity(); } else { camExternalMatrix.copyFrom(camera.matrixExternal); } camMatrix.copyWithScrollFactorFrom( ignoreCameraPosition ? camera.matrix : camera.matrixCombined, camera.scrollX, camera.scrollY, src.scrollFactorX, src.scrollFactorY ); calcMatrix.copyFrom(camMatrix); if (parentMatrix) { calcMatrix.multiply(parentMatrix); } spriteMatrix.applyITRS(src.x, src.y, src.rotation, src.scaleX, src.scaleY); calcMatrix.multiply(spriteMatrix); return result; };

返回值result在 GetCalcMatrixResults.js 中定义,beta 7 新增了cameraExternal属性:

结果属性类型含义
cameraExternalTransformMatrix相机外部矩阵(相机在屏幕上的位置);ignoreCameraPosition时为单位矩阵
cameraTransformMatrix相机视图矩阵,已按对象scrollFactor修正滚动偏移
spriteTransformMatrix游戏对象自身的世界变换矩阵
calcTransformMatrix最终合成矩阵(相机 × 父级 × 对象)

配套的单元测试(GetCalcMatrix.test.js)精确验证了这一行为:

  • ignoreCameraPosition = true时,cameraExternala/b/c/d/tx/ty全部回到单位矩阵值(a=1, d=1, tx=0, ty=0);
  • ignoreCameraPosition = false时,cameraExternal完整复制相机的matrixExternal(测试中验证了缩放4与平移(10, 20))。

同一测试还覆盖了滚动偏移与scrollFactor的组合([L204-L228]):scrollFactor = 1时滚动不产生位移,scrollFactor = 0时完整应用滚动偏移。此外GetCalcMatrix返回的是同一个结果对象引用(每次调用复用内部矩阵实例,见 [L230-L239]),这意味着拿到结果后必须立即使用或自行复制,否则下一次渲染会覆盖这些值——这是从 Phaser 3.50 起就一直存在的约定。

四、新方法 copyWithScrollFactorFrom:替代手工滚动运算

在 Phaser 3 时代,渲染代码里经常出现这类手工修正:

spriteMatrix.e -= camera.scrollX * src.scrollFactorX; spriteMatrix.f -= camera.scrollY * src.scrollFactorY;

beta 7 提供了标准替代方案TransformMatrix#copyWithScrollFactorFrom(src, scrollX, scrollY, scrollFactorX, scrollFactorY)(TransformMatrix.js):

copyWithScrollFactorFrom: function (src, scrollX, scrollY, scrollFactorX, scrollFactorY) { var matrix = this.matrix; matrix[0] = src.a; matrix[1] = src.b; matrix[2] = src.c; matrix[3] = src.d; var sx = scrollX * (1.0 - scrollFactorX); var sy = scrollY * (1.0 - scrollFactorY); matrix[4] = src.a * sx + src.c * sy + src.e; matrix[5] = src.b * sx + src.d * sy + src.f; return this; }

它从源矩阵复制旋转/缩放部分(a、b、c、d),并把滚动偏移按(1 - scrollFactor)的比例折算进平移分量(e、f)。注意一个易混淆点:scrollFactor越大,滚动对矩阵的影响越小——scrollFactor = 1表示对象跟随世界(不受相机滚动影响),scrollFactor = 0表示对象完全锁定在屏幕上(如 HUD)。上面的公式中(1.0 - scrollFactor)恰好体现了这一语义。

TransformMatrix.test.js 对该方法做了系统验证:滚动为(0, 0)时结果与源矩阵一致;scrollFactor = 1时不产生滚动位移;scrollFactor = 0时完整应用(100, 200)的滚动偏移;scrollFactor = 0.5时平移分量取半。

对内部系统的实际影响

  • CanvasRenderer(src/renderer/canvas/CanvasRenderer.js)与TilemapLayerCanvasRenderer(src/tilemaps/TilemapLayerCanvasRenderer.js)等渲染器均改为消费新的矩阵体系;
  • GetCalcMatrix调用方ignoreCameraPositiontrue时改用camera.matrix而非camera.matrixCombined,确保 framebuffer 渲染不再被相机屏幕位置污染;
  • 新系统修复了大量嵌套变换、滤镜与变换矩阵组合使用时的历史问题(changelog 原文:"fixes many issues with nested transforms, filters, and other uses of transforms")。

五、SpriteGPULayer 批量渲染增强

SpriteGPULayer是 Phaser 4 引入的 GPU 批量精灵层,beta 7 为其补齐了一批面向性能调优的 API 与文档:

5.1 新增成员操作方法

  • SpriteGPULayer#insertMembers(index, members)(SpriteGPULayer.js):在指定索引处批量插入成员;
  • SpriteGPULayer#insertMembersData(index, data)(SpriteGPULayer.js):直接以底层数据块形式插入成员,用于零拷贝的高效写入路径;
  • SpriteGPULayer#getDataByteSize()(SpriteGPULayer.js):返回单个成员的字节大小,内部在分配成员缓冲区(this.nextMember = new ArrayBuffer(this.getDataByteSize()))时使用,外部在按字节写入成员数据时同样需要它来推算偏移。

5.2 非循环动画与动态粒子

beta 7 允许SpriteGPULayer成员动画设置loop: false,一次性播放后即终止。这一能力特别适合一次性粒子特效与动态来源(如程序生成的临时成员)——不需要为了单次播放创建并回收整个层。

从源码看,循环标志被编码进 delay 的符号位(SpriteGPULayer.js):value.loop !== undefined ? value.loop : true为默认值,if (!loop)时以负 delay 写入,解码端通过var loop = delay > 0还原。同时,Gravity 模式动画现在支持负加速度(即向上的"喷发"效果),该模式使用SpriteGPULayer#gravity属性,默认值为1024像素/秒²(SpriteGPULayer.js)。

5.3 分段(segment)处理修正

一个值得注意的底层修复:SpriteGPULayer的段数从 32 改为 24。原因是 32 段会与32 位数字处理发生冲突(segment 编码与位移位运算在 32 位整数边界上产生错误),24 段避开了这一陷阱。若你的代码直接操作 segment 索引或依赖原始编码布局,请注意:

  • 段数上限已改为 24(this._segments = 24,见 SpriteGPULayer.js);
  • 成员数据的位编码被重新排列(changelog:"Rearrange SpriteGPULayer data encoding"),因此自定义 shader 或直接读写成员数据的代码需要同步更新
  • 修复了从配置对象(config object)生成帧动画失败的问题。

六、其他小改动与新增能力

  • Extern#render 文档:新增了编写Extern#render函数的说明文档(Extern是外部渲染对象,用于把自定义渲染逻辑挂进 Phaser 渲染管线)。
  • Tilemap 父矩阵支持TilemapLayerTilemapGPULayer渲染时支持父矩阵(parent matrix),可以正确嵌套在Container等带变换的父级之下。
  • Shape 默认filtersFocusContext = true(Shape.js):防止滤镜上下文聚焦时把描边(stroke)裁剪到边缘之外。若你在Shape上叠加滤镜,将不再需要手动设置该标志。

七、修复与行为调整清单

7.1roundPixels默认改为false

这是 beta 7 中最可能影响现有项目观感的行为变化。在 Config.js 中:

  • roundPixels的默认值由true调整为falseGetValue(renderConfig, 'roundPixels', false, config));
  • 开启pixelArt时仍会自动把roundPixels置为true("When enabled, this also setsantialiasandantialiasGLtofalseandroundPixelstotrue")。

官方注释给出了理由:该选项极易产生"messy results"(脏乱像素边缘),因此默认关闭;但它仍保留给确实需要整数像素对齐的场景。迁移建议:

  • 追求像素风、需要纹理对齐像素网格的项目:显式配置roundPixels: true或直接开启pixelArt: true
  • 其他项目保持默认即可,无需改动。

7.2 DOMElement 无容器时报错

DOMElement依赖一个 DOM 容器来挂载元素。beta 7 起,若游戏配置中没有设置 DOM 容器,构造时会直接抛出明确错误:

throw new Error('No DOM Container set in game config');

(见 DOMElement.js。)这要求使用DOMElement前必须在 Game 配置中提供dom.createContainer(或等价配置),错误信息比静默失败更容易定位问题。

7.3 TextureSource#setFlipY 全局生效

TextureSource#setFlipY(value)(TextureSource.js)现在能影响所有纹理——除了压缩纹理(compressed textures)——因为压缩纹理的方向是固定的。该方法控制 WebGL 纹理上传时的UNPACK_FLIP_Y_WEBGL标志;值为undefined时默认为true,且会触发this.update()刷新纹理。

7.4 WebGLProgramWrapper 对 undefined uniform 的识别

WebGLProgramWrapper现在能正确识别值为undefined的 uniform,并判断其是否真的发生了变化:如果 uniform 仍是undefined且没有更新,则跳过无意义的 GPU uniform 上传,减少不必要的状态更新开销。

7.5 TileSprite 与 smoothPixelArt

修复了TileSprite错误应用smoothPixelArt游戏选项的问题——此前该选项在TileSprite上可能被错误地套用,导致平铺精灵纹理出现意外的线性/最近邻过滤行为。

7.6 BatchHandler 事件引用

修复BatchHandler中缺少对 Renderer 事件(Renderer events)的引用问题(感谢 @mikuso 的反馈),避免在特定事件分发路径上出现引用错误。

八、升级与迁移清单

针对 beta 7 的变更,升级时建议逐项核对:

  1. 不要直接读取/改写Camera#matrix来叠加滚动:改用camera.matrixExternalcamera.matrixCombined或通过GetCalcMatrix的结果消费矩阵;需要带滚动因子的副本时使用copyWithScrollFactorFrom
  2. 自定义渲染器 / 自定义 shader:确认渲染管线传入的矩阵语义。渲染到 framebuffer 时传camera.matrix(不含位置),常规渲染时传camera.matrixCombinedGetCalcMatrixResults的新属性cameraExternal对应相机屏幕位置。
  3. SpriteGPULayer段数:若依赖 32 段布局,需调整为 24 段并适配新的位编码;一次性粒子特效请使用loop: false
  4. roundPixels:若此前依赖默认开启的像素对齐,需在配置中显式声明。
  5. DOMElement:确保 Game 配置中创建了 DOM 容器,否则直接抛错。
  6. Shape+ 滤镜:无需再手动开启filtersFocusContext,默认已开启以避免描边被裁剪。

结语

Phaser 4.0 beta 7 的相机矩阵重写是一次"向内"的架构级变更:对外 API 保持稳定,对内则将视图与位置彻底解耦,并通过GetCalcMatrix(ignoreCameraPosition)为 framebuffer 渲染提供了一条干净路径,从根上解决了嵌套变换与滤镜场景下的矩阵污染问题。配合SpriteGPULayer的批量能力扩展与一系列渲染修复,这一版本为 Phaser 4 的正式发布奠定了更稳固的变换与渲染基础。后续版本中这些 API 已持续演进,建议以仓库内 changelog/v4/4.0-rc 目录下的相邻版本记录(如 beta-8、rc.1~rc.7)对照阅读,掌握完整演进脉络。

【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询