Files
UnityAI/modules/frameworks/资源管理/微信小游戏资源接入方案.md

352 lines
15 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.
# HF 框架微信小游戏 CDN 资源接入方案
## 一、目标与边界
本方案只设计 HF/StrayFog 框架自身的微信小游戏资源能力,不与任何具体业务工程绑定。
最终使用方式必须保持简单:
1. 业务继续选择现有 `CDNAssets` 加载模式。
2. 编译时启用 `WeChat` 宏,框架自动使用微信 CDN 实现。
3. 未启用 `WeChat` 宏,框架继续使用原有 CDN 实现。
4. 不新增 `isWeChat` 参数、项目启动代码、项目名路径或业务层初始化接口。
本阶段不处理微信登录、支付、广告、分享,也不负责安装、升级或转换 WXSDK。
## 二、微信官方约束
### 2.1 SDK 与宏
微信官方文档建议 Unity 2019 及以后版本通过 Package Manager 安装 WXSDK,并说明同时导出普通 WebGL 时应使用编译宏隔离微信专用接口。
框架采用已有的 `WeChat` 宏作为唯一开关:
```csharp
#if WeChat
// 微信小游戏实现
#else
// 原有实现
#endif
```
`WeChat` 宏启用时,使用方必须已经导入提供 `WeChatWASM` 和运行时程序集 `Wx` 的微信插件。宏未启用时,框架不得编译或调用任何微信 API。
### 2.2 AssetBundle
微信官方资源文档明确要求:
- 非首包资源放在 CDN,通过异步接口按需加载。
- 小游戏不能沿用普通 APP 的本地路径 AssetBundle 加载方式。
- Bundle 应通过 CDN URL 使用 `UnityWebRequestAssetBundle` 或 `DownloadHandlerAssetBundle` 异步加载。
- 微信转换插件在底层负责 Bundle 缓存和更新;业务层仍按远程请求编写。
- 不应自行将普通 Bundle 写入 `WX.env.USER_DATA_PATH` 后再使用 `LoadFromMemoryAsync`。
- 禁止使用 `WWW`、`WWW.LoadFromCacheOrDownload` 和 Unity 旧 Cache API。
- Bundle 文件名应包含内容 Hash;推荐 LZ4,并在不需要跨 Unity 版本时关闭 TypeTree。
- 不再使用的 Bundle 应及时卸载。
因此,框架的微信 CDN 后端负责“创建远程 Bundle 请求和管理生命周期”,不实现第二套 AssetBundle 磁盘缓存。
## 三、框架公开行为
### 3.1 选择矩阵
| 加载模式 | `WeChat` 宏 | 框架行为 |
|---|---:|---|
| `CDNAssets` | 开启 | 使用微信 CDN AssetBundle 后端 |
| `CDNAssets` | 关闭 | 使用现有 CDN 后端 |
| 非 `CDNAssets` | 开启或关闭 | 保持现有 Editor、PersistentData、StreamingAssets 等逻辑 |
只有同时满足“当前为 `CDNAssets`”和“编译启用 `WeChat`”时才切换实现。业务调用方式、资源 ID、回调和 `HFFunCatalogue.Asset` 入口保持不变。
### 3.2 唯一开关
框架内部选择逻辑应等价于:
```csharp
if (loadModel == enHFAbLoadModel.CDNAssets)
{
#if WeChat
LoadAssetBundleByWeChatCdn();
#else
LoadAssetBundleByDefaultCdn();
#endif
return;
}
LoadByExistingMode();
```
不得再叠加以下开关:
- `HF_WECHAT_MINIGAME`
- `UNITY_WEIXINMINIGAME`
- `useWechatCdn`
- `isMiniGame`
- 项目配置表中的微信布尔值
Unity 目标平台仍由原逻辑识别为 WebGL,资源平台目录继续使用 `ab_webgl`;`WeChat` 只决定 CDN 实现,不新增 `ab_wechat`。
## 四、框架内部设计
### 4.1 编译期分流
微信实现直接封装在现有框架资源调用链中,不新增业务初始化入口:
```text
HFAssetLoader
-> CDNAssets
-> UnityWebRequestAssetBundle(仅 WeChat 宏编译)
-> 原有下载与本地缓存流程(未启用 WeChat 宏)
HFABMgr Manifest
-> CDNAssets
-> UnityWebRequestAssetBundle(仅 WeChat 宏编译)
-> 原有 Manifest 下载流程(未启用 WeChat 宏)
SFSubPackageRuntimeV2
-> Bootstrap 与状态文件
-> WeChatMiniGame / WXFileSystemManager(仅 WeChat 宏编译)
-> System.IO(未启用 WeChat 宏)
-> 普通 AssetBundle
-> UnityWebRequestAssetBundle 预热,由微信插件缓存
```
微信桥接负责两项能力:
- 创建微信环境下的远程 AssetBundle 请求。
- 为 V2 状态和 Bootstrap 原始文件提供微信文件存储。
普通 AssetBundle 缓存仍由微信插件底层负责,不进入 `HF Byte Storage`。
### 4.2 无注册、无第二开关
不建立 `HFResourcePlatform.Register(...)` 一类注册流程,也不要求项目启动代码识别微信平台。现有 CDN 根地址、版本、渠道和平台参数仍使用框架原配置;`WeChat` 只在编译时替换 `CDNAssets` 的底层实现。
微信文件根目录由 `HFAssetBundleHelper.GetCdnSaveRootPath()` 在 `#if WeChat` 中直接返回 `WX.env.USER_DATA_PATH` 下的框架目录。V2 在第一次读取状态前等待下载配置与存储回调全部设置完成,避免先访问 `persistentDataPath` 再切换存储。
### 4.3 程序集边界
- 微信 API 只出现在 `#if WeChat` 包围的框架代码中。
- `WeChatMiniGame` 使用插件提供的 `WeChatWASM` 文件系统类型。
- Loader、Manifest 和 V2 普通 Bundle 分支只依赖 Unity 标准 `UnityWebRequestAssetBundle`。
- 不新增项目程序集引用、项目名、业务配置或微信布尔字段。
- 未启用 `WeChat` 宏时,原有 CDN 代码保持编译和运行。
- 启用 `WeChat` 宏但没有导入微信插件时,`WeChatMiniGame` 会产生明确的编译错误,而不是静默使用普通 WebGL 文件系统。
## 五、CDN AssetBundle 实现
### 5.1 默认微信后端
第一阶段使用微信官方常规异步方案:
```csharp
#if WeChat
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(remoteUrl);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
// 交给 HF Loader 生命周期管理。
}
else
{
// 进入框架原有候选 CDN、重试和失败回调。
}
request.Dispose();
#endif
```
实现规则:
- 始终传入完整 CDN URL;热启动同样走该请求,由微信插件判断缓存命中。
- 不调用 `ExistLocalFile`、`ReadLocalFile`、`File.Exists`、`FileInfo`、`File.ReadAllBytes`、`AssetBundle.LoadFromFileAsync` 或 `AssetBundle.LoadFromMemoryAsync` 加载微信普通 Bundle。
- 不对插件 Bundle 缓存做业务层物理文件检查、删除和大小统计。
- 不使用 `WWW`、旧 Cache API 或 IndexedDB 模拟缓存。
- 请求进度通过 `UnityWebRequest.downloadProgress` 回传给现有 HF 回调。
- 资源大小优先使用 Manifest 的 `Size`;没有可靠值时保持 `Size = 0`,不影响加载结果。
- 相同 URL 的并发请求继续由框架合并。
- 请求失败继续使用框架现有候选 CDN 地址与重试策略。
### 5.2 `WXAssetBundle` 扩展后端
微信插件还提供更节省内存的 `WXAssetBundle`。官方同时说明它可能增加磁盘读取成本,因此第一阶段不默认启用。
后续如果真机数据证明必须使用,可以在框架现有微信 CDN 请求方法内部整体替换实现,不改变业务接口和宏:
```csharp
WXAssetBundle.GetAssetBundle(remoteUrl)
DownloadHandlerWXAssetBundle.assetBundle
bundle.WXUnload(false)
```
这仍然只由 `WeChat` 宏选择。不得为它再增加第二个业务开关;是否升级为该实现属于框架版本决策。
### 5.3 Hash 与更新
微信底层缓存依赖 URL 和 Bundle Hash。框架构建工具必须保证:
- WebGL Bundle 物理文件名包含内容 Hash。
- V2 Manifest 保存 CDN 上真实的 Hash 文件名。
- 相同内容 URL 稳定,不同内容生成新 URL。
- 使用 `BuildAssetBundleOptions.ChunkBasedCompression`。
- 无跨 Unity 版本兼容要求时使用 `DisableWriteTypeTree`。
- 如果现有构建器已经拼接内容 Hash,不再重复使用另一套 Hash 命名。
微信端不读取 Bundle 本地字节做 MD5 校验。V2 中的 MD5 保留用于构建审计、差异展示和非 Bundle 原始文件校验。
## 六、V2 状态与 Bootstrap 存储
### 6.1 存储范围
微信文件存储只处理:
- `subpackage_state.json`
- 最近一次有效的 `subpackage.json`
- AOT DLL 原始字节
- HotUpdate DLL 原始字节
- 框架未来明确声明为原始文件的内容
普通 AssetBundle、场景 Bundle、UI、模型、贴图和音频 Bundle 不写入此存储。
### 6.2 框架通用路径
微信小游戏自身已经按应用隔离用户目录,因此框架路径不包含任何项目名:
```text
WX.env.USER_DATA_PATH/hf_resource/v2/ab_webgl
```
路径由 `WeChatMiniGame.MiniGameSaveRootPath()` 内部生成。业务不能传入产品名或项目目录。
处理规则:
- 使用 `WXFileSystemManager` 读写。
- 路径统一为 `/`,禁止 `..` 和越过框架根目录。
- 不使用 `FileInfo` 解析微信路径或统计大小。
- 已持有完整原始字节时可以记录 `LongLength`,否则大小保持 `0`。
- 读取为 `null`、空字节或 MD5 不一致时,当前流程重新下载并覆盖。
- 写入或删除失败不影响下一次重新读取和恢复。
### 6.3 V2 初始化
V2 初始化由资源框架内部完成:
1. 平台桥接已经自动注册。
2. `SetLocalStorage` 只保存回调,不自行调用 `LoadState()`。
3. CDN、版本、平台和 V2 Local Root 全部设置完成后,只调用一次 `LoadState()`。
4. `WeChat` 宏下使用微信字节存储;`#else` 使用 `System.IO`。
5. 禁止先访问 `Application.persistentDataPath`,再切换到微信存储。
V2 的 Bundle 历史下载状态不能作为微信插件缓存仍存在的证明。每次进程首次使用某个 Bundle 时仍创建远程请求,由插件决定缓存或网络;Package 的微信状态只表示本次进程请求成功。
## 七、卸载与失败恢复
### 7.1 卸载
第一阶段使用 `UnityWebRequestAssetBundle`:
```csharp
assetBundle.Unload(false);
```
卸载顺序:
1. 业务释放场景、窗口、音频或实例引用。
2. HF Loader 清理资源引用。
3. Bundle 执行 `Unload(false)`。
4. 请求和 DownloadHandler 被释放。
如果框架以后内部切换为 `WXAssetBundle`,对应请求方法必须改用 `WXUnload(false)`。这一差异仍封装在框架内部,不暴露给业务。
`Resources.UnloadUnusedAssets` 只在场景切换或批量窗口关闭后的安全节点调用,不在每次资源卸载时执行,也不主动调用 `GC.Collect`。
### 7.2 失败恢复
- 请求失败按候选 CDN 和框架重试次数重新请求。
- 新 Bundle 通过新 Hash URL 绕过旧版本缓存。
- 不依赖微信插件缓存的物理路径,不主动删除插件缓存。
- 请求成功但 `AssetBundle == null` 时最多重试一次,禁止无限循环。
- V2 状态或 Bootstrap 原始文件损坏时重新下载覆盖。
- 日志包含逻辑资源名、完整 URL、候选地址序号、重试次数和请求错误。
## 八、框架代码修改边界
| 内容 | 框架源位置 |
|---|---|
| CDN 地址、本地根目录与宏选择 | `Assets/StrayFog/Editor/CopyToX/CopyToGeneral/AssetBundle/ESF_AssetBundleHelper.cs` |
| 微信字节存储实现 | `Assets/StrayFog/Editor/CopyToX/CopyToGeneral/MinGameModel/WeChatMiniGame.cs` |
| Loader 请求、进度、重试和卸载 | `Assets/StrayFog/Editor/CopyToX/CopyToHF/AssetBundle/Class/` |
| CDN Manifest 请求 | `Assets/StrayFog/Editor/CopyToX/CopyToHF/AssetBundle/ESF_ABMgr_Manifest.cs` |
| V2 状态与 Bootstrap | `Assets/StrayFog/Core/SFSubPackageV2.cs` |
| WebGL Bundle 构建参数与 Hash Manifest | `Assets/StrayFog/Editor/Tools/AssestBundle/` |
禁止把实现放入项目的 Game、Hotfix、启动入口、LoadingWindow 或其他业务管理器中。
修改 ESF 源后执行 CopyToX,并验证生成结果;不能只修改 CopyToHF、CopyToGeneral 等生成目录。
## 九、验收标准
### 9.1 公共行为
- 业务只设置 `CDNAssets`,不调用微信初始化 API。
- 打开 `WeChat` 宏后自动走微信 CDN 后端。
- 关闭 `WeChat` 宏后自动走原 CDN 后端。
- 非 CDN 模式行为不变。
- 框架代码中没有项目名、项目路径或业务程序集依赖。
### 9.2 微信 CDN
- Bundle 使用完整 CDN URL 异步加载。
- 相同 Hash URL 的后续请求由插件复用缓存,框架代码路径不变。
- 框架不生成自定义微信 Bundle 缓存文件。
- Bundle Loader 不持有用于 `LoadFromMemoryAsync` 的完整缓存字节。
- 请求失败、重试、进度和完成回调符合现有公开 API。
- Bundle 卸载后 Loader 和 Bundle 内存下降。
### 9.3 V2 原始文件
- V2 状态与 Bootstrap 写入框架通用微信目录。
- 空文件、损坏文件和 MD5 不一致能够重新下载。
- 微信路径不进入 `System.IO` 和 `FileInfo`。
- V2 初始化只读取一次正确存储中的状态。
### 9.4 回归
- Editor、Android、iOS 和普通 WebGL 的原 CDN 行为不变。
- 未启用 `WeChat` 宏时不编译微信 API 调用。
- `WeChat` 宏启用但插件缺失时给出明确编译或初始化错误。
## 十、当前实施结果
截至 2026-07-22,框架第一阶段已经完成:
1. `CDNAssets + WeChat` 直接使用 `UnityWebRequestAssetBundle` 加载主 Bundle,普通 Bundle 不再走自建文件缓存和 `LoadFromMemoryAsync`。
2. CDN Manifest 使用同一 AssetBundle 请求方式,并支持按现有候选 CDN 根地址回退。
3. 请求成功但 Bundle 为空时只重试一次;网络失败继续尝试候选 CDN,失败后正确结束 Loader 状态。
4. 微信普通 Bundle 的 V2 下载改为请求预热,只记录本进程成功状态,不持久化伪缓存命中状态。
5. V2 状态和 Bootstrap 原始字节写入 `WX.env.USER_DATA_PATH/hf_resource/v2/<platform>`,微信路径不再进入 `FileInfo`。
6. 微信读取返回 `null` 或 MD5 不一致时沿用 V2 原流程重新下载;微信写入结果会向 V2 报告成功或失败。
7. `SetDownloadConfig` 和 `SetLocalStorage` 只更新配置,状态在首次实际操作前延迟读取一次。
8. ESF 源文件已按 CopyToX 规则同步到 HF 生成目录,并校验生成内容一致。
9. 普通分支及临时追加 `WeChat` 宏的 `StrayFogCore`、`GameGeneral`、`GameHF`、`SFEditor` 均完成 0 error 编译验证。
仍需真机阶段验证微信插件缓存复用、弱网切换、内存峰值和 Bundle 卸载效果。真机数据不足前不启用 `WXAssetBundle`,也不增加第二个开关。
## 十一、外部依据
- [微信 SDK 安装与使用](https://developers.weixin.qq.com/minigame/dev/guide/game-engine/unity-webgl-transform/Design/SDKInstaller.html)
- [微信资源按需加载概述](https://developers.weixin.qq.com/minigame/dev/guide/game-engine/unity-webgl-transform/Design/ResourcesLoading.html)
- [微信使用 AssetBundle 进行资源按需加载](https://developers.weixin.qq.com/minigame/dev/guide/game-engine/unity-webgl-transform/Design/UsingAssetBundle.html)
- [微信 FileSystemManager.readFile](https://developers.weixin.qq.com/minigame/dev/api/file/FileSystemManager.readFile.html)
## 十二、文档关联
| 文档 | 关联原因 |
|---|---|
| [资源管理](README.md) | 当前资源框架职责和实现边界 |
| [资源管理使用指南](使用指南.md) | 现有加载、卸载和资源更新公开 API |
| [Hotfix 启动](../Hotfix启动.md) | Bootstrap AOT 与 HotUpdate 启动顺序 |
| [工作流清单](../../../rules/40-工作流清单.md) | 实施、生成、验证和文档同步流程 |