【SBP】Unity 打包构建管线原理解析于对比
Unity内置打包流程与SBP(Scriptable Build Pipeline)的对比分析及全流程梳理,了解打包过程到底在干什么,怎么做的。SBP又赋予了什么新的能力。SBP的运行原理机制。SBP打包的示例。
一、发展历史与核心差异
内置打包流程
- 传统方式基于
BuildPipeline.BuildAssetBundles
,通过C++实现黑盒操作,开发者仅能控制输入参数(如压缩格式、目标平台),无法干预内部逻辑。 - 局限性:增量构建效率低,无法定制资产处理逻辑,大型项目构建耗时显著。
SBP的演进
- Unity 2018后引入SBP,将构建逻辑从C++迁移到C#层,开放流水线式架构,支持自定义任务注入。
- 目标:提升构建速度(尤其是增量构建)、增强灵活性,成为Addressables系统的底层基础
二、全流程对比与操作权限
完整可编程化标记图
内置管线流程:
[资源收集] → [依赖分析(黑盒)] → [序列化(固定)] → [Bundle生成] → [压缩(有限)] → [输出]SBP可编程化节点:↓ ↓ ↓ ↓[自定义依赖分析] [自定义序列化] [Bundle布局调整] [动态压缩/加密]↓ ↓ ↓ ↓
[资源收集] → [依赖分析(开放)] → [序列化(可扩展)] → [Bundle生成(可控)] → [后处理(可注入)] → [输出]
SBP通过解耦流水线任务(IBuildTask
)和开放底层接口(如ContentBuildInterface
),将内置管线中90%的黑盒操作转为可编程节点.
内置打包流程
- 资源收集
- 自动扫描项目资产,但依赖关系分析不可见。
- 资产打包-Bundle生成
- 调用
BuildPipeline.BuildAssetBundles
,内部完成依赖解析、压缩、序列化,开发者仅能设置输出路径和压缩格式。
- 调用
- 可执行文件构建
- 通过
BuildSettings
配置场景列表、平台参数,生成exe/apk/ipa等文件。
- 通过
- 可操作项:
- 设置Bundle名称、压缩格式(LZMA/LZ4)。
- 配置构建设备参数(如分辨率、图标)。
- 不可操作项:
- 资产依赖解析逻辑、序列化过程、并行任务调度。
SBP流程
- 流水线初始化
- 通过
ContentPipeline.BuildAssetBundles
启动,允许自定义BuildParameters
和BuildTasks
。
- 通过
- 资产处理阶段
- 依赖分析:开放API(如
CalculateAssetDependencyData
)支持自定义依赖规则。 - 序列化:可插入自定义序列化器(如优化特定资产类型的二进制格式)。
- 依赖分析:开放API(如
- Bundle生成与输出
- 支持多线程分发任务,开发者可控制压缩、加密等后处理逻辑。
- 可操作项:
- 自定义依赖分析、序列化、压缩策略。
- 注入预处理/后处理任务(如资源加密)。
- 不可操作项:
- 底层引擎接口(如物理系统集成)仍由Unity控制
内置打包管线中可编程化改造的详细对比
一、资源收集与依赖分析
- 内置管线
- 黑盒操作:自动扫描项目资产,依赖分析不可见。
- 不可定制:依赖关系由Unity内部算法处理,无法干预。
- SBP的可编程化
- 开放API:通过
CalculateAssetDependencyData
和CalculateCustomDependencyData
任务暴露依赖分析逻辑。 - 自定义规则:开发者可注入
IBuildTask
修改依赖关系(如排除特定资源)。
- 开放API:通过
二、资产序列化与Bundle生成
- 内置管线
- 固定流程:资产序列化为二进制格式(不可定制),Bundle布局由Unity控制。
- SBP的可编程化
- 序列化控制:通过
WriteSerializedFile
任务支持自定义序列化器(如优化二进制结构)。 - Bundle布局:
GenerateBundlePacking
任务允许调整Bundle分块策略(如按类型分组)。
- 序列化控制:通过
三、压缩与加密
- 内置管线
- 有限选项:仅支持LZMA/LZ4压缩,无加密接口。
- SBP的可编程化
- 动态压缩:通过
ArchiveAndCompressBundles
任务支持自定义压缩算法(如Zstd)。 - 加密扩展:可插入后处理任务(如
PostWritingCallback
)实现资源加密。
- 动态压缩:通过
四、增量构建与缓存
- 内置管线
- 效率低下:全量构建,无细粒度缓存机制。
- SBP的可编程化
- 增量处理:通过
ContentBuildInterface
的缓存API实现仅构建变更资源。 - 缓存复用:依赖分析结果和中间文件可持久化复用。
- 增量处理:通过
五、平台特定处理
- 内置管线
- 硬编码逻辑:平台适配(如纹理压缩转换)不可修改。
- SBP的可编程化
- 平台钩子:通过
SwitchToBuildPlatform
任务插入平台预处理逻辑(如Android纹理格式重定向)
- 平台钩子:通过
三、操作示例
内置打包代码
csharp
// 生成AssetBundle
BuildPipeline.BuildAssetBundles("OutputPath",BuildAssetBundleOptions.ChunkBasedCompression,BuildTarget.Android);
SBP自定义任务
csharp
var customParams = new BundleBuildParameters(BuildTarget.Android,CompressionType.LZ4);
var content = new BundleBuildContent(assetEntries);
var tasks = new List<IBuildTask> {new CustomDependencyTask(),// 自定义依赖分析new BundleCompressionTask()// 自定义压缩
};
ContentPipeline.BuildAssetBundles(customParams, content, tasks);
内置打包管线完整示例
-
功能:生成压缩格式为LZ4的AssetBundle,输出到
Assets/AssetBundles
目录 -
限制:无法干预依赖分析、序列化等内部逻辑
-
BuildAssetBundles.cs
using UnityEditor; using System.IO;public class BuiltInPipelineExample {[MenuItem("Tools/Build AssetBundles")]static void BuildAllAssetBundles(){string outputPath = Path.Combine(Application.dataPath, "AssetBundles");if (!Directory.Exists(outputPath))Directory.CreateDirectory(outputPath);// 配置Bundle参数BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression;BuildTarget target = BuildTarget.Android;// 执行打包BuildPipeline.BuildAssetBundles(outputPath, options, target);AssetDatabase.Refresh();} }
SBP定制化模块示例
-
SBP给出了兼容内置打包管线的
CompatibilityBuildPipeline.BuildAssetBundles(outputPath, options, buildTarget);
来快速从内置管线升级到SBP。详细升级指南见https://docs.unity3d.com/Packages/com.unity.scriptablebuildpipeline@1.21/manual/UpgradeGuide.html -
也给出了SBP主要的打包方式
ContentPipeline.BuildAssetBundles(...)
。 -
其中
ContentPipeline.BuildAssetBundles(...)
方式主要依赖于一系列打包任务List和相应的上下文信息(IBuildContext
实现这个接口的各种参数信息).通过这个方法将参数和任务注入进去,方法内会根据传入信息组装完善好上下文,然后根据上下文,依此执行传入的task。BuildTasksRunner.Run(taskList, buildContext);
这里的BuildTaskRunner会在Run时将任务列表和构建的上下文信息传入,然后对每个任务调用数据注入ContextInjector.Inject(context, task);
和ContextInjector.Extract(context, task);
来扫描每个任务中用属性[InjectContext(ContextUsage.**In**)]
标记的可传入或传出的数据,根据标记的属性In、Out、InOut,对每个任务之间的数据进行传递。 -
属性标记脚本:
- com.unity.scriptablebuildpipeline@1.21.25/Editor/Injector/ContextInjector.cs
-
默认任务脚本:
- com.unity.scriptablebuildpipeline@1.21.25/Editor/Shared/DefaultBuildTasks.cs
-
默认
DefaultBuildTasks.ContentFileCompatible()
中给出了一系列打包任务的必要示例流程。public static IList<IBuildTask> ContentFileCompatible() {var buildTasks = new List<IBuildTask>();// SetupbuildTasks.Add(new SwitchToBuildPlatform());buildTasks.Add(new RebuildSpriteAtlasCache());// Player ScriptsbuildTasks.Add(new BuildPlayerScripts());buildTasks.Add(new PostScriptsCallback());// DependencybuildTasks.Add(new CalculateSceneDependencyData());buildTasks.Add(new CalculateCustomDependencyData());buildTasks.Add(new CalculateAssetDependencyData());buildTasks.Add(new StripUnusedSpriteSources());buildTasks.Add(new PostDependencyCallback());// PackingbuildTasks.Add(new ClusterBuildLayout());buildTasks.Add(new PostPackingCallback());// WritingbuildTasks.Add(new WriteSerializedFiles());buildTasks.Add(new ArchiveAndCompressBundles());buildTasks.Add(new GenerateLinkXml());buildTasks.Add(new PostWritingCallback());return buildTasks; }
1. 依赖分析定制
-
原理:通过
IBuildTask
接口修改DependencyData
,实现资源过滤 -
CustomDependencyTask.cs
using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline.Interfaces;public class CustomDependencyTask : IBuildTask {public int Version => 1;[InjectContext(ContextUsage.In)]IBuildContent m_Content;public string[] excludePaths = { "Assets/Resources/Dev/" };public ReturnCode Run(){m_Content.Assets.RemoveAll(asset =>{var path = AssetDatabase.GUIDToAssetPath(asset);return System.Array.Exists(excludePaths, p => path.StartsWith(p));});return ReturnCode.Success;} }
2. 自定义序列化
-
控制点:替换特定类型资产的
Serializer
实现 -
CustomSerializationTask.cs
using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline.Interfaces;public class CustomSerializationTask : IBuildTask {public int Version => 1;[InjectContext(ContextUsage.In)]IBuildContent m_Content;public ReturnCode Run(){// 自定义序列化逻辑:过滤特定类型资源m_Content.Assets.RemoveAll(asset => {var path = AssetDatabase.GUIDToAssetPath(asset);return path.EndsWith(".tmp") || path.Contains("/Temp/");});return ReturnCode.Success;} }
3. Bundle生成调整
-
逻辑:调整Bundle命名规则:添加构建时间戳
-
BundleAdjustmentTask.cs
using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline.Interfaces;public class BundleAdjustmentTask : IBuildTask {public int Version => 1;[InjectContext(ContextUsage.In)]IBundleLayout m_Layout;public ReturnCode Run(){// 调整Bundle命名规则:添加构建时间戳foreach (var bundle in m_Layout.BundleToAssets.Keys.ToList()){var newName = $"{bundle}_{DateTime.Now:yyyyMMdd}";m_Layout.BundleToAssets[newName] = m_Layout.BundleToAssets[bundle];m_Layout.BundleToAssets.Remove(bundle);}return ReturnCode.Success;} }
4. 后处理动态加密
-
触发时机:在
IPostBuildTask
中处理已生成的Bundle文件 -
PostProcessEncrypt.cs
using System.IO; using UnityEditor.Build.Pipeline.Interfaces;public class PostProcessEncryptionTask : IBuildTask {public int Version => 1;[InjectContext(ContextUsage.In)]IBuildResults m_Results;public ReturnCode Run(){// 对生成的Bundle进行XOR简单加密foreach (var bundle in m_Results.BundleInfos){var bytes = File.ReadAllBytes(bundle.Value.FileName);for (int i = 0; i < bytes.Length; i++)bytes[i] ^= 0xAA;File.WriteAllBytes(bundle.Value.FileName, bytes);}return ReturnCode.Success;} }
5. 完整SBP流水线整合
-
增量构建:通过
UseCache
复用历史构建数据 -
模块组合:按需注入自定义任务链
-
SBPPipeline.cs
using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline; using System.Collections.Generic;public static class SBPPipeline {public static void Build(){var customParams = new BundleBuildParameters(BuildTarget.Android, BuildTargetGroup.Android, "OutputPath");var content = new BundleBuildContent(AssetDatabase.GetAllAssetPaths());var tasks = new List<IBuildTask> {new CustomDependencyTask(), // 依赖分析new CustomSerializationTask(), // 序列化new BundleAdjustmentTask(), // Bundle布局new ArchiveAndCompressBundlesTask(), // 默认压缩new PostProcessEncryptionTask() // 加密};// 增量构建:启用缓存params.UseCache = true;ContentPipeline.BuildAssetBundles(customParams, content, out _, tasks);} }
差异总对比
模块 | 内置管线 | SBP |
---|---|---|
依赖分析 | 不可定制 | 通过IBuildTask 重写逻辑 |
Bundle布局 | 固定分组规则 | 动态分块策略 |
后处理 | 仅支持基础压缩 | 支持加密、签名等扩展 |
增量构建 | 全量重建 | 缓存复用提升效率 |
- SBP通过解耦构建步骤为可编程任务(
IBuildTask
),实现传统黑盒操作的全面开放
四、实现原理对比
- 内置流程:通过C++硬编码实现资产处理,逻辑不可扩展。
- SBP:基于C#反射和接口抽象,将构建步骤拆分为可组合的
IBuildTask
,通过依赖注入实现扩展
五、迁移建议
- 适用场景:大型项目需增量构建优化时推荐SBP,小型项目可使用内置流程。
- 注意事项:SBP需适配Addressables系统,旧项目需重构构建脚本
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)