Unity AssetBundle資源打包


unity資源打包可以分為一下幾個過程:

1、先把圖片批量生成圖集

2、把其他路徑下的資源,比如邏輯lua腳本拷貝到資源文件夾下,方便后面資源打包

3、自動給資源文件夾下所有資源設置AssetBundle的Name和variant

4、利用unity提供的api進行資源打包

5、創建XML資源列表

一、圖集打包

圖集打包使用的是unity新版的SpriteAtlas,舍棄了舊版的SpritePackage,主要是舊版的圖集無法從圖集獲取圖片資源

    public static string[] textureExtensions = new[] {".png"};


    [MenuItem("BuildAssetBundle/ScanSpriteAtlas")]
    public static void ScanSpriteAtlas()
    {
        var path = Path.Combine(Application.dataPath, "ResourcesAssets");
        var dirInfo = new DirectoryInfo(path);
        ScanSprites(dirInfo);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    public static void ScanSprites(DirectoryInfo dirInfo)
    {
        var dirArr = dirInfo.GetDirectories();
        if (dirArr.Length > 0)
        {
            for (int i = 0; i < dirArr.Length; i++)
            {
                ScanSprites(dirArr[i]);
            }
        }
        var fileArr = dirInfo.GetFiles();
        if (fileArr.Length > 0)
        {
            var dirPath = dirInfo.FullName.Replace("\\", "/");
            var subDirPath = dirPath.Replace(Application.dataPath, "Assets");
            for (int i = 0; i < fileArr.Length; i++)
            {
                var label = "";
                var variant = "";
                if (textureExtensions.Contains(fileArr[i].Extension))
                {
                    var texImporter = AssetImporter.GetAtPath(Path.Combine(subDirPath, fileArr[i].Name)) as TextureImporter;
                    if (texImporter.textureType == TextureImporterType.Sprite)
                    {
                        GetSpriteAtlas(dirInfo);
                        break;
                    }
                }
            }
        }
    }

    public static SpriteAtlas GetSpriteAtlas(DirectoryInfo dirInfo)
    {
        //文件夾路徑
        var assetDataPath = dirInfo.FullName.Replace("\\", "/").Replace(Application.dataPath, "Assets");
        //圖集名稱
        var atlasName = assetDataPath.Replace("Assets/ResourcesAssets/", "").Replace("/", "-");
        //圖集路徑
        var assetPath = $"{assetDataPath}/{atlasName}.spriteatlas";
        //加載圖集
        var sa = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(assetPath);
        if (sa == null)
        {
            Debug.Log($"Creat SpriteAtlas at path : {assetDataPath}");
            sa = new SpriteAtlas();
            AssetDatabase.CreateAsset(sa, assetPath);
            Object texture = AssetDatabase.LoadMainAssetAtPath(assetDataPath);
            SpriteAtlasPackingSettings packset = new SpriteAtlasPackingSettings()
            {
                blockOffset = 1,
                enableRotation = false,
                enableTightPacking = false,
                padding = 4
            };
            sa.SetPackingSettings(packset);
            SpriteAtlasTextureSettings texSet = new SpriteAtlasTextureSettings()
            {
                readable = true,
                filterMode = FilterMode.Bilinear,
                sRGB = true,
                generateMipMaps = true
            };
            sa.SetTextureSettings(texSet);
            sa.SetIncludeInBuild(false);
            sa.SetIsVariant(false);
            sa.Add(new Object[] { texture });
        }
        return sa;
    }

二、資源拷貝

資源拷貝也是為了加密做准備,在把我們的lua腳本拷貝到資源文件夾下的時候可以進一步加密,把我們的lua腳本變為加密之后的腳本,可以防止軟件被反編譯之后拿到我們的lua腳本

    public static void CopyLuaToSources(bool encode)
    {
        string outPath = Application.dataPath + "/ResourcesAssets/Lua";
        try
        {
            if (!Directory.Exists(outPath))
            {
                Directory.CreateDirectory(outPath);
            }
        }
        catch (System.UnauthorizedAccessException ex)
        {
            if (Debug.unityLogger.logEnabled)
            {
                Debug.Log(ex.Message);
            }
        }
        var hasErr = false;
        var strErr = new StringBuilder();
        CopyLuaBytesFiles(ref hasErr, Application.dataPath+"/Lua/", outPath, encode, strErr);
        if (hasErr)
        {
            Exception ex = new Exception(strErr.ToString());
            throw (ex);
        }
    }

    public static void CopyLuaBytesFiles(ref bool hasErr, string targetPath, string outPath, bool isencode = false, StringBuilder strErr = null)
    {
        if (!Directory.Exists(targetPath))
        {
            return;
        }
        string[] files = Directory.GetFiles(targetPath, "*.lua", SearchOption.AllDirectories);
        int len = targetPath.Length;
        if (targetPath[len - 1] == '/' || targetPath[len - 1] == '\\')
        {
            --len;
        }
        LuaHashComparer hashComparer = new LuaHashComparer();
        for (int i = 0; i < files.Length; i++)
        {
            //獲取Assets/Lua之后的lua文件路徑
            string str = files[i].Remove(0, len);
            //lua文件copy到的目標文件
            string desc = $"{outPath}{str}.bytes";
            string dir = Path.GetDirectoryName(desc);
            Directory.CreateDirectory(dir);
            string src = files[i];
            if (isencode)
            {
                //這是帶加密的步驟,先放着
            }
            else
            {
                //正常的copy,不帶加密的流程
                if (hashComparer.IsChanged(src, src.Replace(targetPath, "").ToLower()))
                {
                    var _outPath = desc.ToLower();
                    if (File.Exists(_outPath))
                    {
                        File.Delete(_outPath);
                    }
                    File.Copy(src, _outPath, true);
                    File.SetAttributes(_outPath, FileAttributes.Normal);
                }
            }
        }
        EditorUtility.ClearProgressBar();
        AssetDatabase.Refresh();
    }

三、自動設置AssetBundle的Name和Variant

這一步其實做的就是資源分類,每個資源都有其對應的資源路徑,包名其實就是它的路徑

public static void AutoSetBundleName(bool clear)
    {
        string path = Path.Combine(Application.dataPath, "ResourcesAssets");
        DirectoryInfo info = new DirectoryInfo(path);
        ScanDir(info, clear);
    }

    public static string[] IgnoreExtensions = new[] {".meta", ".DS_Store"};
    public static string[] VideoExtensions = new[] { ".mp4"};
    public static List<FileInfo> videoFileList = new List<FileInfo>();
    public static void ScanDir(DirectoryInfo info, bool clear, bool smartSet = true)
    {
        DirectoryInfo[] dirArr = info.GetDirectories();
        //遞歸遍歷所有文件夾
        for (int i = 0; i < dirArr.Length; i++)
        {
            ScanDir(dirArr[i], clear);
            //顯示進度條
            EditorUtility.DisplayProgressBar("掃描資源文件夾", $"資源文件夾名:{dirArr[i].Name}", i*1.0f/dirArr.Length);
        }
        FileInfo[] fileArr = info.GetFiles();
        if (fileArr.Length > 0)
        {
            string dirPath = info.FullName.Replace("\\", "/");
            string subDirPath = dirPath.Replace(Application.dataPath, "Assets");
            string assetBundleLabel = subDirPath.Replace("Assets/ResourcesAssets/", "").ToLower();
            //根據文件夾添加標簽,自動打包
            for (int i = 0; i < fileArr.Length; i++)
            {
                if (IgnoreExtensions.Contains(fileArr[i].Extension))
                {
                    continue;
                }
                string label = "None";
                string variant = "None";
                if (!clear)
                {
                    if (textureExtensions.Contains(fileArr[i].Extension))
                    {
                        //根據路徑獲取指定資源
                        var texImporter = AssetImporter.GetAtPath(Path.Combine(subDirPath, fileArr[i].Name)) as TextureImporter;
                        if (texImporter.textureType == TextureImporterType.Sprite)
                        {
                            continue;
                        }
                    }
                    label = assetBundleLabel;
                    variant = "variant";
                }
                string assetPath = $"{subDirPath}/{fileArr[i].Name}";
                AssetImporter asset = AssetImporter.GetAtPath(assetPath);
                Debug.Log($"FileName:{fileArr[i].Name},Extension:{fileArr[i].Extension}");
                if (asset.assetBundleName != label || !smartSet)
                {
                    try
                    {
                        asset.SetAssetBundleNameAndVariant(label, variant);
                    }
                    catch (Exception ex)
                    {
                        Debug.Log(ex.Message);
                        EditorUtility.ClearProgressBar();
                    }
                }
                EditorUtility.DisplayProgressBar("設置AB包名", $"包名:{label}", i*1.0f/fileArr.Length);
            }
        }
        EditorUtility.ClearProgressBar();
    }

四、資源打包

使用unity提供的api進行資源打包,資源包構建選項為BuildAssetBundleOptions.ChunkBasedCompression

BuildAssetBundleOptions.ChunkBasedCompression采用LZ4的壓縮格式,相比於LZMA而言文件體積更大,但是不要求在使用之前整個bundle都被解壓。
LZ4使用chunk based 算法,這就運行文件以chunk或者piece的方式加載,只解壓一個chunk文件,而無需解壓bundle中其余不相關的chunk

public static void BuildAllAssetBundleToPersistent(BuildAssetBundleOptions bundleOptions)
    {
        string packPath = OriginPath;
        FileHelper.CreatDirectory(packPath);
        if (packPath.Length <= 0)
            return;
        Debug.Log($"OutPath:{packPath}");
        BuildPipeline.BuildAssetBundles(packPath, bundleOptions, GetCurrBuildTarget());
        AssetDatabase.Refresh();
    }

五、創建資源列表XML

創建資源列表,用xml文件來存儲,存儲資源的名字、key值(即資源路徑)和資源對呀ab包的名字(即MD5),用來進行資源加載的時候進行索引

 public static void CreatVersionResXML()
    {
        string packagePath = hotfixPath;
        if (packagePath.Length <= 0 || !Directory.Exists(packagePath))
        {
            return;
        }
        List<string> fileList = new List<string>();
        fileList = FileHelper.GetFiles(OriginPath, fileList, ".variant");
        XmlDocument xml = new XmlDocument();
        xml.AppendChild(xml.CreateXmlDeclaration("1.0", "UTF-8", null));
        //生成根節點
        xml.AppendChild(xml.CreateElement("Root"));
        //生成大版本號
        XmlNode root = xml.SelectSingleNode("Root");

        //版本號賦值
        XmlElement verNode = xml.CreateElement("version");
        verNode.InnerText = $"{Application.version}.{GitCommitDataId}";
        root.AppendChild(verNode);
        //是否加密
        XmlElement encNode = xml.CreateElement("encryption");
        encNode.InnerText = "true";
        root.AppendChild(encNode);

        //添加資源加點
        XmlElement resNode = xml.CreateElement("res");
        root.AppendChild(resNode);

        string copyToPath = hotfixPath;
        FileHelper.RebuildDic(copyToPath);

        for (int i = 0; i < fileList.Count; i++)
        {
            FileInfo info = new FileInfo(fileList[i]);
            string md5 = FileHelper.BuildFileMD5(fileList[i]);
            string fileName = fileList[i].Substring(OriginPath.Length + 1).Replace("\\", "/").Replace(".variant", "");
            File.Copy(fileList[i], Path.Combine(copyToPath, md5));
            string asset = FileHelper.LoadFile($"{fileList[i]}.manifest");
            int idx = asset.IndexOf("Dependencies:");
            string subStr = Regex.Unescape(asset.Substring(idx + "Dependencies:".Length));
            List<string> depsArr = new List<string>();
            //判斷是否擁有依賴資源文件
            if (!subStr.Contains("[]"))
            {
                //去掉字符串首尾空格
                subStr = subStr.Trim();
                string[] deps = subStr.Split('-');
                for (int j = 0; i < deps.Length; j++)
                {
                    string key = deps[j].Trim().Replace(OriginPath + "/", "").Replace(".variant", "");
                    depsArr.Add(key);
                }
            }
            AddNodeToXML(xml, fileName, md5, info.Length, depsArr);
            EditorUtility.DisplayProgressBar("生成資源文件", $"添加資源信息:{fileName},md5為:{md5}", i*1.0f/fileList.Count);
        }
        string dirPath = resPath.Replace("/version", "");
        FileHelper.CreatDirectory(dirPath);
        xml.Save(resPath + @".xml");
        xml.Save(packagePath + @".xml");
    }

    public static void AddNodeToXML(XmlDocument xml, string name, string md5str, long size, List<string> desp)
    {
        //創建根節點
        XmlNode resNode = xml.SelectSingleNode("Root/res");
        //添加元素
        XmlElement element = xml.CreateElement("res");

        element.SetAttribute("size", size.ToString());
        element.SetAttribute("name", name);
        element.InnerText = md5str;

        if (desp.Count > 0)
        {
            XmlElement depEle = xml.CreateElement("dep");
            for (int i = 0; i < desp.Count; i++)
            {
                XmlElement dep = xml.CreateElement("dep");
                dep.InnerText = desp[i];
                depEle.AppendChild(dep);
            }
            element.AppendChild(depEle);
        }
        resNode.AppendChild(element);
    }

 

然后一個資源打包流程就完成了

新手個人理解,如果有誤,敬請諒解

如果您有更好的方式,歡迎分享給我,謝謝了您嘞!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM