Unity實(shí)踐—Unity 內(nèi)置資源獨(dú)立打包

針對(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 和貼圖等資源,可在編輯器中直接選擇

image
image

由上圖可見(jiàn)內(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 中,AddressablesDefault Build Script是不會(huì)統(tǒng)計(jì)這些引用而單獨(dú)分包的,會(huì)導(dǎo)致內(nèi)置資源被重復(fù)打進(jìn)不同的 Bundle 中

可通過(guò)創(chuàng)建使用KnobUISprite的 Image Prefab 各兩個(gè),并分別打成四個(gè) Bundle

image
image

通過(guò)對(duì)四個(gè) Bundle 解包可看到使用相同資源的 Bundle 都有類似如下的內(nèi)容(KnobUISprite),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

image

若內(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ò)程,如上代碼為開(kāi)始構(gòu)建的核心部分,在DoBuild方法中創(chuàng)建構(gòu)建任務(wù)隊(duì)列,使用ContentPipeline.BuildAssetBundles開(kāi)始構(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;
}

腳本改寫

由上述代碼可見(jiàn),默認(rèn)的打包腳本已經(jīng)幫助我們篩選出了所有的內(nèi)置資源,只是額外添加了 Shader 單一類型的篩選,因此直接改造CreateBuiltInShadersBundle即可

  1. 創(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;
}
  1. 創(chuàng)建一個(gè)新的 Build Script 繼承自BuildScriptBase,所有代碼和BuildScriptPackedMode.cs保持一致,菜單名稱配置可自定義

    RuntimeDataBuildTasksbuildTasks.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ò)解包可見(jiàn)其中只有之前重復(fù)打包的 Knob 和 UISprite 兩個(gè)內(nèi)置資源,而之前的四個(gè) Bundle 已不再包含具體的資源數(shù)據(jù),僅包含一段簡(jiǎn)單的引用數(shù)據(jù),同時(shí)單個(gè)包體的大小由之前的 8 KB 減小為 4 KB

image
Builtin 打包前 Builtin 打包后
Bundle 數(shù)量 4 5
總 Bundle 大小 32 KB 22 KB
單個(gè)包體大小
image
image

源碼鏈接:GRTools.Addressables · Warl-G

參考

Unity內(nèi)置資源如何打包避免冗余 - 知乎 (zhihu.com)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容