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

由上圖可見內(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ò)程屁擅,如上代碼為開始構(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即可

  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ò)解包可見其中只有之前重復(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)系作者
  • 序言:七十年代末闲昭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子靡挥,更是在濱河造成了極大的恐慌序矩,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跋破,死亡現(xiàn)場(chǎng)離奇詭異簸淀,居然都是意外死亡瓶蝴,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門租幕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舷手,“玉大人,你說(shuō)我怎么就攤上這事劲绪∧锌撸” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵贾富,是天一觀的道長(zhǎng)歉眷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)颤枪,這世上最難降的妖魔是什么汗捡? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮畏纲,結(jié)果婚禮上扇住,老公的妹妹穿的比我還像新娘。我一直安慰自己盗胀,他們只是感情好台囱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著读整,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咱娶。 梳的紋絲不亂的頭發(fā)上米间,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音膘侮,去河邊找鬼屈糊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛琼了,可吹牛的內(nèi)容都是我干的逻锐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雕薪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昧诱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起所袁,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盏档,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后燥爷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜈亩,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懦窘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稚配。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畅涂。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖道川,靈堂內(nèi)的尸體忽然破棺而出午衰,到底是詐尸還是另有隱情,我是刑警寧澤愤惰,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布苇经,位于F島的核電站,受9級(jí)特大地震影響宦言,放射性物質(zhì)發(fā)生泄漏扇单。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一奠旺、第九天 我趴在偏房一處隱蔽的房頂上張望蜘澜。 院中可真熱鬧,春花似錦响疚、人聲如沸鄙信。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)装诡。三九已至,卻和暖如春践盼,著一層夾襖步出監(jiān)牢的瞬間鸦采,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工咕幻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渔伯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓肄程,卻偏偏與公主長(zhǎng)得像锣吼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蓝厌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容