UNITY編輯器模式下static變量的坑


在unity中寫編輯器擴展工具,如在編輯器中加個菜單,點擊這個菜單項時執行打包功能。

類如下,其中的靜態變量,如果每次進來不清空,則LIST會越來越大,打包函數執行完后系統不會幫我們清空

#if UNITY_EDITOR

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

using UnityEngine;
using LitJson;
using System.Security.Cryptography;

//資源清單,列出了游戲中用到的所有資源
[Serializable]
public class AssetsList
{
    /*** 資源清單
     * <相對路徑, MD5串>
     */
    public Dictionary<string, string> Assets2Md5 = new Dictionary<string, string>();//容量未設置,后面可以寫成常量,根據上次數據得出
}

public class AssetBundleBuilder
{
    public static string BundlePath = "";
    public static AssetsList AssetsList = new AssetsList();
    public static List<string> DependAssets_PathList = new List<string>(8000); //InGameRes所依賴的ArtSource等文件夾下的資源
    public static List<string> Assets_PathList = new List<string>(8000); //InGameRes文件夾下的資源
    public static List<AssetBundleBuild>  BuildList = new List<AssetBundleBuild>(18000); //估計值,后面可以寫成常量,根據上次數據得出

    public static string ResRootPath = "Assets/Resources"; //資源目錄,根據此目錄查找所有依賴的資源
    public static string ResDependPath = "Assets/ArtSource"; //資源目錄,根據此目錄查找所有依賴的資源
    public static string ResFolder = "Resources"; //資源打包的依據文件夾,此文件夾下的所有文件及所有依賴文件都被打進AB包,不要在此文件夾下放備份或無用的資源

    public static string Depends_List_File = "Assets/depends_list.json";
    public static string Assets_List_File = "Assets/assets_list.json";

    public static int FileNumLimit = 10; //測試使用,只收集限定數量的文件
    public static void BuildAB(BuildTarget buildTarget)
    {
        if (!Directory.Exists(ResRootPath))
        {
            Debug.LogError("資源目錄不存在:" + ResRootPath);
            return;
        }

        var files = Directory.GetFiles(ResRootPath, "*", SearchOption.AllDirectories);

        var stopwt = System.Diagnostics.Stopwatch.StartNew();
        var t1 = stopwt.ElapsedMilliseconds;

        /***
         * 坑,這里必須要清空,否則每次進來就會往Assets_PathList里添加一次內容
         */
        BuildList.Clear();
        Assets_PathList.Clear();
        DependAssets_PathList.Clear();
        AssetsList.Assets2Md5.Clear();

        var infoTitle = "收集文件,目錄 : " + ResRootPath;
        //第一步,將ResRootPath目錄下的文件收集起來,此目錄的東西都是游戲中要用到的,此目錄之外的東西不一定會用到
        var idx = 0;
        for (int i=0, cnt=files.Length; i<cnt; ++i)
        {
            var item = files[i];
            var ext = Path.GetExtension(item);
            if (string.Compare(ext, ".meta", true)==0 || string.Compare(ext, ".cs", true)==0)
                continue;

            if ((string.Compare(ext, ".prefab", true) == 0 || string.Compare(ext, ".mat", true) == 0
                || string.Compare(ext, ".unity", true) == 0 || string.Compare(ext, ".controller", true) == 0))
            {
                idx++;
                if (idx > FileNumLimit)
                    break;
                //添加到構建列表
                var abName = AddToBuildList(item);
                Assets_PathList.Add(abName);
            }

            EditorUtility.DisplayProgressBar(infoTitle, item, i * 1.0f / cnt);
        }

        WriteToJson(Assets_List_File, Assets_PathList);


        //第二步,將ResRootPath依賴的文件收集進來
        CheckAllDepends(files);

        var infoTitle2 = "收集依賴,目錄 : " + ResDependPath;

        for (int i = 0, cnt = DependAssets_PathList.Count; i < cnt; ++i)
        {
            var item = DependAssets_PathList[i];

            //添加到構建列表
            AddToBuildList(item);

            EditorUtility.DisplayProgressBar(infoTitle2, item, i * 1.0f / cnt);
        }

        //第三步,增量打包
        var targetName = buildTarget == BuildTarget.iOS ? "IOS" : "Android";
        BundlePath = Application.dataPath.Substring(0, Application.dataPath.Length-6) + "Bundles" + targetName; //放到Assets同目錄
        
        if (!Directory.Exists(BundlePath))
        {
            Directory.CreateDirectory(BundlePath);
        }

        var dt1 = stopwt.ElapsedMilliseconds - t1;

        ClearUnusedBundles(files);
        /***
         * 一次傳入所有需要打包的數據,UNITY會保證它們之間的依賴被正確處理:先打被依賴的包,再打自己
         * 我們必須保證傳入數據的完全,如果被依賴的包不在我們傳入的列表中,則相應資源會被打到所有用到該包的包中,造成資源的重復包含
         */
        AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(BundlePath, BuildList.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, buildTarget);
        EditorUtility.ClearProgressBar();

        var dt2 = stopwt.ElapsedMilliseconds - t1 - dt1;

        EditorUtility.DisplayDialog("打包完成", "耗時:" + dt1 / 1000.0f + " + " + dt2/1000.0f + "\n路徑:" + BundlePath, "ok");

        GenResList(manifest);

        EditorUtility.UnloadUnusedAssetsImmediate();

    }

