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

前提说明:

  • 本文只是针对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)次才独立打包,开心就好。

资源依赖处理的代码构建思路:
Unity打包AssetBundle自动分析资源依赖关系(包括UGUI图集打包)_第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 assetInfoDict = new Dictionary();

    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 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();
    }
    /// 
    /// 递归分析每个所被依赖到的资源
    /// 
    /// 
    /// 
    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;
        }
    }
}

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 childSet = new HashSet();
    private HashSet parentSet = new HashSet();

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

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

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

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

        List infolist = new List(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);
    }

    /// 
    /// 如果父节点早已当此父节点为父节点
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 打包碎片粒度
    /// 
    /// 
    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("" + "设置ab,Image资源: " + this.assetPath + "");
        }
        else
        {
            string abname = this.assetPath.Replace("/", "_") + ".ab";
            //不是图集,而且大于阀值
            if (this.parentSet.Count >= pieceThreshold)
            {
                ai.SetAssetBundleNameAndVariant(abname, string.Empty);
                Debug.Log("" + "设置ab,有多个引用: " + this.assetPath+"");
            }
            //根节点
            else if (this.parentSet.Count == 0 || this.isRootAsset)
            {
                ai.SetAssetBundleNameAndVariant(abname, string.Empty);
                Debug.Log("" + "设置ab,根资源ab: " + this.assetPath + "");
            }
            else
            {
                //其余的子资源
                ai.SetAssetBundleNameAndVariant(string.Empty, string.Empty);
                Debug.Log("" + "清除ab, 仅有1个引用: " + this.assetPath + "");
            }
        }
    }
}


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

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

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

你可能感兴趣的:(编程语言类,Unity开发,编辑器类)