在Addressable中分离字体与图集依赖
解读
国内上线包体普遍要求“首包<150 MB”,而字体(Font)+图集(Atlas)往往占掉30%以上。策划与美术为了“一图多用”会把主界面、战斗、商城三套UI打成一个巨型图集,再把“全量汉字+符号”的TTF/OTF打包进Resources,导致首次下载慢、热更冗余、内存峰值高。Addressable虽然支持“按标签分包”,但字体与图集天然存在“被预制体同时引用”的链式依赖,若不做显式分离,最终仍会被打进同一Bundle,失去粒度控制的意义。因此面试官想确认两点:
- 你是否理解AssetBundle Provider 与 ResourceLocationMap 的依赖解析规则;
- 能否给出可落地的分包策略,兼顾版本兼容、包体大小、运行时加载耗时与内存占用。
知识点
- Addressable AssetGroup 的 Include Dependencies 开关:默认开启,会把被引用的资产自动拉进同一组;关闭后需手动补齐依赖,否则运行时报“KeyNotFoundException”。
- AssetReference vs 直接引用:预制体里挂“Font”或“SpriteAtlas”字段属于直接引用,只要该预制体进A组,字体/图集必进A组;改用AssetReferenceAtlasedSprite/AssetReferenceTTF才能打破链。
- Label 与 Schema 的“副本规则”:同一资产打两组会产生双份,需用**“Duplicate Asset Isolation”**或自定义脚本去重。
- Android/iOS 字体渲染差异:Android 7 以下对 SDF 字体支持不完整,需额外打包Fallback Asset(.fontsettings + .ttf);iOS 动态字有系统字体回退,可只打包核心字符集。
- 图集后打包(Late Binding Atlas):Unity 2021.2+ 的 SpriteAtlasV2 支持**“Include in Build”关闭**,运行时通过AtlasManager.Register手动绑定,实现“脚本级分离”。
- Build Script 的回调注入点:
IDataBuilder接口的PostProcessBundles阶段可扫描 bundle 内依赖,把大于阈值的字体/图集自动迁移到新组,并写回 catalog。 - 内存峰值控制:字体采用动态字+预生成SDF(Hybrid模式),图集采用**“稀疏精灵”+按需UnloadUnusedAssets**,防止一次性加载整图集导致GFX内存暴涨。
- 热更合规:国内渠道(华为、OV)要求**“资源可逆降级”,catalog 必须带CRC + Hash + OfflineCache**,分离后需验证字体回退链是否完整,否则会出现“口口口”事故。
答案
- 资产改造
a. 把所有UI预制体里的“Font”字段改为AssetReferenceTTF,Sprite 改为AssetReferenceAtlasedSprite;
b. 对字体做子集化:用FontEngine.GetGlyphPairAdjustmentTable统计常用汉字7000个,生成DynamicOSFontFallback.asset,把完整TTF拆成**Core.fontsettings(200 KB)+ Full.ttf(8 MB)**两份。 - 分组策略
a. 新建组“G_Font_Core”放Core.fontsettings,Include Dependencies=Off,Label=font_core;
b. 新建组“G_Font_Full”放Full.ttf,Include Dependencies=Off,Label=font_full,下载类型设为**“按需下载”;
c. 新建组“G_UIAtlas_Common”放公共图集,Include Dependencies=Off,Label=atlas_common;
d. 新建组“G_UIAtlas_Shop”放商城大图集,Include Dependencies=Off,Label=atlas_shop;
e. 在Custom Build Script**里扫描所有Bundle,若发现字体体积>500 KB或图集>1 MB,自动迁移到对应大资源组,并记录迁移日志。 - 加载层封装
a. 字体加载:优先使用Addressables.LoadAssetAsync<FontSettings>(“font_core”),若出现缺字,触发**“font_full”的异步下载+Font.CreateDynamicFontFromOSFont回退;
b. 图集加载:UI面板打开前调用AtlasManager.PreloadAtlases(List<string> labels),下载完成后通过SpriteAtlasManager.Register注册,UnloadAssetBundles时按引用计数卸载;
c. 网络失败兜底:把“font_core”与“atlas_common”同时打一份StreamingAssets\Boot目录,首包自带,启动时用Addressables.RuntimePath**重定向到本地路径,保证离线可用。 - 验证与监控
a. 打真机包后,用Unity Profiler->AssetBundle Stats检查首包只含“G_Font_Core+G_UIAtlas_Common”,体积<30 MB;
b. 用UWA GOT查看首次打开商城界面时,G_Font_Full+G_UIAtlas_Shop的下载量<15 MB,内存峰值增加<50 MB;
c. 灰度阶段在Bugly埋点,统计“字体回退成功率>99.5%,图集下载失败率<0.3%”,低于阈值才全量推送。
拓展思考
- 如果项目使用TextMeshPro,需把SDF生成参数(Atlas Width=2048, Padding=8)也做分包,TMP的Font Asset Fallback List支持运行时插入,可结合Addressables.ResourceLocationAsyncOperation实现“字重按需下载”。
- 当项目升级到Unity 2022 LTS,可试用**“Content Catalog JSON 分离”功能,把字体与图集的Catalog拆成独立文件,实现“静默后台更新”**,用户无需重启APP即可看到新字重或新图标。
- 面对字节跳动、微信小游戏的“子包”限制,需把Addressable Bundle再转成**“小游戏插件分包”格式,利用ttf压缩库(font-spider-core)把8 MB字体压到1.2 MB,同时用CRN/DXT5把图集压到原体积25%,在WebGL平台通过“IndexedDB缓存”**实现二次进入秒开。