    public static string AddToBuildList(string resPath)
    {
        var relativeDir = resPath;

        //不能這樣處理,因為可能有同名卻不同后綴的文件存在
        //relativeDir = relativeDir.Substring(0, relativeDir.LastIndexOf(".")); //去除后綴
        relativeDir = relativeDir.Replace("\\", "/");
        var abBuild = new AssetBundleBuild();
        abBuild.assetBundleName = relativeDir;
        abBuild.assetNames = new string[] { resPath };
        abBuild.assetBundleVariant = "ab";
        BuildList.Add(abBuild);

        return relativeDir;
    }

    //將AB包移動到StreamingAssets目錄下,為打APK或IPA作准備
    public static void CopyToStreamingAssets(BuildTarget buildTarget)
    {
        CopyFolder(BundlePath, Application.streamingAssetsPath + "/Bundles/");
    }

    /***
     * 增量打包的關鍵,遍歷上次打出的包,與本次【構建列表BuildList】里的內容對比,不在列表中的刪除掉。
     * 在列表中的保留不變。這樣當我們再次執行打包操作時,如果文件對應的AB包已存在且文件沒有更改,則不用重新打包
     */
    public static void ClearUnusedBundles(string[] files)
    {
        if (!Directory.Exists(BundlePath))
        {
            Debug.LogError("包目錄不存在:" + BundlePath);
            return;
        }

        var abFiles = Directory.GetFiles(BundlePath + "/assets", "*", SearchOption.AllDirectories);
        var delList = new List<string>();

        for (int i=0, n=abFiles.Length; i<n; ++i)
        {
            var ext = Path.GetExtension(abFiles[i]);
            var file = Path.ChangeExtension(abFiles[i], null);
            if(string.Compare(ext, ".manifest", true) == 0)
            {
                file = Path.ChangeExtension(file, null);
            }

            file = file.Substring(file.IndexOf("assets", StringComparison.CurrentCultureIgnoreCase));
            if (!File.Exists(file))
            {
                delList.Add(abFiles[i]);
            }

            EditorUtility.DisplayProgressBar("正在檢查AssetBundle", file, i * 1.0f / n);

        }

        for (int i=0, n= delList.Count; i<n; ++i)
        {
            EditorUtility.DisplayProgressBar("正在清理無用的AssetBundle", delList[i], i * 1.0f / n);
            if (!File.Exists(delList[i]))
            {
                EditorUtility.DisplayDialog("", "不存在此文件", "ok");
                continue;
            }

            File.Delete(delList[i]);

        }

        EditorUtility.ClearProgressBar();

        WriteToJson("Assets/delete_list.json", delList);
    }

    //剪切到某個目錄,並重命名
    public static void MoveFolder(string src, string dest)
    {
        if (Directory.Exists(dest))
        {
            Directory.Delete(dest, true);
        }
        Directory.CreateDirectory(dest);

        File.Move(src, dest + "/Bundles");
    }

    //復制到某個目錄,並重命名
    public static void CopyFolder(string src, string dest)
    {
        if (Directory.Exists(dest))
        {
            Directory.Delete(dest, true);
        }
        Directory.CreateDirectory(dest);

        foreach (var sub in Directory.GetDirectories(src))
        {
            CopyFolder(sub + "/", dest + Path.GetFileName(sub) + "/");
        }

        foreach (var file in Directory.GetFiles(src))
        {
            File.Copy(file, dest + Path.GetFileName(file));
        }
    }

    /*** 生成資源清單
     * 格式:MD5 : 相對於/Bundles/的路徑
     * 作用:熱更的對比依據,通過MD5對比,確定文件的增-刪-修改
     */
    public static void GenResList(AssetBundleManifest manifest)
    {
        AssetsList.Assets2Md5.Clear();

        var abs = manifest.GetAllAssetBundles();
        foreach (var ab in abs)
        {
            var hashes = manifest.GetAssetBundleHash(ab);
            AssetsList.Assets2Md5.Add(ab, hashes.ToString());
        }

        var writer = new JsonWriter();
        writer.PrettyPrint = true;
        JsonMapper.ToJson(AssetsList, writer);
        File.WriteAllText(BundlePath + "/asset_list.json", writer.ToString(), System.Text.Encoding.ASCII);

    }

