Phaser 3.2.0(Kaori)版本深度解析:Render Texture、Headless 渲染模式与场景管理新能力
导读
本文以 Phaser 3 官方变更日志 CHANGELOG-v3.2.md 为骨架,深入剖析 3.2.0 "Kaori"(2018 年 3 月 5 日发布)带来的核心能力:全新的 Render Texture 游戏对象、HEADLESS 无渲染模式、Game.resize 响应式调整、二次贝塞尔插值与曲线、以及场景管理与输入系统的多项增强。读完本文,你将理解这些功能的设计动机、底层实现原理与典型应用场景,并能在你自己的 Phaser 项目中正确使用它们。
一、新特性总览:3.2.0 给开发者带来了什么
3.2.0 版本的功能点可以归纳为五大方向:
| 方向 | 代表特性 |
|---|---|
| 渲染与纹理 | 新的 Render Texture 游戏对象、roundPixels 配置、HEADLESS 渲染模式 |
| 尺寸与缩放 | Game.resize、InputManager.resize、场景级 resize 事件 |
| 场景管理 | SceneManager.remove、moveAbove、moveBelow、升级版 swapPosition |
| 数学与曲线 | 二次贝塞尔插值函数、QuadraticBezierCurve 曲线类、Path.quadraticBezierTo |
| 输入与加载 | setInteractive 的 dropZone 参数、Sprite 拖放目标、multiatlas 新格式、Load.plugin 类参数 |
下面逐一展开,并结合当前仓库源码验证其实现。
二、Render Texture:可绘制的动态纹理游戏对象
2.1 它是什么
3.2.0 首次引入了 Render Texture 游戏对象。你可以对它执行 clear(清空)、fill(填充)和绘制纹理帧等操作,同时它本身也是一个可显示的游戏对象,拥有独立的变换(位置、旋转、缩放),还可以作为另一个游戏对象的 Bitmap Mask 使用。
在当前仓库源码中,Render Texture 被定义为"Dynamic Texture 与 Image 游戏对象的组合",内部基于 DynamicTexture 展示自身(参见 RenderTexture.js 的类注释)。其构造参数如下(@since 3.2.0):
new Phaser.GameObjects.RenderTexture(scene, x, y, width, height, forceEven)
x/y:游戏对象在世界中的位置,默认0;width/height:渲染纹理的尺寸,默认32;forceEven:默认true,强制将宽高四舍五入为偶数,能显著改善渲染质量;若你确需奇数尺寸纹理,可设为false。
创建时它会通过 scene.sys.textures.addDynamicTexture(UUID(), width, height, forceEven) 生成一张唯一命名的动态纹理,再以 Image 的方式显示(RenderTexture.js)。
2.2 典型用法:把复杂对象烘焙成一张纹理
Render Texture 的核心价值在于"批量烘焙":将多个复杂对象一次性绘制到一张纹理上,再让其他 Sprite 等游戏对象复用这张纹理。之后只需更新这张纹理,所有使用它的游戏对象都会立即同步变化,且对 WebGL 友好,避免了每次变更都进行昂贵的 GPU 上传。
// 在 Scene 的 create 中
const rt = this.add.renderTexture(400, 300, 256, 256);
// 清空与填充
rt.clear();
rt.fill(0x000000, 0.5);
// 将另一个游戏对象绘制到纹理上
rt.draw(sprite, 100, 100);
2.3 使用建议与注意事项
根据源码注释(RenderTexture.js),官方给出了明确的选型建议:
- 纹理需要被多个游戏对象复用、或需要跨 Scene 使用时,直接用 Dynamic Texture(纹理是全局存储的);
- 纹理不会在游戏中显示,仅用作掩码或 Shader 输入时,也用 Dynamic Texture;
- 只需要在游戏中以单个游戏对象展示时,才用 Render Texture,它帮你把 Image 与 Dynamic Texture 打包在了一起。
两点技术限制(从源码注释可确认):WebGL1 下 FrameBuffer 无法抗锯齿,因此直接绘制 Shape 或 Graphics 时边缘可能出现锯齿;动态纹理默认不自动生成 mipmap(每次重建代价超过 10 微秒),如需 mipmap 必须额外开启渲染配置 mipmapRegeneration。
三、HEADLESS 渲染模式:不渲染画布的运行方式
3.2.0 实现了无头渲染模式(修复了 issue #3256)。在游戏配置中将 renderType 设为 HEADLESS 后,游戏主循环会运行一个跳过渲染的特殊 step。
3.1 底层实现
在 Game.js 中,start 会根据是否有 renderer 决定主循环回调:有渲染器走 this.step,否则走 this.headlessStep。headlessStep(@since 3.2.0,见 Game.js)与正常 step 一样:
- 发出
PRE_STEP/STEP/POST_STEP事件; - 照常更新
SceneManager与所有活动场景(this.scene.update(time, delta)); - 发出
PRE_RENDER与POST_RENDER事件——尽管没有任何内容真正显示。
也就是说,逻辑更新与事件系统完全保留,唯独渲染被跳过。
同时,CreateRenderer.js 中当 renderType === CONST.HEADLESS 时直接提前返回,不再实例化 WebGL/Canvas 渲染器。但文档特别注明:它仍然会创建一个 Canvas 元素,因为输入等大量内部系统依赖它,只是不会向画布绘制任何内容。
3.2 配置方式
const config = {
type: Phaser.HEADLESS,
width: 800,
height: 600,
scene: { preload, create, update }
};
const game = new Phaser.Game(config);
使用 HEADLESS 时,CreateRenderer 不会再根据设备能力自动判定渲染器(该分支被跳过),这也是其名称"无头"的含义——不产出任何画面,适合服务端模拟、自动化测试、逻辑先行等场景。
四、Game.resize 与全局 resize 事件链
3.2.0 让"动态调整游戏尺寸"变得统一且简单:
Game.resize(width, height):一次调用同时调整游戏配置、渲染器与输入系统;- 调用后会触发所有
Scene.Systems的resize方法,每个场景随即发出resize事件,回调收到两个参数:新的 canvas 宽度与高度; InputManager.resize:同步更新输入边界(bounds)定义与输入缩放因子,保证调整尺寸后鼠标/触摸坐标依然准确。
典型响应式代码
// 在 Scene 内监听
this.events.on('resize', (width, height) => {
// 依据新尺寸重新排版 UI 或更新相机
this.cameras.main.setSize(width, height);
});
这套事件链是构建"窗口变化即自适应"的 H5 游戏的关键基础设施:开发者只需在 resize 回调中重排布局,输入系统与渲染器的同步问题由框架内部处理。
五、roundPixels:杜绝子像素插值
Game.Config.roundPixels 属性在 3.2.0 加入(参见 Config.js 的注释与实现,默认 false):开启后,带纹理的游戏对象在 WebGL 与 Canvas 渲染时只会落在整数像素坐标上,从而避免子像素插值造成的模糊抖动。
const config = {
type: Phaser.AUTO,
roundPixels: true, // 纹理游戏对象按整数像素绘制
// ...
};
注意:没有纹理的游戏对象(如 Graphics)会忽略该属性;另外从源码看,配置 pixelArt 为 true 时会自动把 roundPixels 置为 true(Config.js),两者是配套关系。对于像素风游戏或追求画面锐利的场景,这是一个低成本高收益的开关。
六、输入系统增强:dropZone 与拖放目标
6.1 setInteractive 新增 dropZone 参数
GameObject.setInteractive 新增了布尔参数 dropZone,可在设置交互对象的同时将其标记为"放置区",不再需要额外步骤:
// 将该对象设为交互对象,并标记为 drop zone
sprite.setInteractive({ dropZone: true });
// 或直接传布尔参数
zoneSprite.setInteractive(true);
6.2 Sprite 可以作为拖放目标
3.2.0 起,Sprite 本身可以成为 drop zone,让其他游戏对象拖拽到它上面作为目标。与拖放相关的两个关键修复(详见下方"Bug 修复"部分)保证了 topOnly 开启时 drop zone 依然能正常工作——drop zone 在输入插件中被单独处理,不会被提前过滤出显示列表。
七、场景管理新方法:remove / moveAbove / moveBelow / swapPosition
3.2.0 为场景管理补齐了一批实用方法(均在 SceneManager.js 中实现,且同步暴露在 ScenePlugin.js 中供场景内调用):
| 方法 | 说明 |
|---|---|
SceneManager.remove(key) |
移除并销毁一个场景,释放场景 key 供后续场景复用,并可能将其从活跃内存中清出(SceneManager.js) |
SceneManager.moveAbove(keyA, keyB) |
将场景 A 移到场景 B 的正上方(SceneManager.js) |
SceneManager.moveBelow(keyA, keyB) |
将场景 A 移到场景 B 的正下方(SceneManager.js) |
SceneManager.swapPosition(keyA, keyB) |
交换任意两个场景的位置(SceneManager.js) |
swapPosition 在 3.2.0 中被升级:此前只能交换"调用者场景"与另一个场景的位置,现在新增可选参数 keyB,可交换任意两个场景。例如:
// 在场景内:把 'UI' 场景移动到 'Game' 场景之上
this.scene.moveAbove('UI', 'Game');
// 交换任意两个场景的显示层级
this.scene.swapPosition('Game', 'Menu');
// 移除并销毁一个场景
this.scene.remove('Level1');
场景层级(z-order)直接影响渲染顺序与输入穿透行为,这四个方法让运行时动态调整场景关系变得直观可控。
八、数学与曲线:二次贝塞尔插值与曲线类
3.2.0 将二次贝塞尔(Quadratic Bezier)能力引入数学与曲线模块。
8.1 Math.Interpolation.QuadraticBezier
新增的插值函数位于 QuadraticBezierInterpolation.js,@since 3.2.0。它由起点、一个控制点、终点三个值定义:
var value = Phaser.Math.Interpolation.QuadraticBezier(t, p0, p1, p2);
t:插值位置,0返回起点值,1返回终点值,中间值被平滑插值;p0:起点值;p1:控制点值,将曲线拉向自己、决定弧线的形状,曲线并不直接穿过它;p2:终点值。
其实现本质是三项伯恩斯坦基函数的加权和:
P0(t) = (1 - t)² · p0
P1(t) = 2(1 - t)t · p1
P2(t) = t² · p2
源码注释表明该实现参考了 three.js 的 Interpolations,是标准的二次贝塞尔公式。
8.2 QuadraticBezierCurve 曲线类
配合新增的 QuadraticBezierCurve.js(@since 3.2.0),你可以在场景中直接创建由起点、控制点、终点定义的二阶贝塞尔曲线:
const curve = new Phaser.Curves.QuadraticBezier(
new Phaser.Math.Vector2(100, 500), // p0 起点
new Phaser.Math.Vector2(400, 100), // p1 控制点
new Phaser.Math.Vector2(700, 500) // p2 终点
);
// 或用点对数组构造(多段曲线)
const curve2 = new Phaser.Curves.QuadraticBezier([
100, 500, 400, 100, 700, 500
]);
该曲线类内部正是复用了 QuadraticBezierInterpolation 计算路径点(QuadraticBezierCurve.js),两条能力互为表里。
8.3 Path.quadraticBezierTo
Path.quadraticBezierTo 允许你在现有 Path 中追加一段二次贝塞尔曲线,用于构建更复杂的运动轨迹或矢量路径。
九、加载器增强:multiatlas 新格式与 plugin 类参数
9.1 multiatlas 支持 Texture Packer 新版 JSON
Loader.multiatlas 现在支持 Texture Packer 新的 JSON 图集导出格式——该格式将所有图片文件合并为一份组合图集。使用 Texture Packer 中的 "Phaser 3 Export" 即可产出这种格式。源码层面,multiatlas 通过 MultiAtlasFile.js 加载,一个 key 对应一份 JSON 数据文件,并按需解析出内部纹理:
// 加载多图集:一个 key + 一份 JSON
this.load.multiatlas('level1', 'images/Level1.json');
// 或使用完整配置对象
this.load.multiatlas({
key: 'level1',
url: 'images/Level1.json'
});
(完整参数签名可参考 MultiAtlasFile.js 中 LoaderPlugin#multiatlas 的文档注释。)这对使用 Texture Packer 工作流的团队是直接的效率提升:导出一次即可覆盖所有散图。
9.2 Load.plugin 接受类作为参数
LoaderPlugin.plugin 现在既接受 URL 字符串,也直接接受一个类作为参数,免去了先写插件文件再加载的麻烦,让场景内注入自定义插件更加顺手。
十、Tween.complete 与 WebGLPipeline 扩展
10.1 Tween.complete
Tween.complete 允许你将一个补间标记为"已完成",无论它当前处于哪个阶段。若定义了 onComplete 回调,它会被调用;还可以传入可选的延迟时间:
tween.complete(250); // 250ms 后再触发完成逻辑
这为"提前终结动画并正确触发完成回调"提供了官方通道,无需手动 hack 补间状态。
10.2 WebGLPipeline 更易扩展
3.2.0 改进了 WebGLPipeline,使其更易于继承扩展、更便于创建自定义渲染管线(custom rendering passes)。这是后续版本大量自定义着色器与管线功能的基础性铺垫。
十一、重点 Bug 修复:读懂修复背后的机制
3.2.0 修复了一批影响面较大的问题,理解它们有助于规避同类坑:
- Arcade Physics 的
allowRotation:此前没有应用到父游戏对象上,现在旋转设置能正确传导; - 缩放后的物理体位置计算:游戏对象缩放时,Arcade Physics body 仍按原始尺寸计算位置(感谢 @pixelpicosean 报告),已修复为基于缩放后的尺寸;
- drop zone 与
topOnly冲突:topOnly的实现曾把 drop zone 从显示列表中过滤掉,导致拖放失效;现在 drop zone 在 Input Plugin 中被独立处理,即使开启topOnly也能正常放置(间接修复 #3291); InputManager.updateBounds偏移:canvas 在页面中有水平或垂直位移时,边界获取不正确导致缩放因子偏离、输入坐标误触发,已修正;- HTML5 音频解锁:此前只有在第一个场景加载过音频才会在触摸事件时解锁音频 API;现在仅当缓存中存在音频文件时才解锁(修复 #3311);
- Text.lineSpacing 未生效:渲染时未计入行间距,已修复(#3215);
- 场景启动队列:SceneManager 采用新的队列,对所有待启动场景按严格顺序创建与 boot,避免场景在 create 中引用 boot 列表更靠后的场景时报错(#3314)。
十二、行为更新与体验优化
AnimationComponent.play现在会调用setSizeToFrame()与updateDisplayOrigin(),避免新动画帧尺寸与旧帧不同导致显示错位;Text.setText/BitmapText.setText:对假值(非 0)自动置为空字符串;BitmapText 还会先转成字符串、且仅在文本真正变化时才更新;transparent配置:设置了transparent但未提供backgroundColor时,之前会渲染成黑色,现在能正确透明;若提供颜色值则必须包含 alpha 分量(参见 Config.js 的实现);- Arcade Physics 支持普通 Group:
collide/overlap现在可传入普通 Group 与 Physics Group(修复 #3277); Texture.get优化:先判空再报错,允许在动画配置中省略动画帧而不产生控制台警告;setFrame自动同步尺寸与原点:Texture 组件的setFrame会自动将游戏对象的宽高重置为新帧的尺寸,并按帧的尺寸调整显示原点;若帧定义了自定义 pivot,则原点与其对齐;- 键盘管理器:持续按住按键不再反复触发
keydown(修复 #3239); - 场景渲染条件:SceneManager 只为"可见且处于 running 或 paused"的场景渲染,跳过
init状态的场景; Game.preBoot/postBoot回调:现在会将 game 实例作为参数传给回调;Graphics.arc(WebGL):行为更接近 Canvas 下的arc;- 游戏对象销毁事件:GameObjects 被销毁时会发出
destroy事件,便于做额外的清理处理(修复 #3251); - RandomDataGenerator 随机性改进:正确缓存类属性提升随机质量(#3289),并修复了
sign属性的方法名冲突(#3323)。
十三、结语
Phaser 3.2.0 "Kaori" 是 3.x 早期一个含金量很高的里程碑:Render Texture 奠定了运行时动态烘焙纹理的基础,HEADLESS 模式为无渲染环境提供了官方路径,Game.resize 事件链统一了响应式调整,二次贝塞尔插值补全了数学工具集,场景管理的 remove/moveAbove/moveBelow/swapPosition 则让运行时场景编排更加灵活。这些能力中的大多数至今仍以相同或演进后的形态存在于 Phaser 3 后续版本中(例如 3.60 将 Render Texture 的核心能力拆分至 Dynamic Texture),理解它们的历史背景与源码实现,能帮助你更准确地选型与排错。若要深入源码,可继续阅读 RenderTexture.js、Game.js、CreateRenderer.js、SceneManager.js 与 QuadraticBezierInterpolation.js 等文件。