[资源构建] 图集白块问题
本文介绍运行时精灵丢失图集、显示为白块或透明块的原因,以及两种常用解决方案。
问题原因
UI预制体通常引用的是源图片中的 Sprite 对象,而不是 .spriteatlas 资源本身。虽然Unity在构建阶段会把Sprite的渲染数据重定向到图集纹理,但在编辑器资源依赖关系中,UI预制体不一定会显式依赖对应的 SpriteAtlas。
YooAsset加载资源时,会根据资源清单中记录的实际依赖加载Bundle。如果面板资源没有建立对图集Bundle的依赖,运行时只加载面板和源Sprite所在的Bundle,保存最终图集纹理的Bundle却没有被加载,Sprite便无法取得正确的纹理数据,从而显示为白块。
两种方案的核心区别如下:
- 方案1在编辑器保存UI预制体时,人为添加对图集资源的显式引用。
- 方案2在YooAsset构建资源清单时,根据SBP构建结果自动补充图集Bundle依赖。
关于 Include In Build
使用本文两种方案前,请确保SpriteAtlas检视面板勾选 Include In Build。开启后,Unity引擎会在构建时为Sprite序列化默认图集引用;相关依赖加载完成后,Sprite可以直接读取图集中的渲染数据。
方案1:编辑器下手动添加图集依赖
该方案在保存UI预制体时扫描所有 Image 组件,根据Sprite找到对应的SpriteAtlas,并将其写入 PanelManifest.ReferencesAtlas。序列化后的 SpriteAtlas 字段会形成显式资源引用,因此YooAsset能够识别并记录 UI预制体 -> SpriteAtlas 的依赖关系。
完整代码请参考:Extension Sample/Runtime/UIPanelMonitor目录脚本。
导入相关脚本后,需要开启监控并配置项目目录的GUID。目录GUID可以从对应目录的 .meta 文件中获取。
public static bool EnablePanelMonitor = true;
/// <summary>
/// 面板文件夹GUID
/// </summary>
private const string UIPanelDirectoryGUID = "12d33f33f3a55224c9c747d7bffa1c68";
/// <summary>
/// 精灵文件夹GUID
/// </summary>
private const string UISpriteDirectoryGUID = "935d7f20c085cc141a3daf9cacfabfae";
/// <summary>
/// 图集文件夹GUID
/// </summary>
private const string UIAtlasDirectoryGUID = "c355c783476322b4cacac98c5e1b46d8";
public class PanelManifest : MonoBehaviour
{
/// <summary>
/// 面板依赖的图集列表
/// </summary>
public List<SpriteAtlas> ReferencesAtlas = new List<SpriteAtlas>();
}
public class UIPanelMonitor : UnityEditor.Editor
{
[InitializeOnLoadMethod]
static void StartInitializeOnLoadMethod()
{
PrefabStage.prefabSaving += OnPrefabSaving;
}
static void OnPrefabSaving(GameObject go)
{
// 扫描预制体的所有Image组件,
// 找到对应图集并写入PanelManifest。
}
}
示例按照以下约定推导图集路径:
精灵路径:Assets/UISprites/Common/button.png
图集路径:Assets/UIAtlas/Common.spriteatlas
也就是说,精灵所在的直接父目录名必须和图集文件名一致。项目目录结构不符合该约定时,需要修改 GetAtlasPath() 的查找逻辑。
注意事项
- 示例代码仅在Unity 2021.3及以上版本的编辑器中生效,并且
EnablePanelMonitor默认关闭。 - 示例只扫描UGUI的
Image组件,不会处理SpriteRenderer、UI Toolkit、自定义组件或运行时动态赋值的Sprite。 - 扫描范围包含禁用节点,但动态创建的子节点以及运行时替换的Sprite仍需自行处理。
- 不在指定精灵目录中的图片会被忽略;找不到约定的图集文件时会抛出异常。
- 保存预制体后应检查
PanelManifest中的图集列表是否完整,并避免手工删除该组件或引用。
方案2:构建时自动建立图集依赖
YooAsset的可编程构建管线可以通过 TrackSpriteAtlasDependencies 自动补充图集依赖:
private static void BuildInternal(BuildTarget buildTarget)
{
var buildOutputRoot = BundleBuilderHelper.GetDefaultBuildOutputRoot();
var streamingAssetsRoot = BundleBuilderHelper.GetStreamingAssetsRoot();
// 构建参数
var buildParameters = new ScriptableBuildParameters();
buildParameters.BuildOutputRoot = buildOutputRoot;
buildParameters.BundledFileRoot = streamingAssetsRoot;
buildParameters.TrackSpriteAtlasDependencies = true;
// 其它参数省略
// 执行构建
}
实现原理
构建过程中,YooAsset会执行以下处理:
- 在生成构建映射时收集所有参与构建的
SpriteAtlas资源及其Bundle名称。 - 从SBP构建结果中获取Bundle依赖关系,并建立反向引用关系图。
- 找出所有引用图集Bundle的资源包。
- 遍历这些资源包中的主资源,将图集Bundle ID补充到主资源的依赖Bundle列表。
- 将引用资源的标签传播给图集Bundle,保证按标签构建或发布资源时不会遗漏图集文件。
运行时加载UI预制体时,YooAsset便会根据修正后的资源清单自动加载对应的图集Bundle,不需要额外编写图集加载代码。
注意事项
- 该功能仅支持
ScriptableBuildPipeline,使用旧版构建管线时参数不会生效。 - Unity的
EditorSettings.spritePackerMode不能为Disabled,否则YooAsset不会执行图集依赖补充。 - SpriteAtlas必须被资源收集器收集并参与当前资源包构建,否则构建映射中不存在对应图集,无法自动补充依赖。
- 强烈建议UI面板预制体使用
PackSeparately打包规则。自动修正首先根据Bundle引用关系定位图集,再将依赖补充给引用Bundle内的主资源;如果多个面板打进同一个Bundle,加载其中任意面板时都可能连带加载该Bundle涉及的全部图集。 - 修改图集、Sprite Packer模式或打包规则后,建议执行一次完整构建,避免旧的SBP缓存干扰验证结果。
通用检查事项
无论采用哪种方案,都建议检查以下内容:
- 同一图集内的所有Sprite应采用一致的打包策略,并尽量放入同一个Bundle。将它们分散到多个Bundle可能导致每个Bundle都包含一份图集纹理,造成包体和内存冗余。
- 如果采用本文方案1或方案2显式收集SpriteAtlas,则应通过三方解包插件确认没有产生非预期冗余。
- 在构建报告或资源清单中检查UI资源是否包含对应图集Bundle依赖。
- 使用离线模式或联机模式加载真实Bundle进行测试。编辑器模拟模式直接访问AssetDatabase,可能掩盖Bundle依赖缺失问题。
- 测试UI的首次打开、关闭后重新打开、资源卸载以及版本更新后的加载流程,确认图集句柄和Bundle生命周期正确。