针对内置资源重复打包冗余的问题,编写 Addressables Build 脚本将内置资源独立打包
本人原博:Warl-G's Blog - Unity实践—Unity 内置资源独立打包
什么是内置资源
Unity 提供了一些内置资源,可在编辑器中找到内置资源包unity_builtin_extra
- Windows:
~/Editor/Data/Resources/unity_builtin_extra
- MacOS:
~/Unity.app/Contents/Resources/unity_builtin_extra
unity_builtin_extra
中包含了一系列默认 Shader 和贴图等资源,可在编辑器中直接选择
由上图可见内置贴图资源路径为 Resources/unity_builtin_extra
,在代码中可使用AssetDatabase.GetAssetPath
得到同样的路径
但无法通过该路径读取资源,编辑器下可用接口AssetDatabase.GetBuiltinExtraResource
加载内置资源,以下为内置贴图路径
"UI/Skin/UISprite.psd"
"UI/Skin/Background.psd"
"UI/Skin/InputFieldBackground.psd"
"UI/Skin/Knob.psd"
"UI/Skin/Checkmark.psd"
"UI/Skin/DropdownArrow.psd"
"UI/Skin/UIMask.psd"
另外还有Runtime
还有接口Resources.GetBuiltinResource
,但目前没有明确用法
为什么要将内置资源打包
若制作多个使用了同样内置资源的 Prefab 且被分到了不同的 Bundle 中,Addressables
的 Default Build Script
是不会统计这些引用而单独分包的,会导致内置资源被重复打进不同的 Bundle 中
可通过创建使用Knob
和UISprite
的 Image Prefab 各两个,并分别打成四个 Bundle
通过对四个 Bundle 解包可看到使用相同资源的 Bundle 都有类似如下的内容(Knob
或 UISprite
),data 部分就是资源实际的数据内容,被重复打进了两个包
ID: 5424255917358561739 (ClassID: 213) Sprite
m_Name "Knob" (string)
m_Rect (Rectf)
x 12 (float)
y 12 (float)
width 40 (float)
height 40 (float)
m_Offset (0 0) (Vector2f)
m_Border (0 0 0 0) (Vector4f)
m_PixelsToUnits 200 (float)
m_Pivot (0.5 0.5) (Vector2f)
m_Extrude 1 (unsigned int)
m_IsPolygon 0 (bool)
m_RenderDataKey (pair)
first 0000000000000000f000000000000000 (GUID)
second 10913 (SInt64)
m_AtlasTags (vector)
size 0 (int)
...............................
...............................
size 184 (int)
data (UInt8) #0: 205 204 204 61 205 204 76 61 0 0 0 0 92 143 194 61 205 204 204 189 0 0 0 0 205
data (UInt8) #25: 204 204 61 123 20 174 189 0 0 0 0 123 20 174 61 205 204 204 61 0 0 0 0 205 204
data (UInt8) #50: 76 189 205 204 204 61 0 0 0 0 10 215 163 189 205 204 204 189 0 0 0 0 92 143 194
data (UInt8) #75: 189 41 92 143 61 0 0 0 0 205 204 204 189 143 194 245 60 0 0 0 0 205 204 204 189
data (UInt8) #100: 174 71 97 189 0 0 0 0 0 0 0 0 62 62 62 143 62 62 62 143 62 62 62 143 62
data (UInt8) #125: 62 62 143 62 62 62 143 73 73 73 137 116 116 116 135 146 146 146 94 255 255 255 0 255 255
data (UInt8) #150: 255 0 146 146 146 50 117 117 117 134 55 55 55 143 62 62 62 143 62 62 62 143 62 62 62
data (UInt8) #175: 143 62 62 62 143 62 62 62 143
m_Bindpose (vector)
size 0 (int)
...............................
...............................
此时一个 Bundle 的大小约为 8 KB
若内置资源使用范围比较广泛且分包较多,也是有可能造成一定的空间浪费,因此可重写Addressables
打包脚本,将使用的内质资源独立打包
编写 Addressables 打包脚本
默认打包脚本
首先可以查看Addressables
的默认打包流程,在Packages/Addressables/Editor/Build/DataBuilders
下可找到Addressables
提供的几种预设打包模式脚本,其中BuildScriptPackedMode.cs
即为Default Build Script
static IList RuntimeDataBuildTasks(string builtinShaderBundleName)
{
var buildTasks = new List();
// Setup
buildTasks.Add(new SwitchToBuildPlatform());
buildTasks.Add(new RebuildSpriteAtlasCache());
// Player Scripts
if (!s_SkipCompilePlayerScripts)
buildTasks.Add(new BuildPlayerScripts());
buildTasks.Add(new PostScriptsCallback());
// Dependency
buildTasks.Add(new CalculateSceneDependencyData());
buildTasks.Add(new CalculateAssetDependencyData());
buildTasks.Add(new AddHashToBundleNameTask());
buildTasks.Add(new StripUnusedSpriteSources());
buildTasks.Add(new CreateBuiltInShadersBundle(builtinShaderBundleName));
buildTasks.Add(new PostDependencyCallback());
// Packing
buildTasks.Add(new GenerateBundlePacking());
buildTasks.Add(new UpdateBundleObjectLayout());
buildTasks.Add(new GenerateBundleCommands());
buildTasks.Add(new GenerateSubAssetPathMaps());
buildTasks.Add(new GenerateBundleMaps());
buildTasks.Add(new PostPackingCallback());
// Writing
buildTasks.Add(new WriteSerializedFiles());
buildTasks.Add(new ArchiveAndCompressBundles());
buildTasks.Add(new GenerateLocationListsTask());
buildTasks.Add(new PostWritingCallback());
return buildTasks;
}
protected virtual TResult DoBuild(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) where TResult : IDataBuilderResult
{
//////////////////////
//////////////////////
var builtinShaderBundleName = Hash128.Compute(GetProjectName()) + "_unitybuiltinshaders.bundle";
var buildTasks = RuntimeDataBuildTasks(builtinShaderBundleName);
buildTasks.Add(extractData);
IBundleBuildResults results;
using (m_Log.ScopedStep(LogLevel.Info, "ContentPipeline.BuildAssetBundles"))
using (new SBPSettingsOverwriterScope(ProjectConfigData.generateBuildLayout)) // build layout generation requires full SBP write results
{
var exitCode = ContentPipeline.BuildAssetBundles(buildParams, new BundleBuildContent(m_AllBundleInputDefs), out results, buildTasks, aaContext, m_Log);
if (exitCode < ReturnCode.Success)
return AddressableAssetBuildResult.CreateResult(null, 0, "SBP Error" + exitCode);
}
//////////////////////
//////////////////////
}
抛弃代码中对资源的预分析和配置过程,如上代码为开始构建的核心部分,在DoBuild
方法中创建构建任务队列,使用ContentPipeline.BuildAssetBundles
开始构建打包
RuntimeDataBuildTasks
任务队列中有一个任务CreateBuiltInShadersBundle
的功能是找到打包资源中使用到的内置 Shader 并独立打包,分析其中核心方法
public ReturnCode Run()
{
//获取所有依赖资源中的内置资源,内置资源的GUID都统一为 0000000000000000f000000000000000
HashSet buildInObjects = new HashSet();
foreach (AssetLoadInfo dependencyInfo in m_DependencyData.AssetInfo.Values)
buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
foreach (SceneDependencyInfo dependencyInfo in m_DependencyData.SceneInfo.Values)
buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
ObjectIdentifier[] usedSet = buildInObjects.ToArray();
Type[] usedTypes = BuildCacheUtility.GetTypeForObjects(usedSet);
if (m_Layout == null)
m_Layout = new BundleExplictObjectLayout();
//从依赖的内置资源中找到所有的 Shader 资源,并记录在指定的 Bundle 名下
Type shader = typeof(Shader);
for (int i = 0; i < usedTypes.Length; i++)
{
if (usedTypes[i] != shader)
continue;
m_Layout.ExplicitObjectLocation.Add(usedSet[i], ShaderBundleName);
}
if (m_Layout.ExplicitObjectLocation.Count == 0)
m_Layout = null;
return ReturnCode.Success;
}
脚本改写
由上述代码可见,默认的打包脚本已经帮助我们筛选出了所有的内置资源,只是额外添加了 Shader 单一类型的筛选,因此直接改造CreateBuiltInShadersBundle
即可
- 创建一个新的实现
IBUildTask
的类CreateBuiltInBundle
,主要代码内容与CreateBuiltInShadersBundle
保持一致,构造方法记录两个 Bundle 名ShaderBundleName 和 BundleName ,一个用于打包内置 Shader,一个用于打包其他内置资源,并对做出如下修改
public ReturnCode Run()
{
HashSet buildInObjects = new HashSet();
foreach (AssetLoadInfo dependencyInfo in m_DependencyData.AssetInfo.Values)
buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
foreach (SceneDependencyInfo dependencyInfo in m_DependencyData.SceneInfo.Values)
buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
ObjectIdentifier[] usedSet = buildInObjects.ToArray();
Type[] usedTypes = ContentBuildInterface.GetTypeForObjects(usedSet);
if (m_Layout == null)
m_Layout = new BundleExplictObjectLayout();
// 将 Shader 和非 Shader 资源分别记录到两个不同的 Bundle 中
Type shader = typeof(Shader);
for (int i = 0; i < usedTypes.Length; i++)
{
m_Layout.ExplicitObjectLocation.Add(usedSet[i], usedTypes[i] == shader ? ShaderBundleName : BundleName);
}
if (m_Layout.ExplicitObjectLocation.Count == 0)
m_Layout = null;
return ReturnCode.Success;
}
-
创建一个新的 Build Script 继承自
BuildScriptBase
,所有代码和BuildScriptPackedMode.cs
保持一致,菜单名称配置可自定义将
RuntimeDataBuildTasks
中buildTasks.Add(new CreateBuiltInShadersBundle(builtinShaderBundleName));
替换为改造后的CreateBuiltInBundle
,并在DoBuild
方法中配置 Bundle 名称
static IList RuntimeDataBuildTasks(string builtinShaderBundleName, string builtinBundleName)
{
var buildTasks = new List();
// Setup
buildTasks.Add(new SwitchToBuildPlatform());
buildTasks.Add(new RebuildSpriteAtlasCache());
// Player Scripts
if (!s_SkipCompilePlayerScripts)
buildTasks.Add(new BuildPlayerScripts());
buildTasks.Add(new PostScriptsCallback());
// Dependency
buildTasks.Add(new CalculateSceneDependencyData());
buildTasks.Add(new CalculateAssetDependencyData());
buildTasks.Add(new AddHashToBundleNameTask());
buildTasks.Add(new StripUnusedSpriteSources());
buildTasks.Add(new CreateBuiltInBundle(builtinShaderBundleName, builtinBundleName));
buildTasks.Add(new PostDependencyCallback());
// Packing
buildTasks.Add(new GenerateBundlePacking());
buildTasks.Add(new UpdateBundleObjectLayout());
buildTasks.Add(new GenerateBundleCommands());
buildTasks.Add(new GenerateSubAssetPathMaps());
buildTasks.Add(new GenerateBundleMaps());
buildTasks.Add(new PostPackingCallback());
// Writing
buildTasks.Add(new WriteSerializedFiles());
buildTasks.Add(new ArchiveAndCompressBundles());
buildTasks.Add(new GenerateLocationListsTask());
buildTasks.Add(new PostWritingCallback());
return buildTasks;
}
protected virtual TResult DoBuild(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) where TResult : IDataBuilderResult
{
//////////////////////
//////////////////////
var builtinBundleName = Hash128.Compute(GetProjectName()) + "_unitybuiltin.bundle";
var builtinShadersBundleName = Hash128.Compute(GetProjectName()) + "_unitybuiltinshaders.bundle";
var buildTasks = RuntimeDataBuildTasks(builtinShadersBundleName, builtinBundleName);
buildTasks.Add(extractData);
//////////////////////
//////////////////////
}
修改效果
构建 Bundle 后,多出一个大小为 7 KB 的defaultlocalgroup_unitybuiltin.bundle
,通过解包可见其中只有之前重复打包的 Knob 和 UISprite 两个内置资源,而之前的四个 Bundle 已不再包含具体的资源数据,仅包含一段简单的引用数据,同时单个包体的大小由之前的 8 KB 减小为 4 KB
Builtin 打包前 | Builtin 打包后 | |
---|---|---|
Bundle 数量 | 4 | 5 |
总 Bundle 大小 | 32 KB | 22 KB |
单个包体大小 |
|
|
源码链接:GRTools.Addressables · Warl-G
参考
Unity内置资源如何打包避免冗余 - 知乎 (zhihu.com)