針對(duì)內(nèi)置資源重復(fù)打包冗余的問(wèn)題党瓮,編寫 Addressables Build 腳本將內(nèi)置資源獨(dú)立打包
本人原博:Warl-G's Blog - Unity實(shí)踐—Unity 內(nèi)置資源獨(dú)立打包
什么是內(nèi)置資源
Unity 提供了一些內(nèi)置資源,可在編輯器中找到內(nèi)置資源包unity_builtin_extra
- Windows:
~/Editor/Data/Resources/unity_builtin_extra
- MacOS:
~/Unity.app/Contents/Resources/unity_builtin_extra
unity_builtin_extra
中包含了一系列默認(rèn) Shader 和貼圖等資源驶睦,可在編輯器中直接選擇
由上圖可見內(nèi)置貼圖資源路徑為 Resources/unity_builtin_extra
潦闲,在代碼中可使用AssetDatabase.GetAssetPath
得到同樣的路徑
但無(wú)法通過(guò)該路徑讀取資源,編輯器下可用接口AssetDatabase.GetBuiltinExtraResource
加載內(nèi)置資源,以下為內(nèi)置貼圖路徑
"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
缰儿,但目前沒(méi)有明確用法
為什么要將內(nèi)置資源打包
若制作多個(gè)使用了同樣內(nèi)置資源的 Prefab 且被分到了不同的 Bundle 中,Addressables
的 Default Build Script
是不會(huì)統(tǒng)計(jì)這些引用而單獨(dú)分包的烘跺,會(huì)導(dǎo)致內(nèi)置資源被重復(fù)打進(jìn)不同的 Bundle 中
可通過(guò)創(chuàng)建使用Knob
和UISprite
的 Image Prefab 各兩個(gè)湘纵,并分別打成四個(gè) Bundle
通過(guò)對(duì)四個(gè) Bundle 解包可看到使用相同資源的 Bundle 都有類似如下的內(nèi)容(Knob
或 UISprite
),data 部分就是資源實(shí)際的數(shù)據(jù)內(nèi)容滤淳,被重復(fù)打進(jìn)了兩個(gè)包
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)
...............................
...............................
此時(shí)一個(gè) Bundle 的大小約為 8 KB
若內(nèi)置資源使用范圍比較廣泛且分包較多梧喷,也是有可能造成一定的空間浪費(fèi),因此可重寫Addressables
打包腳本脖咐,將使用的內(nèi)質(zhì)資源獨(dú)立打包
編寫 Addressables 打包腳本
默認(rèn)打包腳本
首先可以查看Addressables
的默認(rèn)打包流程铺敌,在Packages/Addressables/Editor/Build/DataBuilders
下可找到Addressables
提供的幾種預(yù)設(shè)打包模式腳本,其中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);
}
//////////////////////
//////////////////////
}
拋棄代碼中對(duì)資源的預(yù)分析和配置過(guò)程屁擅,如上代碼為開始構(gòu)建的核心部分偿凭,在DoBuild
方法中創(chuàng)建構(gòu)建任務(wù)隊(duì)列,使用ContentPipeline.BuildAssetBundles
開始構(gòu)建打包
RuntimeDataBuildTasks
任務(wù)隊(duì)列中有一個(gè)任務(wù)CreateBuiltInShadersBundle
的功能是找到打包資源中使用到的內(nèi)置 Shader 并獨(dú)立打包派歌,分析其中核心方法
public ReturnCode Run()
{
//獲取所有依賴資源中的內(nèi)置資源弯囊,內(nèi)置資源的GUID都統(tǒng)一為 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();
//從依賴的內(nèi)置資源中找到所有的 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;
}
腳本改寫
由上述代碼可見胶果,默認(rèn)的打包腳本已經(jīng)幫助我們篩選出了所有的內(nèi)置資源匾嘱,只是額外添加了 Shader 單一類型的篩選,因此直接改造CreateBuiltInShadersBundle
即可
- 創(chuàng)建一個(gè)新的實(shí)現(xiàn)
IBUildTask
的類CreateBuiltInBundle
早抠,主要代碼內(nèi)容與CreateBuiltInShadersBundle
保持一致霎烙,構(gòu)造方法記錄兩個(gè) Bundle 名ShaderBundleName 和 BundleName ,一個(gè)用于打包內(nèi)置 Shader蕊连,一個(gè)用于打包其他內(nèi)置資源悬垃,并對(duì)做出如下修改
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 資源分別記錄到兩個(gè)不同的 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;
}
-
創(chuàng)建一個(gè)新的 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);
//////////////////////
//////////////////////
}
修改效果
構(gòu)建 Bundle 后,多出一個(gè)大小為 7 KB 的defaultlocalgroup_unitybuiltin.bundle
载庭,通過(guò)解包可見其中只有之前重復(fù)打包的 Knob 和 UISprite 兩個(gè)內(nèi)置資源趟佃,而之前的四個(gè) Bundle 已不再包含具體的資源數(shù)據(jù),僅包含一段簡(jiǎn)單的引用數(shù)據(jù)昧捷,同時(shí)單個(gè)包體的大小由之前的 8 KB 減小為 4 KB
Builtin 打包前 | Builtin 打包后 | |
---|---|---|
Bundle 數(shù)量 | 4 | 5 |
總 Bundle 大小 | 32 KB | 22 KB |
單個(gè)包體大小 | image
|
image
|
源碼鏈接:GRTools.Addressables · Warl-G