Unity打包AssetBundle自动分析资源依赖关系(包括UGUI图集打包)

https://blog.csdn.net/u012740992/article/details/79371986

 

怎么分析资源的依赖关系呢,并设置AssetBundleName呢?
我们检测资源之间的依赖关系,遍历每一个有引用的资源进行分析,对于非UGUI的图集资源(UGUI图集上面说了),如果此资源A被其他地方资源B引用仅仅1次,那么就将此资源A的AssetBundleName置空不设置,这样打包时,此资源就会自动被和资源B打到一起合成一个AssetBundle包,如此减少打包的碎片。如果资源A被引用超过2次及以上,那么就为资源独立设置AssetBundleName,从而避免被重复打包到几个依赖它的资源包。
这里所说的资源被依赖超过2次就独立打包,如果觉得碎片化太严重,产生太多AssetBundle文件,也可以设置成n(n>=1)次才独立打包,开心就好。
————————————————
版权声明:本文为CSDN博主「漫漫之间n」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012740992/article/details/79371986

 

前提说明:

    本文只是针对Unity5.x及以上版本打包AssetBundle。Unity5.x虽然说打包时会处理好资源的依赖关系,但前提依然要我们设置好目标资源的AssetBundleName,如果设置资源AssetBundleName时忽略了资源之间的依赖关系,那么打包AssetBundle时,依然会产生重复打包的资源,所以我写了一套脚本来自动分析资源的依赖关系,并根据资源的依赖关系来设置AssetBundleName,从而避免不必要的资源重复打包。
    我也了解过,
    Unity的AssetStore也有对应的图形界面工具,查看和处理打包AssetBundle时遇到的资源重复打包问题,但觉得图形化界面工具还得人工查看和修改,如果项目大了,一千多个ab甚至几千个ab,那么每次更新资源时去查看哪里存在依赖那也挺累得,效率不高,并且手动修正难说说没有看漏改漏。所以,不如来套脚本搞它一把,提高工作效率和质量。

我用的是Unity5.3.6。

前提有点特殊的是Unity的UGUI图集打包,Unity的官网有介绍如何将UGUI图集正确打成AssetBundle(但我现在翻回去找不到链接了= =。),简单说下:
首先,UGUI是有图集的。
我的UGUI图集打包AssetBundle方式是,每张图集打成一个AssetBundle,所以我这里保证了每张UI图片的PackingTag和自己的AssetBundleName一样。原因,如果AssetBundleName不一样,那么这张UI图片后面所打成的AssetBundle包将会扯带了整张图集的内容,这张图集别的UI图片也在用啊,却被无辜包含进了这个ab包。所以通过保证UI图片的PackingTag==AssetBundleName来保证每张图集只存在一个AssetBundle,保证图集不被重复打包。

怎么分析资源的依赖关系呢,并设置AssetBundleName呢?
我们检测资源之间的依赖关系,遍历每一个有引用的资源进行分析,对于非UGUI的图集资源(UGUI图集上面说了),如果此资源A被其他地方资源B引用仅仅1次,那么就将此资源A的AssetBundleName置空不设置,这样打包时,此资源就会自动被和资源B打到一起合成一个AssetBundle包,如此减少打包的碎片。如果资源A被引用超过2次及以上,那么就为资源独立设置AssetBundleName,从而避免被重复打包到几个依赖它的资源包。
这里所说的资源被依赖超过2次就独立打包,如果觉得碎片化太严重,产生太多AssetBundle文件,也可以设置成n(n>=1)次才独立打包,开心就好。

资源依赖处理的代码构建思路:
这里写图片描述
其实资源之间的依赖关系,就是一个树形依赖关系,只要能构建出资源之间的依赖树,那么就能了解到某个资源被多少颗树引用,也就是被多少个资源引用,从而对症下药,自然能合理设置AssetBundleName。

然后有代码LoaderManager.cs和ABInfo.cs。
注意:一定要放在Editor文件夹下!!

LoaderManager.cs

using UnityEngine;
using System.Collections;
using System.IO;
using UnityEditor;
using System.Collections.Generic;

public class LoaderManager {

    static Dictionary<string, AssetInfo> assetInfoDict = new Dictionary<string, AssetInfo>();

    private static string curRootAsset = string.Empty;
    private static float curProgress = 0f;


    [MenuItem("AssetBundleMgr / SetAssetbundleName")]
    static void SetABNames()
    {
        string path = GetSelectedAssetPath();
        if (path == null)
        {
            Debug.LogWarning("请先选择目标文件夹");
            return;
        }
        LoaderManager.GetAllAssets(path);

    }
    [MenuItem("AssetBundleMgr / ClearAllAssetbundelname")]
    static void CleaarAllABNames()
    {
        string[] abnames = AssetDatabase.GetAllAssetBundleNames();
        foreach (var n in abnames)
        {
            AssetDatabase.RemoveAssetBundleName(n, true);
        }
    }

