跳到主要内容
版本:3.0.x

[资源构建] 图集打包零冗余

什么是散图纹理冗余

图片导入为 Sprite 后,同时包含以下两类数据:

  • Sprite 对象:保存名称、矩形、轴心、边框、网格等信息。
  • Texture2D 对象:保存源图片的纹理像素。

SpriteAtlas 构建后还会生成图集 Texture2D,并记录每个 Sprite 在图集中的矩形、UV 和纹理引用。正常情况下,最终资源包需要保留 Sprite 对象和图集纹理,但不再需要源图片的独立纹理像素。

因此,资源包里出现 Sprite 对象不代表存在纹理冗余。真正的冗余是同一份图像内容同时存在于图集纹理和源 Texture2D 中。

工作原理

SpriteAtlas 构建时,Unity 会生成图集纹理,并记录每个 Sprite 在图集中的位置和渲染信息。运行时无论是由 UI 预制体直接引用 Sprite,还是通过 SpriteAtlas.GetSprite() 获取 Sprite,最终都应使用图集纹理进行渲染。

在正常的 AssetBundle 构建过程中,Unity 会保留 Sprite 对象所需的信息,并剔除已经进入图集的源纹理数据,从而避免源纹理和图集纹理同时存在。

这意味着:

  1. Sprite 对象仍然会参与构建,这是正常且必要的。
  2. 源 Texture2D 的像素数据应从最终产物中剔除。
  3. SpriteAtlas 或其依赖缺失时,Sprite 无法回退到源纹理,通常会显示为白块或透明块。

部分旧版 SBP 在处理 SpriteAtlas 时存在缺陷,可能错误地将源 Texture2D 保留在资源包中,进而造成散图纹理冗余。

推荐配置

1. 使用兼容的 SBP 版本

com.unity.scriptablebuildpipeline 升级到当前 Unity 编辑器支持的稳定版本。不同 Unity 版本所兼容的 SBP 分支并不相同,不要把某个历史版本号当作所有项目的固定推荐版本。

例如,SBP 1.21.22 曾出现过 SpriteAtlas 源纹理冗余问题,相关讨论见:Issue #288

升级 SBP、Unity 编辑器或修改图集设置后,建议清理旧的构建结果并执行一次完整构建,避免缓存影响验证。

2. 正确配置资源收集器

推荐按以下方式收集资源:

  • SpriteAtlas:使用 MainAssetCollector,通常使用 PackSeparately 单独打包。
  • 图集源 Sprite:使用 StaticAssetCollector,同一图集的源 Sprite 应采用一致的打包规则。
  • UI 面板预制体:使用 MainAssetCollector,建议使用 PackSeparately

StaticAssetCollector 会让源 Sprite 作为明确的构建资源参与打包,但不会将其写入 YooAsset 资源清单。因此,源 Sprite 无法通过地址单独加载,仍然可以被 UI 预制体引用,也可以被 SpriteAtlas 用来生成或取得 Sprite。 StaticAssetCollector 本身不会剔除源纹理。源纹理是否被正确剔除由 Unity 和 SBP 的构建过程决定。该收集器的作用是明确源 Sprite 的构建归属,并避免将其作为可寻址主资源写入清单。

3. 正确处理图集依赖

SpriteAtlas 检视面板中的 Include In Build 控制图集是否作为 Sprite 的默认图集,并不只是控制 .spriteatlas 文件能否进入 AssetBundle。

  • 开启时:Sprite 会建立对默认图集的引用。资源管理系统必须保证对应图集 Bundle 作为依赖被加载。
  • 关闭时:Sprite 不保存默认图集引用,运行时需要监听 SpriteAtlasManager.atlasRequested,加载图集后调用事件提供的注册回调完成 Late Binding。

使用 SBP 构建管线并开启 Include In Build 时,可以启用自动图集依赖追踪:

var buildParameters = new ScriptableBuildParameters();
buildParameters.TrackSpriteAtlasDependencies = true;

关于默认图集依赖、Late Binding 和运行时白块问题,请参考图集白块问题

图集加载范例

如果项目通过图集取得 Sprite,可以将 SpriteAtlas 作为可寻址主资源加载:

private AssetHandle _atlasHandle;

public IEnumerator Start()
{
var package = YooAssets.GetPackage("DefaultPackage");
_atlasHandle = package.LoadAssetAsync<SpriteAtlas>(location);
yield return _atlasHandle;

if (_atlasHandle.Status == EOperationStatus.Succeeded)
{
var atlas = _atlasHandle.GetAssetObject<SpriteAtlas>();
_image.sprite = atlas.GetSprite("icon_test");
}
}

private void OnDestroy()
{
_image.sprite = null;
_atlasHandle?.Release();
}

使用图集期间应保留加载句柄,避免图集 Bundle 和纹理被提前卸载。

如果 UI 预制体已经直接引用源 Sprite,则不需要再调用 GetSprite();只要资源清单中存在正确的图集 Bundle 依赖,加载预制体时便会同时加载图集。

验证构建结果

不要仅通过编辑器模拟模式判断是否存在冗余。模拟模式直接访问 AssetDatabase,不能反映真实 AssetBundle 的内容和依赖关系。

建议按以下步骤验证:

  1. 使用目标平台执行一次完整构建。
  2. 在构建报告中确认 SpriteAtlas、源 Sprite 和 UI 预制体被分配到预期的 Bundle。
  3. 使用 AssetStudio 等工具检查 Bundle 内容。
  4. 确认图集生成的 Texture2D 和 Sprite 对象存在。
  5. 确认源图片对应的独立 Texture2D 像素数据没有再次写入 Bundle。
  6. 使用离线模式或联机模式加载真实 Bundle,测试首次打开UI、资源卸载和重新加载。

分析 Texture2D 时应结合对象名称、尺寸和像素数据判断。图集纹理本身也是 Texture2D,资源包中出现 Texture2D 对象并不等同于存在冗余。

常见问题

1. 升级 SBP 后仍然存在冗余

依次检查以下事项:

  1. 是否执行了完整构建,而不是复用了旧的 SBP 缓存。
  2. 源图片是否位于 Resources 目录。该目录中的资源会被 Unity Player 构建系统额外收集。
  3. 同一图集的源 Sprite 是否被分散到多个 Bundle。
  4. 实际使用的 SBP 版本是否与 Packages/manifest.jsonpackages-lock.json 中记录的版本一致。

2. 剔除源纹理后显示白块

这通常不是纹理剔除失败,而是图集 Bundle 没有加载。检查以下事项:

  1. SpriteAtlas 是否参与当前资源包构建。
  2. 开启 Include In Build 时,UI 或 Sprite 所在资源是否记录了图集 Bundle 依赖。
  3. 是否在 Sprite 仍被使用时释放了图集加载句柄。