Unity实践—Unity 内置资源独立打包
针对内置资源重复打包冗余的问题,编写 Addressables Build 脚本将内置资源独立打包本人原博:Warl-G's Blog - Unity实践—Unity 内置资源独立打包
什么是内置资源
Unity 提供了一些内置资源,可在编辑器中找到内置资源包unity_builtin_extra
Windows: ~/Editor/Data/Resources/unity_builtin_extraMacOS:~/Unity.app/Contents/Resources/unity_builtin_extra
unity_builtin_extra 中包含了一系列默认 Shader 和贴图等资源,可在编辑器中直接选择
image
image
由上图可见内置贴图资源路径为 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
image
image
通过对四个 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
image
若内置资源使用范围比较广泛且分包较多,也是有可能造成一定的空间浪费,因此可重写Addressables打包脚本,将使用的内质资源独立打包
编写 Addressables 打包脚本
默认打包脚本
首先可以查看Addressables的默认打包流程,在Packages/Addressables/Editor/Build/DataBuilders下可找到Addressables提供的几种预设打包模式脚本,其中BuildScriptPackedMode.cs即为Default Build Script
static IList<IBuildTask> RuntimeDataBuildTasks(string builtinShaderBundleName){ var buildTasks = new List<IBuildTask>(); // 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<TResult>(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<TResult>(null, 0, "SBP Error" + exitCode); }////////////////////////////////////////////}
抛弃代码中对资源的预分析和配置过程,如上代码为开始构建的核心部分,在DoBuild方法中创建构建任务队列,使用ContentPipeline.BuildAssetBundles开始构建打包
RuntimeDataBuildTasks任务队列中有一个任务CreateBuiltInShadersBundle的功能是找到打包资源中使用到的内置 Shader 并独立打包,分析其中核心方法
public ReturnCode Run(){//获取所有依赖资源中的内置资源,内置资源的GUID都统一为 0000000000000000f000000000000000 HashSet<ObjectIdentifier> buildInObjects = new HashSet<ObjectIdentifier>(); 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 != shader) continue; m_Layout.ExplicitObjectLocation.Add(usedSet, 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<ObjectIdentifier> buildInObjects = new HashSet<ObjectIdentifier>(); 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, usedTypes == 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<IBuildTask> RuntimeDataBuildTasks(string builtinShaderBundleName, string builtinBundleName){ var buildTasks = new List<IBuildTask>(); // 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<TResult>(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
image
Builtin 打包前Builtin 打包后Bundle 数量45总 Bundle 大小32 KB22 KB单个包体大小
image
image
源码链接:GRTools.Addressables · Warl-G
参考
Unity内置资源如何打包避免冗余 - 知乎 (zhihu.com)
页:
[1]