MatchMaster/docs/block-image-loading.md

180 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 方块图片远程加载
新增的 `BlockBtnUI.png`、`door.png`、`down.png` 必须与同名 plist 成对上传,共用同一个版本号;其布局来自远程 plist,说明见 [公共图集远程加载](gameplay-atlas-loading.md)。下文仅上传 PNG 的说明适用于普通方块。当前代码只准备当前关和三张公共图集;下文关于两关窗口的描述属于旧方案。
## 当前方案:原始 PNG + 1.0.0 版本号
Block 图片直接使用源 PNG,不加载 Cocos Asset Bundle,不需要 config.<Hash>.json、import、native 等构建产物。微信/抖音小游戏的新下载直接写入 `USER_DATA_PATH/Block/<version>/`,不进入 Cocos 持久缓存队列;assets/Block 当前保持非 Bundle 配置,音频及其他游戏资源的缓存设置不受影响。当前仍按单张 PNG 下载,不使用 ZIP。
配置在 GameConfig.ts:
- BLOCK_IMAGE_BASE_URL:默认 https://cdn.pay.nika4games.com/remote/Block/。
- BLOCK_IMAGE_VERSION:默认 1.0.0,只在没有可用的远程版本和同地址历史版本时兜底。
- 每次冷启动从上述目录读取 version.json,不依赖客户端的构建 Hash。
已添加 assets/Block/version.json:
```json
{
"version": "1.0.0"
}
```
版本格式为三段数字,例如 1.0.0、1.0.1、2.0.0。按字符串是否一致决定缓存命名空间,不按大小阻止回退;不要把同一个版本号重复用于不同图片内容。
## 上传与后续换图
首次发布包含这套逻辑的客户端后,将 assets/Block 中的 **PNG 和 version.json** 原样上传到 CDN 的 remote/Block/。无需上传 .meta,不需要为图片制作 Cocos 构建包或 ZIP。
以后换图:
1. 替换远程对应 PNG,保留文件名,保持画布尺寸及透明边距兼容。
2. 确认新 PNG 可访问,处理 CDN 缓存。
3. **最后修改远程 version.json**,例如将 1.0.0 改成 1.0.1。只替换 PNG 而不改号,已缓存的玩家会继续使用旧图。
4. 重新启动游戏,检查图片与本地缓存记录。远程文件修改由资源维护方完成,本次代码没有上传 CDN。
图片请求示例:
```text
https://cdn.pay.nika4games.com/remote/Block/1color0.png?v=1.0.1
```
**CDN 要求**:PNG 缓存键需包含 v 参数,或者换图时刷新对应 PNG 缓存;version.json 配置 Cache-Control: no-store 或等效的不缓存规则。客户端版本请求带时间戳、不进入 Cocos 文件缓存,但不能代替 CDN 配置。小游戏域名需具备 request / downloadFile 权限;浏览器预览还需要正确的跨域配置。
这是整套图片的一个总版本号。即使只改一张图,改号后也会逐步重新缓存整套图片,不再像按文件 MD5 时那样复用未修改文件。前台仍只等待当前关及下一关,不等待全量下载。
同一路径覆盖 PNG 不会在服务器保存历史内容。当前会话固定使用启动时选择的 v 参数,但旧会话若缺少某张图片,覆盖后仍可能下载到新内容;若要严格保存各历史版本,需要服务器按版本目录保留图片,本方案不包含该部署方式。
## 游戏内行为
1. GameManager.onLoad 启动独立单例 BlockAssetManager 的后台任务。场景切换不会销毁任务。
2. 先确定本次版本;后台下载与进关等待共用同一个检查。清单最多等待 5 秒,缺失、损坏或网络失败时使用同一 CDN 地址上次记录的有效版本,否则使用 GameConfig 的默认版本。
3. 切场景、切关、回到前台不重新选择版本;下次冷启动才检查后续更新。回到前台仍会检查当前版本的磁盘文件是否完整。
4. 小游戏后台使用平台 `downloadFile` 将 PNG 直接写入版本目录,最多 8 路滚动下载,不解析全量图片,不创建全量 Texture2D / SpriteFrame。前台加载期间停止派发新的后台请求;进关图片就绪后自动继续,不等玩家通关,切场景不取消任务。
5. 当前关和实际下一关的图片就绪后才能进入游戏。包含叠层、变色、问号和开关状态;读取下一关不改当前数据或玩家进度。无尽模式预选下一关并在通关时复用。
6. **仅当前关或下一关的图片需要远程下载时**显示 Loading:HomeScene 调用 JiaZai.openLoad/closeLoad,GameScene 调用 SceneManager.openLoad/closeLoad。内存复用、有效本地/临时文件、单独读取关卡 JSON 或版本清单均不打开 Loading;缓存文件仍需加载到内存、图片就绪后才进入游戏。缓存损坏重下、等待后台正在下载的所需图片也会打开 Loading。图片就绪后关闭,失败则关闭并显示重试。后台单独运行不显示 Loading;新版本失败不使用旧图冒充成功。
7. 旧场景显示期间保留它的图片;销毁后释放旧关独占的动态 SpriteFrame、Texture2D 引用和图片解析缓存,共享纹理保留。不会清空磁盘或音频缓存。
8. 浏览器预览同样按原始 PNG 地址加载,需要先上传图片或将 BASE_URL 指向本地静态图片服务;浏览器不进行全套后台磁盘预缓存。
## 下载并发与进关优先
后台使用 **8 个下载 worker**:一张完成就领取下一张,不等待整批 8 张全部结束;每张结束后只让出一次事件循环,没有 50 ms 或 500 ms 的逐图等待。仅下载当前版本目录中缺失的文件。
BlockAssetManager 自己维护下载队列:后台优先级为 `-10`,总下载上限为 10;进关请求优先级为 `10`。即便 8 个后台请求都未结束,进关仍可额外开始 2 个请求。排队中的同名后台任务被进关命中时会提升为前台任务,已经开始的同名任务由前后台共用同一个 Promise,不会重复下载。没有修改 Cocos 下载器的全局并发,也不会改变音频等资源的缓存参数;平台和网络仍可能限制实际并发。
准备当前关、下一关图片期间,后台 worker 不再领取新文件;已经发出的请求继续完成。图片准备完成或失败后,worker 自动恢复(等待期间每 100 ms 检查一次),进入 GameScene 后继续补齐其余文件,不需要再次调用启动。暂停只针对进关图片准备,不会暂停整局游戏期间的缓存。
平台下载先写入目标文件旁的 `.tmp`,校验文件非空后同步重命名为最终 `.png`。最终文件一旦存在即算该张落盘完成,不再调用 Cocos `cacheFile` / `writeCacheFile`,因此 Block 图片不受 Cocos 默认 500 ms 持久缓存间隔影响。升级前遗留的有效 Cocos 持久文件仍可直接读取;遗留临时文件会优先复制到用户版本目录,避免重复下载。
## 后台重试与本地复查
后台只有一个任务和一个待执行定时器:
| 检查结果 | 后续处理 |
| --- | --- |
| 用户版本目录缺少文件,或下载/写入失败 | 本轮结束后 **10 秒**重试;已经直存的文件逐张跳过 |
| 存在升级前的有效 Cocos 临时文件 | 优先复制到用户版本目录;复制失败则 10 秒后重试,不重复下载该临时文件 |
| 所有最终 PNG 都存在且非空 | 写入 `complete:true` 并停止定时重试;冷启动或回到前台仍会校验实际文件 |
后台单张下载不叠加 Cocos 内部重试,失败交给上述 10 秒流程。前台进关仍立即请求所需图片,不等待后台定时器;如果命中正在等待 10 秒重试的缺图,会马上下载。回到前台也会主动检查,版本清单在同一次启动中不因这些重试重复请求。
## 图片范围与布局
当前目录实查为 **368 张 PNG**:原有 322 张方块/问号/开关图片,另有 46 张 ice_ / xz_ 图片。后台文件清单覆盖当前这些文件;本次没有改变新增 ice_ / xz_ 的显示接入,具体进入内存的图片仍由现有关卡收集逻辑决定。
BlockTexturePaths.ts 维护文件名规则,并保留现有 PNG 的裁剪矩形及偏移,避免改用原图后改变现有 Sprite 的大小和位置。更换图片需保持现有画布、透明边距和方块形状兼容;改变布局或增加文件名/形状时需要相应更新客户端配置,而非只改版本号。
预制体的方块 icon.spriteFrame 保持空引用。动态纹理设置 packable=false,以便独立释放。不要把全套图片重新绑定到场景或预制体。
## 本地缓存
小游戏最终文件位于 `wx.env.USER_DATA_PATH/Block/<version>/<name>.png`;抖音使用对应的 `tt.env.USER_DATA_PATH`。版本目录直接隔离不同图片版本,当前实现不会在新版本尚未完整下载时删除旧版本目录,也不会清理音频或其他 Cocos 缓存。
前台进关与后台扫描均检查实际文件:目录存在就不再创建,清缓存或换设备后目录缺失会重新创建。创建时使用平台递归目录参数;并发创建导致的“已存在”会复查目录后视为成功,权限错误或同名文件占位仍会抛出,不删除占位文件。
cc.sys.localStorage 的 block_image_cache_v1 示例:
```json
{
"version": "1.0.0",
"baseUrl": "https://cdn.pay.nika4games.com/remote/Block/",
"total": 368,
"cached": 368,
"complete": true
}
```
version 表示本次使用/缓存的目标版本;版本或地址改变先写 complete:false。只有版本目录下所有图片文件都存在且非空才写 true;`.tmp`、下载中断或磁盘不足都不等于缓存完成。这个记录是提示和诊断数据,不是跳过实际文件校验的依据。
旧的 Hash 版本记录不作为语义版本使用。标记不代替文件检查;换设备、缓存被清理、标记迁移但图片不存在时仍会补齐文件。图片解析失败会删除该 URL 的坏缓存并重试。
## 图片来源与下载日志
控制台过滤 `Block图片`。每次进关(包括重玩同一关)都会打印当前关、下一关、版本,以及各图片的实际加载来源。地图初始化的补充检查使用 `地图初始化` 标签,与切场景前的 `进关` 区分。
Cocos 微信非调试构建的启动日志级别为 `ERROR`,此时 `cc.log` 被引擎设置为空函数。Block 诊断统一使用 `console.log`,不修改游戏全局日志级别。修改 TypeScript 源码后需重新从 Cocos 构建微信包,再在开发者工具运行;只在开发者工具点编译不会重新构建 Cocos 源码。
后台任务也会打印完整状态,不能只根据是否出现单张下载日志判断是否执行:
- `后台缓存 / 启动`:已调用后台任务,随后检查版本与文件。
- `后台缓存 / 跳过`:输出平台下载接口、用户目录文件系统是否可用。
- `版本检查`:请求开始、成功版本,或失败时采用的回退版本。
- `缓存校验`:当前版本用户目录、有效最终文件数/总数、旧临时文件数、缺失数和完整状态。
- `全部本地缓存有效,无需下载`:已检查实际文件,当前不需要新增网络下载。
- `滚动下载队列,最多8张并发` / `在途=N/8`:后台队列的并发上限、已提交且尚未结束的请求数;实际网络并发还受平台及网络调度影响。
- `暂停派发,优先等待进关图片` / `继续后台缓存`:让前台先完成,已发出的请求继续;进关后自动恢复,不代表后台任务被场景销毁。
- `本轮结束`:全部已持久缓存,或说明缺失/旧临时文件数量并在 10 秒后重试。
本地存储的 `complete` 标记不能单独证明设备当前文件仍然存在;以本轮实际文件校验日志为准。日志文件数检查不等同于逐张解码图片完整性,损坏图片在前台解析失败时重试。
日志格式示例(文件名仅作示意):
```text
[Block图片][进关检查] 当前关50 / 下一关51,版本=1.0.0,共3张
[Block图片][进关来源] 当前关50 1color0.png -> 内存复用,版本=1.0.0
[Block图片][进关来源] 当前关50 2color1.png -> 本地缓存,版本=1.0.0
[Block图片][进关来源] 下一关51 3color2.png -> 远程下载,版本=1.0.0
[Block图片][远程加载] 开始 3color2.png,进关,https://.../3color2.png?v=1.0.0
[Block图片][远程加载] 成功 3color2.png,已直存并加载,https://.../3color2.png?v=1.0.0
```
- `内存复用`:SpriteFrame/纹理已经在内存。
- `本地缓存`:当前版本的持久缓存文件存在且非空。
- `临时缓存`:升级前 Cocos 留下的下载文件可复用,但不代表用户版本目录已经完整,同样不弹 Loading。
- `远程下载`:未命中以上缓存;开始请求或等待已有请求。正在下载的后台任务会标为 `等待后台下载`。
- `远程加载` 记录进关请求的开始、成功、失败;`远程下载` 记录后台文件请求的开始、成功、失败,均包含文件名和带版本号的 URL。
- 坏缓存会先记录缓存来源,再打印 `加载重试` 和远程来源。下载成功不代表 `complete:true`,全量缓存落盘状态以校验后的本地记录为准。
浏览器没有小游戏文件缓存接口,日志中的远程表示发起图片 URL 请求;实际是否由浏览器 HTTP 缓存返回,需在浏览器 Network 面板查看,不能仅凭此日志认定消耗了网络流量。
## 多玩家下载与 CDN 压力
本次目录统计:368 张 PNG,总大小 2,761,430 字节(约 2.76 MB / 2.63 MiB)。假设 10,000 名玩家都没有这个版本的本地缓存、且各自完整下载一次,约产生 **368 万次图片请求、27.6 GB 纯图片数据**;这不是实测并发压力,未计入协议开销、重试、版本 JSON 和其他游戏资源。
影响峰值的主要是同时首次下载/更新版本的人数及下载集中程度,不只是在线人数。客户端当前后台最多 8 张并发,进关优先并暂停新的后台派发,已发出的请求可能与进关请求短暂重叠。并发不增加完整下载一遍的图片总量,但会使请求更集中、提高峰值。有效用户目录文件和升级前的有效 Cocos 缓存可直接复用。总版本号变动会让旧玩家逐步重新下载新版本的整套图片,需要关注发布时的集中流量。
CDN 缓存命中时由节点返回,未命中/过期才需要回源,因此不能把玩家的全部图片请求直接等同于源站请求。预热、缓存有效期和监控命中率/回源流量会影响实际负载。[阿里云 CDN 官方说明](https://www.alibabacloud.com/help/en/cdn/getting-started/getting-started)
部署时应将 PNG 的 `v` 保留为缓存键,使同版本请求共享缓存、不同版本相互区分;避免为每张 PNG 增加每次变化的时间戳。新版本可在切换 version.json 前预热带新 `v` 的 URL。版本 JSON 仍需保证更新及时,不要直接套用 PNG 的长缓存规则。[腾讯云缓存键规则](https://cloud.tencent.com/document/product/228/47671)
10 秒重试是客户端等待间隔,不是服务端限流保证;它比 60 秒更频繁,同一批失败玩家也可能集中重试。大规模发布仍需根据 CDN 的请求数、峰值带宽、回源量和错误率评估,必要时再引入启动错峰或分批发布。本次未读取实际 CDN 配置/容量、未进行线上压力测试,也未修改远程部署。
## 验证
调试控制台:
```js
cc.fx.GameConfig.getLoadedBlockImagePaths();
JSON.parse(cc.sys.localStorage.getItem('block_image_cache_v1'));
```
50 关进入 51 关后,常驻图片路径应为 51+52 关需求的去重集合;转场期间可能暂时包含旧场景图片。底层纹理销毁及 GPU/进程内存变化需真机实测。
运行 node --test tools/test-block-assets.cjs;没有项目级 TypeScript 安装时将 TYPESCRIPT_PATH 指向 TypeScript 的 lib/typescript.js。模拟测试覆盖全关卡需求、文件清单、裁剪布局、两关内存窗口、共享/释放/取消、用户目录缺失/损坏/磁盘不足、原子临时文件、旧 Cocos 临时缓存迁移、语义版本更新、断网重启、微信/抖音/浏览器请求,以及两个场景的缓存命中不弹 Loading、远程重试、后台共享下载和逐图来源日志。另覆盖 10 秒网络重试、状态标记与实际文件不一致,以及前台不受后台等待影响。
并发测试覆盖 Block 自有队列的后台 8 路、前台总计 10 路、单张完成即补位、同路径去重和提升优先级、失败不阻塞其他 worker、全体结束后才重试、进关暂停派发、切入 GameScene 后自动继续,以及前台失败后恢复。
全项目类型检查与本次修改前相比为 178 项既有诊断,没有新增错误。模拟测试不替代实际 CDN 和真机验证;本次没有上传 CDN 或完成真机图片/内存验收。
特殊图导出工具 tools/extract-block-images.py 只生成缺失图片,不覆盖已有图片,原图集需另行保留才能运行。此前复核时 92 张特殊图已是索引色 PNG,与工具直接导出的像素存在差异,原因未确认,当前文件保持不变。