缓存命中不是唯一目标
H5 游戏最怕首次加载慢,于是团队自然会想到 PWA、Service Worker 和 CDN 缓存。缓存确实能提升体验,但缓存策略一旦设计不好,会带来更难排查的问题:代码是新版本,资源是旧版本;地图 JSON 已更新,tileset 还没更新;玩家离线进入旧包,联网后存档字段不兼容。
我遇到过一次线上事故,活动游戏热更新了新角色资源,部分玩家却看到白块。排查后发现,入口 HTML 命中了新版本,Service Worker 仍然返回旧 atlas JSON,CDN 上图片又是新 hash。三个层面的缓存各自“正常”,组合起来却不一致。缓存命中率很好看,玩家体验很糟。
Phaser 项目的发布目标不是尽可能缓存一切,而是保证每次进入游戏时,代码、配置、资源和存档迁移处在同一个版本语义下。资源版本一致性比单个文件的缓存命中更重要。
flowchart TD
A[构建产物] --> B[生成 manifest 和 hash]
B --> C[上传 CDN]
C --> D[发布入口 HTML]
D --> E[Service Worker 检查版本]
E --> F{manifest 是否完整}
F -->|是| G[切换到新资源集]
F -->|否| H[继续使用旧版本]
G --> I[Phaser 加载资源]
H --> I
HTML、代码和资源要分层缓存
入口 HTML 通常不应该长期强缓存,因为它决定玩家进入哪个版本。JS、CSS、图片、音频、地图等带 hash 的资源可以长缓存。manifest 可以短缓存或网络优先,用来告诉客户端当前资源集是什么。不同层使用不同策略,才能兼顾更新和速度。
如果入口 HTML 被强缓存,玩家可能长期停在旧版本;如果所有资源都不缓存,加载体验会差;如果无 hash 资源长缓存,更新又会混乱。最稳的方式是文件名带内容 hash,CDN 设置长缓存,入口和 manifest 控制版本切换。
Phaser preload 时最好读取 manifest,而不是手写一堆固定路径。manifest 里记录资源 key、url、hash、类型和分组。这样发布系统能保证 Phaser 加载的资源来自同一个构建产物。
Service Worker 要谨慎接管
Service Worker 很强,也很容易制造幽灵问题。它可以离线缓存资源、拦截请求、预取下一关资源,但一旦逻辑写错,玩家浏览器里会长期保存错误策略。尤其是活动 H5,生命周期短,没必要为所有项目上复杂 SW。
如果使用 SW,建议策略简单:安装时缓存核心壳和当前 manifest,运行时按 manifest 缓存带 hash 资源,切换版本时先确认新 manifest 全部可用,再激活新缓存。不要在 fetch 里写过多特殊分支。越复杂越难回滚。
SW 更新也要有 UI 策略。新版本准备好时,是立即刷新、下局生效,还是提示玩家?正在战斗中强刷会破坏体验。可以在结算页或大厅提示“新内容已准备,重新进入后生效”。发布策略要尊重游戏状态。
离线可玩要定义范围
PWA 离线不是默认目标。离线能不能进入游戏,取决于玩法是否需要服务器、广告、登录、排行榜和活动配置。如果只是单机关卡,可以支持离线;如果奖励、排行和活动时间都依赖服务端,离线只能提供有限体验或提示联网。
离线模式要有明确边界。哪些资源必须缓存,哪些功能隐藏,离线存档如何标记,重新联网后如何同步。不要让玩家离线获得大量奖励,联网后又被服务器拒绝。对玩家来说,这会像丢进度。
Phaser 客户端可以在启动时检测网络和配置状态。若 manifest 可用但远程活动配置不可用,可以进入本地练习模式;若核心资源不完整,就显示离线不可用。不要让加载失败表现为空白页。
发布要原子化
CDN 发布最怕半完成状态。先上传 JS,再上传图片,再上传 manifest,过程中玩家可能拿到不完整版本。发布顺序应该保证 manifest 最后切换。只有当所有 hash 资源都上传成功并可访问,才发布指向它们的新 manifest 或入口。
回滚也要设计。带 hash 的旧资源应该保留一段时间,旧 manifest 也要可访问。发现新版本有问题时,把入口或版本指针切回旧 manifest,而不是急着删除新资源。删除资源可能让已经拿到新 HTML 的玩家彻底无法加载。
灰度发布更复杂。不同玩家可能拿到不同 manifest。存档迁移要兼容灰度版本,服务端接口也要兼容。不要让灰度玩家写入新版存档后回到旧版无法读取。对于活动小游戏,灰度范围和时长要谨慎。
Phaser 资源 key 和 CDN hash
Phaser 运行期使用 key 访问资源,CDN 使用 hash 保证缓存。两者不要混淆。资源 key 应该稳定表达语义,例如 hero_idle、level_03_map;URL 可以带 hash,例如 /assets/hero_idle.8f3a.png。如果把 hash 放进 key,业务代码和配置会频繁变化。
manifest 可以把稳定 key 映射到 hash URL。Preload 根据 manifest 加载,业务始终使用稳定 key。这样 CDN 缓存和游戏逻辑互不干扰。资源更新只改 manifest,不改玩法配置。
也要避免 key 冲突。不同活动如果都叫 bg,缓存里可能互相覆盖。可以按模块命名:event_summer_bg、common_button_primary。Phaser cache 的 key 是全局运行期空间,不是文件路径。
一个 manifest 加载切口
下面示例展示 Phaser 侧如何根据 manifest 加载资源。真实项目还需要处理失败重试和分组加载。
type AssetEntry = { key: string; type: 'image' | 'atlas' | 'audio' | 'json'; url: string; meta?: string };
function loadFromManifest(scene: Phaser.Scene, assets: AssetEntry[]) {
for (const asset of assets) {
if (asset.type === 'image') scene.load.image(asset.key, asset.url);
if (asset.type === 'json') scene.load.json(asset.key, asset.url);
if (asset.type === 'audio') scene.load.audio(asset.key, asset.url);
if (asset.type === 'atlas' && asset.meta) scene.load.atlas(asset.key, asset.url, asset.meta);
}
}
重点是 Phaser 不关心 hash 规则,只消费 manifest。发布系统负责生成正确 URL,客户端负责按 manifest 加载并校验失败。职责分开以后,缓存策略才不会侵入玩法代码。
诊断信息要面向玩家和客服
缓存问题很难靠玩家描述。页面显示白块,可能是 CDN 404、SW 旧缓存、manifest 错误、跨域失败、WebGL 纹理上传失败。游戏里应该提供复制诊断信息的能力,至少包含 app version、manifest version、SW version、资源加载失败列表、浏览器和网络状态。
客服不需要看技术细节,但需要能把诊断信息交给工程。工程拿到版本和失败 URL,就能判断是发布问题还是个别网络问题。没有诊断,只能让玩家清缓存,这既不专业,也不一定解决。
开发版还可以提供缓存面板:当前 manifest、缓存资源数量、SW 状态、可用离线资源、清理缓存按钮。PWA 问题常常发生在浏览器内部状态里,没有面板就很难观察。
上线前检查清单
上线前检查:入口 HTML 是否短缓存,hash 资源是否长缓存,manifest 是否最后发布,旧资源是否保留,SW 是否能更新和回滚,离线范围是否定义,Phaser key 是否稳定,资源失败是否有重试和诊断,灰度版本是否兼容存档。
还要实际演练:从旧版本进入、更新到新版本、断网进入、半路切后台、清理部分缓存、SW 更新失败、CDN 某个资源 404、回滚到旧 manifest。发布系统不是只在成功路径上工作,真正需要验证的是失败和回退。
发布窗口要考虑玩家正在游戏中
H5 游戏发布经常被当成网页发布,觉得刷新即可。但游戏玩家可能正在一局中、正在看广告、正在结算、正在写存档。如果 Service Worker 在这个过程中切换资源,或者入口提示强制刷新,就可能打断关键流程。发布策略应该和游戏状态协作。
客户端可以把更新分成 discovered、downloaded、ready、applied 几个状态。发现新版本时不打扰玩家,下载完成后等待安全点,例如大厅、结算页或下一次启动。只有安全修复才考虑强提示。对于活动游戏,发布窗口最好避开流量高峰和活动结算时段。
还要考虑多标签页。玩家可能开了两个页面,一个旧版本一个新版本,它们共享同一份浏览器缓存和 localStorage。存档写入要带版本和时间,避免旧标签覆盖新标签的数据。Service Worker 激活新版本时,也要知道旧客户端可能还在运行。浏览器环境不是单实例游戏机,发布系统必须承认这一点。
渠道容器会改变缓存假设
同一个 Phaser 游戏放在普通浏览器、微信 WebView、广告落地页和内嵌 App WebView 中,缓存行为可能不同。有的容器会强缓存入口页,有的会改写 UA,有的会限制 Service Worker,有的会在后台主动清理存储。发布策略不能只按桌面 Chrome 的表现设计。
上线前应该建立渠道矩阵:是否支持 Service Worker,是否允许添加到桌面,是否会清理缓存,是否限制音频和全屏,是否有自己的离线包机制。对于不可靠的容器,可以禁用复杂 PWA,只使用 CDN hash 和短缓存入口。能力不足时退回简单方案,比在不可控环境里强行启用 SW 更安全。
如果渠道提供预加载或离线包能力,也要和 manifest 对齐。渠道包里是什么版本,线上 manifest 指向什么版本,二者必须能解释。否则玩家通过渠道预加载拿到旧资源,入口又请求新配置,问题会比普通浏览器更难排查。
发布文档里最好写清楚每个渠道的刷新方式。普通浏览器可能刷新入口即可,某些 WebView 需要清理离线包,某些广告渠道需要重新提审素材。发布不是只有技术按钮,还包括渠道缓存生效的现实时间。
回滚文档同样重要。出问题时谁切版本指针,谁验证旧 manifest,谁通知运营暂停投放,都应该提前写清楚。发布事故发生后再临时分工,通常会浪费最宝贵的前几分钟。
对活动游戏来说,这几分钟可能就是整场投放的转化差距。
发布、灰度和回滚都写成清单,团队才能在压力下按步骤执行。
清单越具体,恢复越快。
执行人也更不容易漏步骤。
结语
Phaser H5 游戏使用 PWA 和 CDN 可以显著改善加载体验,但缓存策略必须服务版本一致性。代码、资源、配置和存档迁移要作为一个资源集切换,而不是各自缓存各自更新。
缓存命中率高不等于发布可靠。可靠发布意味着新版本完整才切换,旧版本可回退,离线边界清楚,诊断信息可用。做到这些,Phaser 游戏才能在浏览器复杂缓存环境里稳定运营。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。