Phaser 3.60 核心机制详解:Game、Config 与 Device 系统的新特性、更新与修复
【免费下载链接】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 3.60.0 版本中Game、Game Config(游戏配置)与Device(设备检测)三大核心系统。它们构成了每一个 Phaser 应用的地基:Game实例负责启动、全局系统装配与主循环调度,Config负责将传入的配置对象解析为最终生效的参数,而Device则负责探测当前浏览器环境的能力与类型。读完本文,你将掌握 3.60 新增的全局暂停/恢复 API、稳定排序开关stableSort、独立事件总线的全局数据仓库registry,以及本版本对渲染器选择与浏览器检测策略的重要行为变更。
原文记录于 changelog/v3/3.60/Game.md,完整版本更新列表可查阅 changelog/v3/3.60/CHANGELOG-v3.60.md。
一、Game、Config 与 Device 新特性(New Features)
1.1GameConfig.stableSort与Device.features.stableSort:可控的深度排序策略
Phaser 内部大量场景依赖"深度排序"(depth sorting),例如 DisplayList、Container、LightsManager、ParticleEmitter 与 structs/List。排序的稳定性(stable)意味着:当多个元素排序键相等时,它们保持原有的相对顺序——这对渲染次序、粒子生命顺序至关重要。
3.60 新增了GameConfig.stableSort配置项(默认值-1),用数值控制内部深度排序走哪条代码路径:
| 值 | 行为 |
|---|---|
0(或false) | 使用 Phaser 自带的稳定排序函数(传统版本,适用于旧浏览器) |
1(或true) | 直接依赖浏览器原生 ES2019 稳定版Array.sort(仅现代浏览器) |
-1 | 自动检测:由 Phaser 根据浏览器类型与版本判断哪种方式最佳 |
该配置在 src/core/Config.js 中被解析,并将结果同步到Device.features.stableSort:
this.stableSort = GetValue(config, 'stableSort', -1); if (this.stableSort === -1) { this.stableSort = (Device.browser.es2019) ? 1 : 0; } Device.features.stableSort = this.stableSort;Device.features.stableSort的默认声明位于 src/device/Features.js,其含义是"当前浏览器的Array.sort实现是否稳定"。
Phaser 自带的稳定排序函数Phaser.Utils.Array.StableSort(src/utils/array/StableSort.js)是**无递归的归并排序(merge sort)**实现,基于 Two-Screen/stable 0.1.8。它的执行逻辑非常清晰:先判断Device.features.stableSort,为真则直接调用原生array.sort(compare)以充分利用引擎内置的高效排序;否则逐轮以 1、2、4、8……递增的 chunk 尺寸执行RunPass归并,并在奇数次归并后把结果拷贝回原数组。每个排序调用点都会优先走这一统一入口。
1.2Device.es2019:ES2019 原生能力探测
Device.es2019是一个新的布尔属性,用于通过"浏览器类型 + 版本"的基础检测,判断当前浏览器是否原生支持 ES2019 特性(典型代表就是稳定的数组排序)。
从 src/device/Browser.js 的实现可以看到各浏览器的判定规则:
- Edge(新版,UA 含
Edg/):直接判定支持; - Opera(UA 含
OPR):直接判定支持; - Chrome:
chromeVersion > 69时支持(ES2019 稳定排序自 Chrome 70 起原生可用); - Firefox:
firefoxVersion > 10时支持; - Mobile Safari(iOS):直接判定支持;
- 桌面 Safari:
safariVersion > 10时支持; - IE / Trident:不支持。
该属性会在stableSort为-1时被 Config 读取,从而决定内部排序走原生路径还是 Phaser 自研路径。值得注意的是,源码注释同时指出这种方式"并非百分百可靠,但偏向保守(errs on side of caution)",即在不明确时宁可回退到自带的稳定排序实现,也不冒险依赖原生排序的稳定性。
1.3Game.pause、Game.resume与Game.isPaused:一键冻结整个游戏
3.60 为Game实例新增了三个直接可用的全局控制能力(见 src/core/Game.js):
Game.pause():暂停整个游戏与所有 Phaser 系统。注意:它不会暂停正在进行的 Loader 请求;Game.resume():恢复整个游戏,使所有系统继续运行、重新开始渲染;Game.isPaused:布尔属性,标记游戏主循环当前是否处于暂停状态,也可以被直接赋值切换。
// 暂停整个游戏 game.pause(); // 恢复整个游戏 game.resume(); // 直接切换暂停状态 game.isPaused = true;这两个方法与"页面隐藏"(onHidden/onVisible)走的是不同路径。onHidden会真正调用this.loop.pause()停掉 TimeStep 主循环;而Game.pause只把isPaused置为true,主循环(TimeStep / rAF)仍然继续步进,只是在 Game.step 与 Game.headlessStep 的开头做短路返回,跳过整个更新与渲染流程。因此pause()的语义更接近"冻结帧":浏览器仍在收到帧回调,但 Phaser 不再更新任何系统。
源码中对isPaused的 JSDoc 也明确解释了这一点:
Is the Game currently paused? This will stop everything from updating, except the
TimeStepand related RequestAnimationFrame or setTimeout. Those will continue stepping, but the core Game step will be skipped.
同时,pause()与resume()会分别触发Phaser.Core.Events#PAUSE与Phaser.Core.Events#RESUME事件(只有状态发生实际变化时才触发,避免重复广播),并且只在"确实发生了状态切换"时发送事件,避免重复广播。开发者可以在game.events上监听这两个事件来响应全局冻结/解冻:
game.events.on('pause', () => { console.log('游戏已全局暂停'); }); game.events.on('resume', () => { console.log('游戏已恢复'); });二、Game、Config 与 Device 更新(Updates)
2.1 Opera 与 Edge 的浏览器 UA 检测更新
3.60 更新了 Device.Browser 中 Opera 与 Edge 的检测逻辑,采用这两个浏览器现代版本实际使用的 User-Agent 字符串:
- Edge 通过
/Edg\/\d+/匹配(现代 Edge 基于 Chromium,UA 中使用Edg/标记); - Opera 通过
/OPR/匹配。
该变更会破坏与非常老旧版本 Opera / Edge 的兼容性,但换来的是对现代版本的正确识别。从源码可见,这一改动同时影响了Device.es2019的判定:Edge 与 Opera 在命中后均直接置es2019 = true。
2.2Device.safariVersion现在会被正确填充
此前Device.safariVersion始终为undefined,3.60 起,桌面 Safari 的 UA 命中/Version\/(\d+\.\d+(\.\d+)?) Safari/后,会将主版本号解析进Browser.safariVersion(src/device/Browser.js):
Browser.safari = true; Browser.safariVersion = parseInt(RegExp.$1, 10); Browser.es2019 = (Browser.safariVersion > 10);Device.safariVersion的 JSDoc 定义在 src/device/Browser.js。该属性现在同样参与 ES2019 能力判定,并且在 Phaser 其他子系统(例如 src/device/Audio.js 中基于Browser.safari && Browser.safariVersion >= 9的音频能力分支)中被引用,因此填充正确值对音频等功能的特性探测同样有意义。
2.3Game.registry改用独立的事件发射器
Game.registry是一个DataManager实例,作为全局游戏数据仓库使用(初始化代码见 src/core/Game.js):
this.registry = new DataManager(this, new EventEmitter());3.60 之前它复用Game自身的事件发射器,这意味着如果开发者在 Registry 上广播自定义事件,很容易与 Phaser 核心系统发出的事件发生命名冲突。3.60 起 Registry 拥有自己的 EventEmitter,可以安全地用来发射与监听自定义事件,而不再干扰Game实例自身的事件流:
// 全局数据仓库:存取任意游戏级数据 game.registry.set('playerScore', 100); // 现在可以安全地在 registry 上使用自定义事件 game.registry.events.on('my-custom-event', handler); game.registry.events.emit('my-custom-event', payload);由于DataManager本身支持set/get/has/remove等数据操作,并可通过game.registry.events广播变更事件,这一改动让全局数据仓库真正成为一个可与场景数据(Scene-level DataManager)区分开、且事件命名空间隔离的独立设施。
三、Game、Config 与 Device 的 Bug 修复(Bug Fixes)
3.1headlessStep在PRE_RENDER前重置SceneManager.isProcessing
Game.headlessStep是 HEADLESS 渲染模式专用的游戏步进方法(src/core/Game.js)。在 3.60 中,它在触发PRE_RENDER事件之前执行了this.scene.isProcessing = false:
this.scene.update(time, delta); // Render this.scene.isProcessing = false; eventEmitter.emit(Events.PRE_RENDER, null, time, delta);这修复了 HEADLESS 模式下 Scene Manager 无法处理"游戏启动后新添加的 Scene"的问题(对应 issue #5872 与 #5974)。根因是:若不重置isProcessing标志,Scene Manager 会认为上一帧的处理流程尚未结束,从而跳过新注册 Scene 的后续更新。该修复保证了 headless(典型用途是自动化测试与无头服务器端模拟)环境下动态添加场景同样可靠。
3.2 移除从未使用的Config.domBehindCanvas
Config.domBehindCanvas属性被删除——它在内部从未被使用过(issue #5749)。该配置原意是控制 DOM 容器是否置于 Canvas 之后,但 Phaser 内部根本没有读取它的代码路径,属于长期存在的死配置。移除后,开发者不应再依赖该字段;DOM 容器的层级关系应由Config.dom.createContainer与Config.dom.pointerEvents(默认'none')等有效配置控制(见 src/core/Config.js)。
3.3 不支持 WebGL 时,显式Phaser.WEBGL直接报错而非静默回退
行为变更(issue #5583):当设备不支持 WebGL 时:
- 若
renderType设置为Phaser.WEBGL,创建游戏将直接抛出错误,不再自动回退到 Canvas; - 若需要自动回退能力,请使用
AUTO渲染类型。
底层实现位于 src/core/CreateRenderer.js。AUTO模式会根据Device.features.webGL自动选择 WebGL 或 Canvas;而一旦明确指定了WEBGL,代码会在 WebGL 不可用时抛出'Cannot create WebGL context, aborting.':
if (config.renderType === CONST.AUTO) { config.renderType = Features.webGL ? CONST.WEBGL : CONST.CANVAS; } if (config.renderType === CONST.WEBGL) { if (!Features.webGL) { throw new Error('Cannot create WebGL context, aborting.'); } }这一变更的意图是避免开发者以为在用 WebGL 实际却跑在 Canvas 上的隐性降级——如果你明确要求 WebGL(例如依赖 WebGL 专属特效或渲染管线),静默回退反而会导致功能不可用且难以排查。需要兼容旧设备时,应显式使用AUTO。另外,若customEnvironment为true或提供了自定义canvas,则必须显式指定renderType,不能使用AUTO,否则同样会抛错。
3.4 配置中的render对象不再覆盖顶层配置
此前,只要 Game Config 中包含了render对象,就会整体擦除顶层已有的渲染配置。3.60 起(issue 修复见 src/core/Config.js):
render对象中的设置优先于顶层游戏配置;- 但两者会同时生效——
render中未覆盖的顶层设置依然保留。
从 Config 源码可以看到,所有渲染相关属性都采用双重取值策略:先读render对象,再回退到顶层配置,例如:
this.antialias = GetValue(renderConfig, 'antialias', true, config); this.roundPixels = GetValue(renderConfig, 'roundPixels', false, config); this.transparent = GetValue(renderConfig, 'transparent', false, config); this.clearBeforeRender = GetValue(renderConfig, 'clearBeforeRender', true, config);GetValue(renderConfig, key, default, config)的最后一个参数是兜底源——render中没有该项时,会继续从顶层config取值。这样顶层配置与render子对象可以按"细粒度覆盖"的方式合并。
3.5 空render: {}不再把已设置的属性重置为默认值
配套修复(issue #6097):此前如果配置中出现空对象(如render: {}),会导致已设置的顶层属性被默认值覆盖。3.60 修正了取值逻辑,空对象不会触发覆盖,保证"传了但不填"与"完全不传"行为一致。
3.6PluginManager.installScenePlugin的插件索引恢复
PluginManager.installScenePlugin方法现在会检查插件是否缺失于本地keys数组中,若缺失则将其添加回去(见 src/plugins/PluginManager.js)。这修复了某些流程(如重复安装或插件列表被外部改动)下插件键丢失、导致场景插件无法按名称访问的问题。
四、综合实战示例
将 3.60 的核心新特性组合起来,可以得到一个完整的实战骨架——显式指定渲染类型、自动稳定排序、全局暂停控制与独立事件总线的全局数据仓库:
var config = { type: Phaser.AUTO, // 需要兼容回退时使用 AUTO;显式 WEBGL 不支持时会直接报错 width: 800, height: 600, stableSort: -1, // -1 自动检测 / 1 用原生 ES2019 / 0 用 Phaser 自带稳定排序 parent: 'game-container', scene: { create: function () { // 在 registry 上使用独立的事件总线 this.registry.events.on('score-changed', function (score) { console.log('新分数:', score); }); }, update: function () { if (this.input.keyboard.addKey('P').isDown) { this.game.isPaused = !this.game.isPaused; // 直接切换全局暂停 } } } }; var game = new Phaser.Game(config); // 全局事件:响应暂停与恢复 game.events.on('pause', function () { console.log('已暂停'); }); game.events.on('resume', function () { console.log('已恢复'); });其中stableSort的三种取值策略建议如下:
- 面向现代浏览器(Chrome 70+、Firefox 70+、Safari 11+、新版 Edge/Opera)的新项目:保持默认
-1即可,Phaser 会自动探测; - 需要完全可预测、跨环境一致的排序行为:显式设置
1; - 遇到旧浏览器内核(IE/Trident 等)且需要稳定排序:设置
0,强制走 Phaser 自带的无递归归并排序。
总结
Phaser 3.60 对Game、Config、Device三个地基模块的打磨非常务实:Game.pause / resume / isPaused补齐了"不依赖页面可见性、由开发者主动控制"的全局冻结能力;stableSort+Device.es2019让深度排序既能吃到现代引擎的原生性能、又能安全兼容旧内核;Game.registry独立事件总线消除了全局数据仓库与游戏事件命名空间互相污染的风险;而渲染器"不静默回退"与render配置合并规则的修正,则让开发者的配置意图真正如实生效。这些变化共同提升了 Phaser 3.60 在真实多端环境下的可控性与可诊断性,是升级到 3.60 时值得优先了解的核心改动。
如需查阅本版本全部更新,可继续阅读 changelog/v3/3.60/CHANGELOG-v3.60.md,以及同一版本的 Scene.md、Timestep.md、ScaleManager.md 等模块更新说明。
【免费下载链接】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),仅供参考