    public static void GetAllAssets(string rootDir) {
        assetInfoDict.Clear();

        DirectoryInfo dirinfo = new DirectoryInfo(rootDir);
        FileInfo[] fs = dirinfo.GetFiles("*.*", SearchOption.AllDirectories);
        int ind = 0;
        foreach (var f in fs)
        {
            curProgress = (float)ind / (float)fs.Length;
            curRootAsset = "正在分析依赖:"+f.Name;
            EditorUtility.DisplayProgressBar(curRootAsset, curRootAsset, curProgress);
            ind++;
            int index = f.FullName.IndexOf("Assets");
            if (index != -1)
            {
                string assetPath = f.FullName.Substring(index);
                Object asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
                string upath = AssetDatabase.GetAssetPath(asset);
                if (assetInfoDict.ContainsKey(assetPath) == false
                    && assetPath.StartsWith("Assets")
                    && !(asset is MonoScript)
                    && !(asset is LightingDataAsset)
                    && asset != null
                    ) {
                    AssetInfo info = new AssetInfo(upath, true);
                    //标记一下是文件夹下根资源
                    CreateDeps(info);
                }
                EditorUtility.UnloadUnusedAssetsImmediate();
            }
            EditorUtility.UnloadUnusedAssetsImmediate();
        }
        EditorUtility.ClearProgressBar();

        int setIndex = 0;
        foreach (KeyValuePair<string, AssetInfo> kv in assetInfoDict) {
            EditorUtility.DisplayProgressBar("正在设置ABName", kv.Key, (float)setIndex/(float)assetInfoDict.Count);
            setIndex++;
            AssetInfo a = kv.Value;
            a.SetAssetBundleName(2);
        }
        EditorUtility.ClearProgressBar();
        EditorUtility.UnloadUnusedAssetsImmediate();
        AssetDatabase.SaveAssets();
    }
    /// <summary>
    /// 递归分析每个所被依赖到的资源
    /// </summary>
    /// <param name="self"></param>
    /// <param name="parent"></param>
    static void CreateDeps(AssetInfo self, AssetInfo parent = null) {
        if (self.HasParent(parent))
            return;
        if (assetInfoDict.ContainsKey(self.assetPath) == false) {
            assetInfoDict.Add(self.assetPath, self);
        }
        self.AddParent(parent);

        Object[] deps = EditorUtility.CollectDependencies(new Object[] { self.GetAsset() });
        for (int i = 0; i < deps.Length; i++) {
            Object o = deps[i];
            if (o is MonoScript || o is LightingDataAsset)
                continue;
            string path = AssetDatabase.GetAssetPath(o);
            if (path == self.assetPath)
                continue;
            if (path.StartsWith("Assets") == false)
                continue;
            AssetInfo info = null;
            if (assetInfoDict.ContainsKey(path))
            {
                info = assetInfoDict[path];
            }
            else {
                info = new AssetInfo(path);
                assetInfoDict.Add(path, info);
            }
            EditorUtility.DisplayProgressBar(curRootAsset, path, curProgress);
            CreateDeps(info, self);
        }
        EditorUtility.UnloadUnusedAssetsImmediate();
    }