    public static void DeleteBundles(BuildTarget target)
    {
        var path = Application.dataPath + "/../" + (target == BuildTarget.Android ? "BundlesAndroid" : "BundlesIOS");
        var msg = "文件夾不存在" + " " + path;
        if (Directory.Exists(path))
        {
            Directory.Delete(path, true);
            msg = "清理完成";
        }

        EditorUtility.DisplayDialog("清理", msg, "ok");
    }

    //得到指定文件夾下資源的所有依賴
    public static void CheckAllDepends(string targetPath)
    {
        if (!Directory.Exists(targetPath))
        {
            Debug.LogError("資源目錄不存在:" + targetPath);
            return;
        }

        var files = Directory.GetFiles(targetPath, "*", SearchOption.AllDirectories);

        CheckAllDepends(files);
    }

    //得到指定文件的所有依賴
    public static void CheckAllDepends(string[] files)
    {
        var stopWatch = System.Diagnostics.Stopwatch.StartNew();
        var startTime = stopWatch.ElapsedMilliseconds;

        DependAssets_PathList.Clear();

        var idx = 0;
        for(int i=0, fileNum = files.Length; i< fileNum; ++i)
        {
            var item = files[i];
            var ext = Path.GetExtension(item);

            if (string.Compare(ext, ".meta", true) == 0 || string.Compare(ext, ".cs",true) == 0)
                continue;
            if ((string.Compare(ext, ".prefab", true) == 0 || string.Compare(ext, ".mat", true) == 0
                || string.Compare(ext, ".unity", true) == 0 || string.Compare(ext, ".controller", true) == 0))
            {
                idx++;
                if (idx > FileNumLimit)
                    break;

                var dpends = AssetDatabase.GetDependencies(item, true);
                foreach (var s in dpends)
                {
                    if (s.Contains(ResFolder) || s.Contains(".cs"))
                        continue;
                    //var substr = s.Substring(s.IndexOf("Assets/"));
                    if (!DependAssets_PathList.Contains(s))
                    {
                        DependAssets_PathList.Add(s);
                    }
                }
            }

            var progress = i *1.0f/ fileNum;
            EditorUtility.DisplayProgressBar("正在查找引用" + fileNum, item, progress);
        }

        EditorUtility.ClearProgressBar();

        //var savePath = "Assets/refs_list.json";
        WriteToJson(Depends_List_File, DependAssets_PathList);

        var deltaTime = stopWatch.ElapsedMilliseconds - startTime;

        EditorUtility.DisplayDialog("檢查完成", "耗時" + deltaTime/1000.0f + "\n保存路徑: " + Depends_List_File, "ok");
    }

    public static void WriteToJson(string path, object obj)
    {
        var writer = new JsonWriter();
        writer.PrettyPrint = true;
        JsonMapper.ToJson(obj, writer);
        File.WriteAllText(path, writer.ToString());
    }

    [MenuItem("AssetBundle/安卓/構建AB包")]
    public static void BuildAndroidAB()
    {
        BuildAB(BuildTarget.Android);
    }

    [MenuItem("AssetBundle/IOS/構建AB包")]
    public static void BuildIOSAB()
    {
        BuildAB(BuildTarget.iOS);
    }

    [MenuItem("AssetBundle/安卓一鍵打包")]
    public static void AndroidOneKeyBuild()
    {
        BuildAB(BuildTarget.Android);
        CopyToStreamingAssets(BuildTarget.Android);
    }

    [MenuItem("AssetBundle/IOS一鍵打包")]
    public static void IOSOneKeyBuild()
    {
        BuildAB(BuildTarget.iOS);
        CopyToStreamingAssets(BuildTarget.iOS);
    }

    [MenuItem("AssetBundle/安卓/拷到StreamingAssets目錄")]
    public static void CopyAndroidABToStreamingAssets()
    {
        CopyToStreamingAssets(BuildTarget.Android);
    }

    [MenuItem("AssetBundle/IOS/拷到StreamingAssets目錄")]
    public static void CopyIOSABToStreamingAssets()
    {
        CopyToStreamingAssets(BuildTarget.iOS);
    }

    [MenuItem("AssetBundle/清理/清理安卓包")]
    public static void DeleteAndroidABs()
    {
        DeleteBundles(BuildTarget.Android);
    }

    [MenuItem("AssetBundle/清理/清理蘋果包")]
    public static void DeleteIOSABs()
    {
        DeleteBundles(BuildTarget.iOS);
    }

    [MenuItem("AssetBundle/資源檢查/檢查依賴")]
    public static void CheckAssetsDepends()
    {
        CheckAllDepends(ResRootPath);
    }
}

#endif

 


免責聲明!

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



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