Scriptable Build Pipeline 是什么?能来带什么好处?
自定义 Unity 如何构建应用内容
将原先处于C++的引擎代码移到了C#
加速了AssetBundle的构建时间
改善增量构建处理
对开发者来说具备更多的灵活性
Unity 版本要求:Unity 2018.3 +
术语
Asset
磁盘上的源文件,通常位于项目的 “Assets” 文件夹中。此文件在内部导入到您的资产的可用于游戏的表示形式,其中可以包含多个 Object(SubAsset)。
Object (SubAsset)
单个 Unity 可序列化单元。也称为 SubAsset。导入的 Asset 由一个或多个对象组成。
Includes
从中构造 Asset 的唯一一组 Object。
References
一组需要被 Asset 使用,但不在该 Asset 中的 Object。
快速入门 & BuildIn切换
BuildAssetBundles
BuildIn Pipeline
using System.IO;
using UnityEditor;
public static class BuildAssetBundlesExample
public static bool BuildAssetBundles(string outputPath, bool forceRebuild, bool useChunkBasedCompression, BuildTarget buildTarget)
var options = BuildAssetBundleOptions.None;
if (useChunkBasedCompression)
options |= BuildAssetBundleOptions.ChunkBasedCompression;
if (forceRebuild)
options |= BuildAssetBundleOptions.ForceRebuildAssetBundle;
Directory.CreateDirectory(outputPath);
var manifest = BuildPipeline.BuildAssetBundles(outputPath, options, buildTarget);
return manifest != null;
SBP
CompatibilityBuildPipeline 是 SBP 目前官方使用 SBP 流程适配 BuildIn 流程的适配实现。
SBP 不支持以前支持的所有功能。
using System.IO;
using UnityEditor;
// Added new using
using UnityEditor.Build.Pipeline;
public static class BuildAssetBundlesExample
public static bool BuildAssetBundles(string outputPath, bool forceRebuild, bool useChunkBasedCompression, BuildTarget buildTarget)
var options = BuildAssetBundleOptions.None;
if (useChunkBasedCompression)
options |= BuildAssetBundleOptions.ChunkBasedCompression;
if (forceRebuild)
options |= BuildAssetBundleOptions.ForceRebuildAssetBundle;
Directory.CreateDirectory(outputPath);
// Replaced BuildPipeline.BuildAssetBundles with CompatibilityBuildPipeline.BuildAssetBundles here
var manifest = CompatibilityBuildPipeline.BuildAssetBundles(outputPath, options, buildTarget);
return manifest != null;
注意事项
CompatibilityBuildPipeline.BuildAssetBundles vs BuildPipeline.BuildAssetBundle 的特性与行为的对比。
AssetBundleBuild.addressableNames:
https://docs.unity3d.com/ScriptReference/AssetBundleBuild-addressableNames.html
BuildAssetBundleOptions
AssetBundle SBP 构建流程
01 Setup - 平台环境初始化
SwitchToBuildPlatform
RebuildSpriteAtlasCache
02 Player Scripts - 工程源代码编译
BuildPlayerScripts、PostScriptsCallback
03 Dependency
CalculateSceneDependencyData
CalculateCustomDependencyData (UNITY_2019_3_OR_NEWER)
CalculateAssetDependencyData
StripUnusedSpriteSources
PostDependencyCallback
04 Packing
GenerateBundlePacking
GenerateBundleCommands
GenerateSubAssetPathMaps
GenerateBundleMaps
PostPackingCallback
05Writing
WriteSerializedFiles
ArchiveAndCompressBundles
AppendBundleHash
GenerateLinkXml
PostWritingCallback
06 Generate manifest files
官方 TODO 项
01 Setup
SwitchToBuildPlatform
切换当前平台为构建 AssetBundle 的目标平台对应的 BuildTargetGroup 、 BuildTarget。
执行回调 IPreprocessShaders 、IProcessScene、IProcessSceneWithReport
关键逻辑:
EditorUserBuildSettings.SwitchActiveBuildTarget(m_Parameters.Group, m_Parameters.Target);
RebuildSpriteAtlasCache
针对目标平台重新构建 工程内所有的 SpriteAtlas。
关键逻辑:
SpriteAtlasUtility.PackAllAtlases(m_Parameters.Target);
02 Player Scripts
BuildPlayerScripts
编译目标平台源代码生成 ScriptInfo(TypeDB - 记录了脚本类型 、 属性数据)
TypeDB 为后续的序列化提供正确的字段名称。
关键逻辑:
PlayerBuildInterface.CompilePlayerScripts(m_Parameters.GetScriptCompilationSettings(), m_Parameters.ScriptOutputFolder);
PostScriptsCallback
回调脚本订阅了脚本编译完成节点的所有函数。
上层开发者,注册监听 BuildCallbacks.PostScriptsCallbacks。
关键逻辑:
IScriptsCallback.PostScripts(IBuildParameters parameters, IBuildResults results);
03 Dependency
CalculateSceneDependencyData - 计算所有场景的依赖信息
判断是否使用 Cache 机制;计算场景相关依赖项
关键逻辑:
#if UNITY_2019_3_OR_NEWER
ContentBuildInterface.CalculatePlayerDependenciesForScene(scenePath, settings, usageTags, m_DependencyData.DependencyUsageCache);
#else
ContentBuildInterface.PrepareScene(scenePath, settings, usageTags, m_DependencyData.DependencyUsageCache, outputFolder);
#endif
Cache 仅记录预制相关的依赖。
CalculateCustomDependencyData (UNITY_2019_3_OR_NEWER)
计算未被 AssetDatabase 追踪的自定义 Assets 依赖的 Object 关系;
由 IBundleBuildContent.CustomAssets 自定义 AssetBundle 包含的内容。
关键逻辑:
foreach (CustomContent info in IBundleBuildContent.CustomAssets)
info.Processor(info.Asset, this);
CalculateAssetDependencyData - 计算所有 Asset的依赖数据
基础入参
源代码的 TypeDB
GraphicsSettings
场景的 LightSetting
获取 Asset 的所包含的 Objects
即为该 Asset 引用的 Object;为 该 Asset YAML 序列化中 !u![ClassID] 段所表示的 Object。
通过 TypeDB 获取 Objects 所引用的 Objects
如果是 Sprite 类型资源,则进行 Sprite Atlas 的打包设置
计算 SubAssets
关键逻辑:
var includedObjects = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(asset, input.Target);
var referencedObjects = ContentBuildInterface.GetPlayerDependenciesForObjects(includedObjects, input.Target, input.TypeDB);
ObjectIdentifier[] representations = ContentBuildInterface.GetPlayerAssetRepresentations(asset, target);
// Main Asset always returns at index 0, we only want representations, so check for greater than 1 length
if (representations.IsNullOrEmpty() || representations.Length < 2)
return;
extendedData = new ExtendedAssetData();
extendedData.Representations.AddRange(representations.Skip(1));
StripUnusedSpriteSources - 删除 Assets引用中被Packer的Sprite
筛选出已经被 SpritePacker 打包的Sprite,所归属的 SourceTexture;
删除 Assets 中引用的 Sprite信息;
关键逻辑:
var unusedSources = new HashSet();
var textures = m_SpriteData.ImporterData.Values.Where(x => x.PackedSprite).Select(x => x.SourceTexture);
unusedSources.UnionWith(textures);
// Count refs from assets
var assetRefs = m_DependencyData.AssetInfo.SelectMany(x => x.Value.referencedObjects);
foreach (ObjectIdentifier reference in assetRefs)
unusedSources.Remove(reference);
// Count refs from scenes
var sceneRefs = m_DependencyData.SceneInfo.SelectMany(x => x.Value.referencedObjects);
foreach (ObjectIdentifier reference in sceneRefs)
unusedSources.Remove(reference);
// SetOutputInformation
foreach (var source in unusedSources)
var assetInfo = m_DependencyData.AssetInfo[source.guid];
assetInfo.includedObjects.RemoveAt(0);
ExtendedAssetData extendedData;
if (m_ExtendedAssetData != null && m_ExtendedAssetData.ExtendedData.TryGetValue(source.guid, out extendedData))
extendedData.Representations.Remove(source);
if (extendedData.Representations.Count == 1)
m_ExtendedAssetData.ExtendedData.Remove(source.guid);
PostDependencyCallback
在依赖计算完成后的事件通知
上层开发者,注册监听 BuildCallbacks.PostDependencyCallback
关键逻辑:
IDependencyCallback.PostDependency(IBuildParameters parameters, IDependencyData dependencyData);
04Packing - 资源构建
GenerateBundlePacking - 组装AssetBundle以及计算依赖加载列表
Normal AssetBundle
Streamed Scene AssetBundle
ArchiveFileSystem
校验 Object 是否是 Assets 类型 或 自定义Assets类型 | Scene 类型,创建对应类型的 “容器结构”
可通过 ValidationMethods.ValidAssetFake 接管校验逻辑
计算 Asset 所依赖的加载列表
剔除 Unity默认的内置资源引用 - 处理依赖关系中的重复递归依赖 & 打破环形依赖;(若是 Scene Bundle,则剔除 sharedAssets 的重复引用)
关键逻辑:
foreach (var bundle in m_BuildContent.BundleLayout)
if (ValidAssetBundle(bundle.Value, customAssets))
PackAssetBundle(bundle.Key, bundle.Value, assetToReferences);
else if (ValidationMethods.ValidSceneBundle(bundle.Value))
PackSceneBundle(bundle.Key, bundle.Value, assetToReferences);
// Remove Default Resources and Includes for Assets assigned to Bundles
foreach (ObjectIdentifier reference in references)
if (reference.filePath.Equals(CommonStrings.UnityDefaultResourcePath, StringComparison.OrdinalIgnoreCase))
continue;
if (dependencyData.AssetInfo.TryGetValue(reference.guid, out AssetLoadInfo referenceInfo))
referencedAssets.Add(referenceInfo);
continue;
referencesPruned.Add(reference);
GenerateBundleCommands - 为需要序列化的AssetBundle创建WriteOperation行为
初始化 AssetBundle MainAsset
创建对应的 BundleCommand (CreateAssetBundleCommand、CreateSceneBundleCommand + CreateSceneDataCommand)
AssetBundleCommand :
1.建立加载 Asset的入参与 Object 的映射关系
2.记录 AssetsBundle 中包含的Assets相关信息(依赖的Object hash、序列化组织形式等)并排序,以此确保每次序列化数据的稳定性
SceneBundleCommand :
1.建立加载 Scene 所包含需要序列化的Object与加载的入参的映射关系
2.记录 SceneBundle 依赖的Object 至 preloadObjects
SceneDataCommand :
1.建立加载Scene 所需要附加的Assets 的映射关系
2.记录 SceneBundle 依赖的Object 至 preloadObjects
关键逻辑:
Dictionarystring> assetToMainFile = new Dictionarystring>();
foreach (var pair in m_WriteData.AssetToFiles)
assetToMainFile.Add(pair.Key, pair.Value[0]);
foreach (var bundlePair in m_BuildContent.BundleLayout)
if (ValidAssetBundle(bundlePair.Value, customAssets))
// Use generated internalName here as we could have an empty asset bundle used for raw object storage (See CreateStandardShadersBundle)
var internalName = string.Format(CommonStrings.AssetBundleNameFormat, m_PackingMethod.GenerateInternalFileName(bundlePair.Key));
CreateAssetBundleCommand(bundlePair.Key, internalName, bundlePair.Value);
else if (ValidationMethods.ValidSceneBundle(bundlePair.Value))
var firstScene = bundlePair.Value[0];
CreateSceneBundleCommand(bundlePair.Key, assetToMainFile[firstScene], firstScene, bundlePair.Value, assetToMainFile);
for (int i = 1; i < bundlePair.Value.Count; ++i)
var additionalScene = bundlePair.Value[i];
CreateSceneDataCommand(assetToMainFile[additionalScene], additionalScene);
Unity 官方微信
第一时间了解Unity引擎动向,学习最新开发技巧
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.