    static string GetSelectedAssetPath()
    {
        var selected = Selection.activeObject;
        if (selected == null)
        {
            return null;
        }
        Debug.Log(selected.GetType());
        if (selected is DefaultAsset)
        {
            string path = AssetDatabase.GetAssetPath(selected);
            Debug.Log("选中路径: " + path);
            return path;
        }
        else
        {
            return null;
        }
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140

ABInfo.cs

using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;

public class AssetInfo
{

    //是不是被打包文件夹下的直接资源
    private bool isRootAsset = false;

    public string assetPath { get; private set; }

    private HashSet<AssetInfo> childSet = new HashSet<AssetInfo>();
    private HashSet<AssetInfo> parentSet = new HashSet<AssetInfo>();

    public AssetInfo(string assetPath, bool isRootAsset = false)
    {
        this.assetPath = assetPath;
    }
    public Object GetAsset()
    {
        Object asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
        return asset;
    }
    /// <summary>
    /// 从这里开始分析构建资源依赖树
    /// </summary>
    /// <param name="parent"></param>
    public void AddParent(AssetInfo parent)
    {
        if (parent == this || IsParentEarlyDep(parent) || parent == null)
            return;

        parentSet.Add(parent);
        parent.AddChild(this);

        parent.RemoveRepeatChildDep(this);
        RemoveRepeatParentDep(parent);
    }
    /// <summary>
    /// 清除我父节点对我子节点的重复引用,保证树形结构
    /// </summary>
    /// <param name="targetParent"></param>
    private void RemoveRepeatChildDep(AssetInfo targetChild)
    {

        List<AssetInfo> infolist = new List<AssetInfo>(parentSet);
        for (int i = 0; i < infolist.Count; i++)
        {
            AssetInfo pinfo = infolist[i];
            pinfo.RemoveChild(targetChild);
            pinfo.RemoveRepeatChildDep(targetChild);
        }
    }
    /// <summary>
    /// 清除我子节点被我父节点的重复引用,保证树形结构
    /// </summary>
    /// <param name="targetChild"></param>
    private void RemoveRepeatParentDep(AssetInfo targetParent)
    {

        List<AssetInfo> infolist = new List<AssetInfo>(childSet);
        for (int i = 0; i < infolist.Count; i++)
        {
            AssetInfo cinfo = infolist[i];
            cinfo.RemoveParent(targetParent);
            cinfo.RemoveRepeatParentDep(targetParent);
        }
    }

    private void RemoveChild(AssetInfo targetChild)
    {
        childSet.Remove(targetChild);
        targetChild.parentSet.Remove(this);
    }
    private void RemoveParent(AssetInfo parent)
    {
        parent.childSet.Remove(this);
        parentSet.Remove(parent);
    }


    private void AddChild(AssetInfo child)
    {
        childSet.Add(child);
    }

    /// <summary>
    /// 如果父节点早已当此父节点为父节点
    /// </summary>
    /// <param name="targetParent"></param>
    /// <returns></returns>
    private bool IsParentEarlyDep(AssetInfo targetParent)
    {
        if (parentSet.Contains(targetParent))
        {
            return true;
        }
        var e = parentSet.GetEnumerator();
        while (e.MoveNext())
        {
            if (e.Current.IsParentEarlyDep(targetParent))
            {
                return true;
            }
        }
        return false;
    }
    public bool HasParent(AssetInfo p)
    {
        if (parentSet.Contains(p))
            return true;
        return false;
    }
    /// <summary>
    /// 打包碎片粒度
    /// </summary>
    /// <param name="pieceThreshold"></param>
    public void SetAssetBundleName(int pieceThreshold)
    {
        AssetImporter ai = AssetImporter.GetAtPath(this.assetPath);
        //针对UGUI图集的处理,图集以文件夹为单位打包ab
        if (ai is TextureImporter)
        {
            TextureImporter tai = ai as TextureImporter;

            string filePath = System.IO.Path.GetDirectoryName(this.assetPath);
            tai.spritePackingTag = filePath.ToLower().Replace("\\", "_").Replace(".png",string.Empty).Replace(".jpg", string.Empty).Replace(" ", string.Empty);
            
            //AssetBundleName和spritePackingTag保持一致
            tai.SetAssetBundleNameAndVariant(tai.spritePackingTag + ".ab", null);
            Debug.Log("<color=#2E8A00>" + "设置ab,Image资源: " + this.assetPath + "</color>");
        }
        else
        {
            string abname = this.assetPath.Replace("/", "_") + ".ab";
            //不是图集,而且大于阀值
            if (this.parentSet.Count >= pieceThreshold)
            {
                ai.SetAssetBundleNameAndVariant(abname, string.Empty);
                Debug.Log("<color=#6501AB>" + "设置ab,有多个引用: " + this.assetPath+"</color>");
            }
            //根节点
            else if (this.parentSet.Count == 0 || this.isRootAsset)
            {
                ai.SetAssetBundleNameAndVariant(abname, string.Empty);
                Debug.Log("<color=#025082>" + "设置ab,根资源ab: " + this.assetPath + "</color>");
            }
            else
            {
                //其余的子资源
                ai.SetAssetBundleNameAndVariant(string.Empty, string.Empty);
                Debug.Log("<color=#DBAF00>" + "清除ab, 仅有1个引用: " + this.assetPath + "</color>");
            }
        }
    }
}


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160

用法:
1,选择Unity内我们所要打包的资源所在的文件夹;
2,菜单栏“AssetBundleMgr->SetAssetbundleName”,完成!看一下资源的AssetBundleName。

如:我选择了Prefabs文件夹,然后菜单栏“AssetBundleMgr->SetAssetbundleName”,资源都被正确设置了AssetBundleName。


————————————————
版权声明:本文为CSDN博主「漫漫之间n」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012740992/article/details/79371986

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值