/// <summary> /// 拷贝资源包 /// </summary> public static void CopyAssetBundles(string output) { ManifestConfig manifestConfig = JsonReader.Deserialize <ManifestConfig>(File.ReadAllText(assetPath + "/data/conf/manifestfile.json")); // 写入Manifest if (manifestConfig != null) { AssetDatabase.Refresh(); // 拷贝资源 if (Directory.Exists(streamingAssets)) { FileUtil.DeleteFileOrDirectory(streamingAssets); } AssetDatabase.Refresh(); Directory.CreateDirectory(streamingAssets); AssetDatabase.Refresh(); FileUtil.CopyFileOrDirectory(output + "/data", streamingAssets + "/data"); string[] filePaths = Directory.GetFiles(streamingAssets + "/data", "*.manifest", SearchOption.AllDirectories); foreach (var filePath in filePaths) { FileUtil.DeleteFileOrDirectory(filePath); } AssetDatabase.Refresh(); } }
/// <summary> /// 得到清单文件 /// </summary> /// <returns></returns> private static ManifestConfig GetManifest(string output) { output += "/" + output.Split('/')[output.Split('/').Length - 1]; ManifestConfig manifestConfig = null; if (File.Exists(output)) { manifestConfig = new ManifestConfig(); var bundle = AssetBundle.LoadFromFile(output); AssetBundleManifest abManifest = bundle.LoadAsset("assetbundlemanifest") as AssetBundleManifest; string[] bundleNames = abManifest.GetAllAssetBundles(); for (int i = 0; i < bundleNames.Length; ++i) { if (bundleNames[i].EndsWith("updatefile.json") || bundleNames[i].EndsWith("manifestfile.json")) { continue; } Manifest manifest = new Manifest(); manifest.name = bundleNames[i]; ABFI ab = GetABFI(outputPath + "/" + bundleNames[i]); manifest.MD5 = ab.md5; manifest.size = ab.size; foreach (var dependenciesName in abManifest.GetDirectDependencies(bundleNames[i])) { manifest.dependencies.Add(dependenciesName); } manifestConfig.Add(manifest); } bundle.Unload(true); } return(manifestConfig); }
/// <summary> /// Builds the WSP package. /// </summary> private static void BuildWSP() { SolutionHandler solutionHandler = null; Solution manifestConfig = ManifestConfig.Load(Config.Current.ManifestConfigPath); if (manifestConfig != null) { solutionHandler = new SolutionHandler(manifestConfig); } else { solutionHandler = new SolutionHandler(Config.Current.SolutionId); } Log.Information("Building the solution - please wait"); // Build the manifest file and cab list solutionHandler.BuildSolution(); Log.Information("Saving the Manifest.xml file"); // Save the manifest file to the file system solutionHandler.Save(); if (solutionHandler.BuildDDF) { Log.Information("Creating the Cabinet.ddf file"); // Save the ddf to the file system solutionHandler.SaveDDF(); } if (!String.IsNullOrEmpty(Config.Current.CreateWSPFileList)) { Log.Information("Creating the filelist " + Config.Current.CreateWSPFileList); solutionHandler.CreateFileList(); } if (solutionHandler.BuildCAB) { Log.Information("Creating the WSP file"); // Create and save the WSP (cab) file CabHandler.CreateCab(solutionHandler.CabFiles); } if (!string.IsNullOrEmpty(Config.Current.CreateDeploymentFolder)) { Log.Information("Creating the deployment folder"); string s = Config.Current.CreateDeploymentFolder.ToLower(); solutionHandler.CreateDeploymentFolder(s == "stsadm" || s == "all", s == "wspbuilder" || s == "all", s == "ssi" || s == "all"); } if (Config.Current.Cleanup) { solutionHandler.Cleanup(); } }
/// <summary> /// 更新状态 /// </summary> public override void Update() { base.Update(); // 远程更新清单文件加载 if (m_www != null && m_www.isDone) { if (string.IsNullOrEmpty(m_www.error)) { m_remoteUpdateManifestMD5 = Util.GetMD5(m_www.bytes); m_remoteUpdateManifest = JsonReader.Deserialize <ManifestConfig>(m_www.assetBundle.LoadAsset <TextAsset>(Path.GetFileNameWithoutExtension(m_www.url)).text); if (m_www.assetBundle != null) { m_www.assetBundle.Unload(false); } } else { m_remoteUpdateManifest = new ManifestConfig(); } StartAssetUpdate(); m_www.Dispose(); m_www = null; } // 更新中... if (m_assetUpdating) { m_currentRealSize = 0; int count = Mathf.Min(Const.MAX_LOADER, m_async.Count); for (int i = count - 1; i >= 0; --i) { m_currentRealSize += (float)m_async[i].userData * m_async[i].progress; } m_currentRealSize += m_currentSize; if (Time.realtimeSinceStartup >= m_time + 1F) { m_speed = m_currentRealSize - m_lastRealSize; m_lastRealSize = m_currentRealSize; m_time = Time.realtimeSinceStartup; } UIManager.instance.OpenUI(Const.UI_LOADING, Param.Create(new object[] { UILoading.TEXT_TIPS, ConfigManager.GetLang("Asset_Updating"), UILoading.SLIDER, m_currentSize / m_size })); if (m_currentSize == m_size) { m_assetUpdating = false; AssetUpdateComplete(); } } }
/// <summary> /// 清单文件 /// </summary> /// <param name="output"></param> public static void BuildManifestFile(string output) { ManifestConfig manifestConfig = GetManifest(output); // 写入Manifest if (manifestConfig != null) { // 写入到文件 File.WriteAllText(assetPath + "/data/conf/manifestfile.json", JsonWriter.Serialize(manifestConfig)); // 刷新 AssetDatabase.Refresh(); // Build清单文件 AssetBundleBuild[] builds = new AssetBundleBuild[1]; builds[0].assetBundleName = "data/conf/manifestfile"; builds[0].assetBundleVariant = null; builds[0].assetNames = new string[1] { assetPath + "/data/conf/manifestfile.json" }; BuildPipeline.BuildAssetBundles(output, builds, BuildAssetBundleOptions.ChunkBasedCompression, buildTarget); } }
/// <summary> /// 进入状态 /// </summary> /// <param name="param"></param> public override void OnEnter(Param param = null) { base.OnEnter(param); AssetManager.instance.url = App.persistentDataPath; // 加载Loading界面 AssetManager.instance.UnloadAssets(true); UIManager.instance.Clear(); UIManager.instance.OpenUI(Const.UI_LOADING, Param.Create(new object[] { UILoading.TEXT_TIPS, ConfigManager.GetLang("Asset_Request"), UILoading.SLIDER, 0F, UILoading.TEXT_DETAILS, string.Empty }), immediate: true); // 获取本地更新清单文件 AsyncAsset async = AssetManager.instance.AssetBundleLoad(AssetManager.instance.url + Const.UPDATE_FILE); if (async != null && string.IsNullOrEmpty(async.error)) { m_localUpdateManifest = JsonReader.Deserialize <ManifestConfig>(async.mainAsset.ToString()); m_localUpdateManifestMD5 = Util.GetMD5(async.bytes); AssetManager.instance.UnloadAssets(async, true); } // 获取远程更新清单文件 m_www = new WWW(Path.Combine(Path.Combine(App.cdn + App.platform, string.Format(Const.REMOTE_DIRECTORY, App.version)), Const.UPDATE_FILE)); }
/// <summary> /// 分析资源 /// </summary> /// <param name="paths"></param> /// <param name="target"></param> /// <param name="outpath"></param> private static void AnalyzeResource(string[] paths, BuildTarget target, string outpath) { additionBuildPackageCache = new List <string>(); CurManifestConfig = new ManifestConfig(); //以下三个建议使用内部函数,不要直接调用unity原生接口获取, allfileHashMap = new Dictionary <string, string>(); //file hash获取缓存 assetImpoterMap = new Dictionary <string, AssetImporter>(); //Assetimport获取缓存 DependenciesMap = new Dictionary <string, List <string> >(); //依赖获取缓存 //加载配置 ManifestConfig lastManifestConfig = null; if (File.Exists(configPath)) { lastManifestConfig = new ManifestConfig(File.ReadAllText(configPath)); } else { lastManifestConfig = new ManifestConfig(); } // /***************************************开始分析资源****************************************/ //1.收集图集信息 EditorUtility.DisplayProgressBar("分析资源", "收集SpriteAtlas", 0); GetSpriteAtlasInfo(); //2.收集单ab多资源信息 GetBuildAbConfig(); //3.开始分析资源 bool isAdditionBuild = allfileHashMap.Count > 0; //是否为增量打包 List <string> changeList = new List <string>(); for (int index = 0; index < paths.Length; index++) { var mainAssetFullPath = paths[index].Replace("\\", "/"); EditorUtility.DisplayProgressBar("分析资源", string.Format("分析:{0} {1}/{2}", Path.GetFileName(mainAssetFullPath), index + 1, paths.Length), (index + 1f) / paths.Length); //获取被依赖的路径 var mainAssetPath = "Assets" + mainAssetFullPath.Replace(Application.dataPath, ""); var subAssetsPath = GetDependencies(mainAssetPath).ToList(); List <string> subAssetHashList = new List <string>(); //处理依赖资源打包 for (int i = 0; i < subAssetsPath.Count; i++) { var subAsset = subAssetsPath[i]; var subAssetPath = subAssetsPath[i];// Application.dataPath + subAsset.Replace("Assets/", "/"); string subAssetHash = GetHashFromFile(subAssetPath); subAssetHashList.Add(subAssetHash); //本地ab文件存在则不打包 var localABPath = IPath.Combine(outpath, subAssetHash); if (File.Exists(localABPath)) { var lastItem = lastManifestConfig.GetManifestItemByHash(subAssetHash); if (lastItem != null) { CurManifestConfig.AddItem(lastItem); continue; } } else { // 需要对比之前的hash var lastItem = lastManifestConfig.GetManifestItemByHash(subAssetHash); if (lastItem != null && lastItem.Package != "" && lastItem.Hash == subAssetHash) { CurManifestConfig.AddItem(lastItem); continue; } } #region 嵌套引用 - 缓存 var subAssetDpendList = GetDependencies(subAsset).ToList(); //sub dpend 2 hash if (subAssetDpendList.Count > 1) { for (int j = 0; j < subAssetDpendList.Count; j++) { var sbd = subAssetDpendList[j];// Application.dataPath + subAssetDpendList[j].Replace("Assets/", "/"); subAssetDpendList[j] = GetHashFromFile(sbd); } } else { subAssetDpendList.Clear(); } #endregion //开始设置abname var ai = GetAssetImporter(subAsset); ManifestItem.AssetTypeEnum @enum = ManifestItem.AssetTypeEnum.Others; var savename = CheckAssetSaveInfo(subAsset, ref @enum); string packageHashName = null; #region 单ab多资源模式 if (IsMakePackage(subAsset, ref packageHashName)) { #region 增量打包遇到单ab多资源 //增量打包时,如果遇到多包合一时,其中某个变动,剩余的也要一次性打出 // if (isAdditionBuild && !additionBuildPackageCache.Contains(packageHashName)) // { // var lastAssets = lastManifestConfig.Manifest_NameKey.Values.ToList().FindAll((item) => // !string.IsNullOrEmpty(item.Package) && item.Package == packageHashName); // foreach (var la in lastAssets) // { // //考虑增量打包时候,得补齐Runtime下的路径名 // var path = la.Name; // if (!path.StartsWith("Assets/")) // { // foreach (var key in LastAllAssetCache.Keys) // { // var p = path + "."; // if (key.Contains(p)) // { // path = key; // } // } // } // // //获取上次的importer // var laAI = GetAssetImporter(path); // if (laAI == null) // { // Debug.LogError("资源不存在:" + la.Name); // continue; // } // // laAI.assetBundleName = packageHashName; // laAI.assetBundleVariant = ""; // } // // if (isAdditionBuild) // { // additionBuildPackageCache.Add(packageHashName); // } // } #endregion string packageName = ""; foreach (var item in allfileHashMap) { if (item.Value == packageHashName) { packageName = item.Key; packageName = packageName.Replace(BApplication.ProjectRoot + "/", ""); break; } } //保存配置 if (subAsset != mainAssetPath) { CurManifestConfig.AddItem(savename, subAssetHash, subAssetDpendList, @enum, packageHashName); } ai.assetBundleName = packageName; ai.assetBundleVariant = ""; // ai.assetBundleName = packageHashName; // ai.assetBundleVariant = ""; } #endregion #region 单ab单资源模式 else { if (subAsset != mainAssetPath) { CurManifestConfig.AddItem(savename, subAssetHash, subAssetDpendList, @enum); } // if (!subAsset.Contains("Assets/")) // { // ai.assetBundleName = "Assets/Resource/Runtime/" + savename; // ai.assetBundleVariant = ""; // } // else // { ai.assetBundleName = subAsset; ai.assetBundleVariant = ""; // } // ai.assetBundleName = subAssetHash; // ai.assetBundleVariant = ""; } #endregion changeList.Add(subAsset); } //最后保存主文件 var mainHash = GetHashFromFile(mainAssetPath); string package = null; subAssetHashList.Remove(mainHash); if (IsMakePackage(mainAssetPath, ref package)) { //单ab包-多资源模式 ManifestItem.AssetTypeEnum @enum = ManifestItem.AssetTypeEnum.Others; var sn = CheckAssetSaveInfo(mainAssetPath, ref @enum); CurManifestConfig.AddItem(sn, mainHash, subAssetHashList, @enum, package); } else { //单ab包-单资源模式 ManifestItem.AssetTypeEnum @enum = ManifestItem.AssetTypeEnum.Others; var sn = CheckAssetSaveInfo(mainAssetPath, ref @enum); CurManifestConfig.AddItem(sn, mainHash, subAssetHashList, @enum); } } //补全 [单ab多资源的配置],并将真正的hash传入 foreach (var con in PackageConfig) { var hash = GetHashFromString(con.AssetBundleName); //多合一ab的hash是要有所有的依赖文件 hash,再hash一次 CurManifestConfig.AddItem(con.AssetBundleName, hash, new List <string>(), ManifestItem.AssetTypeEnum.Others); } //最后检查配置 foreach (var item in CurManifestConfig.Manifest_HashKey.Values) { for (int i = 0; i < item.Depend.Count; i++) { var dHash = item.Depend[i]; //判断是否在runtime内 var dItem = CurManifestConfig.GetManifestItemByHash(dHash); if (dItem != null) { if (!string.IsNullOrEmpty(dItem.Package)) { //将非Runtime目录中的 item.Depend[i] = dItem.Package; } } else { Debug.LogError("【资源遗失】没找到依赖项:" + dHash); foreach (var v in allfileHashMap) { if (dHash == v.Value) { Debug.LogError("hash source file:" + v.Key); break; } } } } item.Depend.Remove(item.Hash); item.Depend = item.Depend.Distinct().ToList(); } EditorUtility.ClearProgressBar(); changeList = changeList.Distinct().ToList(); Debug.LogFormat("<color=red>本地需要打包数量:{0}</color>", changeList.Count); var buildpath = string.Format("{0}/{1}_changelist.json", Application.streamingAssetsPath, target.ToString()); File.WriteAllText(buildpath, JsonMapper.ToJson(changeList)); Debug.Log("本地打包保存:" + buildpath); }
/// <summary> /// 生成AssetBundle /// </summary> /// <param name="outputPath">导出目录</param> /// <param name="target">平台</param> /// <param name="options">打包参数</param> /// <param name="isHashName">是否为hash name</param> public static bool GenAssetBundle(string outputPath, RuntimePlatform platform, BuildTarget target, BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression, bool isHashName = false, string AES = "") { var _outputPath = Path.Combine(outputPath, BDApplication.GetPlatformPath(platform)); // var artOutputPath = IPath.Combine(_outputPath, "Art"); var buildInfoPath = IPath.Combine(artOutputPath, "BuildInfo.json"); //初始化 allfileHashMap = new Dictionary <string, string>(); var assetPaths = BDApplication.GetAllAssetsPath(); for (int i = 0; i < assetPaths.Count; i++) { assetPaths[i] = assetPaths[i].ToLower(); } /***********************新老资源依赖生成************************/ //获取老的配置 BuildInfo lastBuildInfo = new BuildInfo(); if (File.Exists(buildInfoPath)) { var content = File.ReadAllText(buildInfoPath); lastBuildInfo = JsonMapper.ToObject <BuildInfo>(content); } //获取当前配置 var newbuildInfo = GetAssetsInfo(assetPaths); var buildinfoCahce = JsonMapper.ToJson(newbuildInfo); //BD生命周期触发 BDEditorBehaviorHelper.OnBeginBuildAssetBundle(newbuildInfo); //获取改动的数据 var changedBuildInfo = GetChangedAssets(lastBuildInfo, newbuildInfo); // newbuildInfo = null; //防止后面再用 if (changedBuildInfo.AssetDataMaps.Count == 0) { Debug.Log("无资源改变,不需要打包!"); return(false); } #region 整理依赖关系 //1.把依赖资源替换成AB Name, foreach (var asset in newbuildInfo.AssetDataMaps.Values) { for (int i = 0; i < asset.DependList.Count; i++) { var da = asset.DependList[i]; var dependAssetData = newbuildInfo.AssetDataMaps[da]; //替换成真正AB名 if (!string.IsNullOrEmpty(dependAssetData.ABName)) { asset.DependList[i] = dependAssetData.ABName; } } //去重 asset.DependList = asset.DependList.Distinct().ToList(); asset.DependList.Remove(asset.ABName); } var runtimeStr = "/runtime/"; if (isHashName) { //使用guid 作为ab名 foreach (var asset in newbuildInfo.AssetDataMaps) { var abname = AssetDatabase.AssetPathToGUID(asset.Value.ABName); if (!string.IsNullOrEmpty(abname)) //不存在的资源(如ab.shader之类),则用原名 { asset.Value.ABName = abname; } else { Debug.LogError("获取GUID失败:" + asset.Value.ABName); } for (int i = 0; i < asset.Value.DependList.Count; i++) { var dependAssetName = asset.Value.DependList[i]; abname = AssetDatabase.AssetPathToGUID(dependAssetName); if (!string.IsNullOrEmpty(abname)) //不存在的资源(如ab.shader之类),则用原名 { asset.Value.DependList[i] = abname; } else { Debug.LogError("获取GUID失败:" + dependAssetName); } } } } else { //2.整理runtime路径 替换路径名为Resource规则的名字 foreach (var asset in newbuildInfo.AssetDataMaps) { if (asset.Key.Contains(runtimeStr)) { var newName = asset.Value.ABName; //移除runtime之前的路径、后缀 var index = newName.IndexOf(runtimeStr); newName = newName.Substring(index + 1); //runtimeStr.Length); var extension = Path.GetExtension(newName); if (!string.IsNullOrEmpty(extension)) { newName = newName.Replace(extension, ""); } //刷新整个列表替换 foreach (var _asset in newbuildInfo.AssetDataMaps) { var oldName = asset.Key.ToLower(); //ab替换 if (_asset.Value.ABName == oldName) { _asset.Value.ABName = newName; } //依赖替换 for (int i = 0; i < _asset.Value.DependList.Count; i++) { if (_asset.Value.DependList[i] == oldName) { _asset.Value.DependList[i] = newName; } } } } } } #endregion #region 生成Runtime使用的Config //根据buildinfo 生成加载用的 Config //1.只保留Runtime目录下的配置 ManifestConfig config = new ManifestConfig(); config.IsHashName = isHashName; // foreach (var item in newbuildInfo.AssetDataMaps) { //runtime路径下, //改成用Resources加载规则命名的key if (item.Key.Contains(runtimeStr)) { var key = item.Key; //移除runtime之前的路径、后缀 var index = key.IndexOf(runtimeStr); if (config.IsHashName) { key = key.Substring(index + runtimeStr.Length); //hash要去掉runtime } else { key = key.Substring(index + 1); // 保留runtime } var exten = Path.GetExtension(key); if (!string.IsNullOrEmpty(exten)) { key = key.Replace(exten, ""); } //添加manifest var mi = new ManifestItem(item.Value.ABName, (ManifestItem.AssetTypeEnum)item.Value.Type, new List <string>(item.Value.DependList)); config.ManifestMap[key] = mi; } } //写入 FileHelper.WriteAllText(artOutputPath + "/Config.json", JsonMapper.ToJson(config)); #endregion #region 设置ABname /***********************开始设置build ab************************/ //设置AB name foreach (var changedAsset in changedBuildInfo.AssetDataMaps) { //根据改变的ChangedAssets,获取Asset的资源 var key = changedAsset.Key; var asset = newbuildInfo.AssetDataMaps[changedAsset.Key]; //设置ABName 有ab的则用ab ,没有就用configpath string abname = asset.ABName; // var ai = GetAssetImporter(key); if (ai) { ai.assetBundleName = abname; } } #endregion //3.生成AssetBundle try { BuildAssetBundle(target, _outputPath, options); } catch (Exception e) { Debug.LogException(e); throw; } //4.清除AB Name RemoveAllAssetbundleName(); AssetImpoterCacheMap.Clear(); //the end.删除无用文件 var delFiles = Directory.GetFiles(artOutputPath, "*", SearchOption.AllDirectories); foreach (var df in delFiles) { var ext = Path.GetExtension(df); if (ext == ".meta" || ext == ".manifest") { File.Delete(df); } } //the end. BuildInfo写入 if (File.Exists(buildInfoPath)) { string targetPath = artOutputPath + "/BuildInfo.old.json"; File.Delete(targetPath); File.Move(buildInfoPath, targetPath); } FileHelper.WriteAllText(buildInfoPath, buildinfoCahce); //BD生命周期触发 BDEditorBehaviorHelper.OnEndBuildAssetBundle(outputPath); AssetHelper.AssetHelper.GenPackageBuildInfo(outputPath, platform); return(true); }
/// <summary> /// 分析资源 /// </summary> /// <param name="paths"></param> /// <param name="target"></param> /// <param name="outpath"></param> private static void AnalyzeResource(string[] paths, BuildTarget target, string outpath) { additionBuildPackageCache = new List <string>(); curManifestConfig = new ManifestConfig(); fileHashMap = new Dictionary <string, string>(); //加载 ManifestConfig lastManifestConfig = null; if (File.Exists(configPath)) { lastManifestConfig = new ManifestConfig(File.ReadAllText(configPath)); } else { lastManifestConfig = new ManifestConfig(); } // if (File.Exists(cachePath)) { AssetCache = new ManifestConfig(File.ReadAllText(cachePath)); } else { AssetCache = new ManifestConfig(); } /***************************************开始分析资源****************************************/ //获取图集信息 CollectSpriteAtlas(); EditorUtility.DisplayProgressBar("分析资源 -" + target, "收集SpriteAtlas", 0); //开始分析资源 List <string> changeList = new List <string>(); float curIndex = 0; var allAssetList = paths.ToList(); for (int index = 0; index < allAssetList.Count; index++) { var path = allAssetList[index]; var _path = path.Replace("\\", "/"); EditorUtility.DisplayProgressBar( "分析资源 -" + target, "分析:" + Path.GetFileNameWithoutExtension(_path) + " 进度:" + curIndex + "/" + paths.Length, curIndex / paths.Length); curIndex++; //获取被依赖的路径 var dependsource = "Assets" + _path.Replace(Application.dataPath, ""); var allDependObjectPaths = AssetDatabase.GetDependencies(dependsource).ToList(); dependsource = dependsource.ToLower(); //检查依赖是否存在,不存在的依赖需要人为剔除, for (int i = allDependObjectPaths.Count - 1; i >= 0; i--) { var dp = allDependObjectPaths[i]; //全路径 var fullPath = Application.dataPath + dp.TrimStart("Assets".ToCharArray()); // if (!File.Exists(fullPath)) { //有可能是文件夹 if (!Directory.Exists(fullPath)) { Debug.LogError("丢失依赖:" + dp); } //即使文件夹也要移除啊,么得办法 allDependObjectPaths.RemoveAt(i); } } GetCanBuildAssets(ref allDependObjectPaths); //处理依赖资源打包 for (int i = 0; i < allDependObjectPaths.Count; i++) { var dp = allDependObjectPaths[i]; var dependObjPath = Application.dataPath + dp.TrimStart("Assets".ToCharArray()); string uiid = null; //这里说明文件已经处理过 if (fileHashMap.TryGetValue(dependObjPath, out uiid)) { continue; } else { uiid = GetMD5HashFromFile(dependObjPath); fileHashMap[dependObjPath] = uiid; } //判断是否打包 ManifestItem lastItem = null; AssetCache.ManifestMap.TryGetValue(dp, out lastItem); //已经添加,不用打包 if (lastItem != null && lastItem.UIID == uiid) { //不用打包记录缓存 var _last = lastManifestConfig.ManifestMap.Values.ToList().Find((item) => item.UIID == lastItem.UIID); if (_last != null) { curManifestConfig.AddDepend(_last.Name, _last.UIID, _last.Dependencies, _last.PackageName); } continue; } changeList.Add(dependsource); // AssetCache.AddDepend(dp, uiid, new List <string>()); //开始设置abname 用以打包 AssetImporter ai = AssetImporter.GetAtPath(dp); string abname = "Assets" + dependObjPath.Replace(Application.dataPath, ""); //判断是否要打在同一个ab包内 string packageName = null; var list = new List <string>(); //嵌套引用prefab if (dp.ToLower() != dependsource && Path.GetExtension(dp).ToLower().Equals(".prefab")) { list = AssetDatabase.GetDependencies(abname).ToList(); //检查依赖是否存在,不存在的依赖需要人为剔除, for (int n = list.Count - 1; n >= 0; n--) { var _dp = list[n]; //全路径 var fullPath = Application.dataPath + _dp.TrimStart("Assets".ToCharArray()); // if (!File.Exists(fullPath)) { Debug.LogError("丢失依赖:" + _dp); list.RemoveAt(i); } } GetCanBuildAssets(ref list); //转换成全小写 for (int j = 0; j < list.Count; j++) { list[j] = list[j].ToLower(); } } abname = abname.ToLower(); if (IsMakePackage(abname, ref packageName)) { //增量打包时,如果遇到多包合一时,其中某个变动,剩余的也要一次性打出 if (!additionBuildPackageCache.Contains(packageName)) { var lowPackgeName = packageName.ToLower(); var oldAsset = lastManifestConfig.ManifestMap.Values.ToList().FindAll((item) => item.PackageName == lowPackgeName); foreach (var oa in oldAsset) { AssetImporter _ai = AssetImporter.GetAtPath(oa.Name); if (_ai == null) { Debug.LogError("资源不存在:" + oa.Name); continue; } _ai.assetBundleName = packageName; _ai.assetBundleVariant = ""; } Debug.LogFormat("<color=yellow>多合一打包:{0} , 依赖:{1}</color>", packageName, oldAsset.Count); additionBuildPackageCache.Add(packageName); } // ai.assetBundleName = packageName; ai.assetBundleVariant = ""; //被依赖的文件,不保存其依赖信息 if (abname != dependsource) { curManifestConfig.AddDepend(abname, uiid, list, packageName.ToLower()); } } else { ai.assetBundleName = abname; ai.assetBundleVariant = ""; //被依赖的文件,不保存其依赖信息 if (abname != dependsource) //依赖列表中会包含自己 { curManifestConfig.AddDepend(abname, uiid, list); } } } //保存主文件的依赖 { //获取MD5的UIID var UIID = GetMD5HashFromFile(_path); allDependObjectPaths.Remove(dependsource); for (int i = 0; i < allDependObjectPaths.Count; i++) { allDependObjectPaths[i] = allDependObjectPaths[i].ToLower(); } // string packageName = null; if (IsMakePackage(dependsource, ref packageName)) { //单ab包-多资源模式 curManifestConfig.AddDepend(dependsource, UIID, allDependObjectPaths, packageName.ToLower()); } else { //单ab包-单资源模式 curManifestConfig.AddDepend(dependsource, UIID, allDependObjectPaths); } } } EditorUtility.ClearProgressBar(); Debug.LogFormat("<color=red>本地需要打包数量:{0}</color>", changeList.Count); var buidpath = string.Format("{0}/{1}_buid.json", outpath, target.ToString()); FileHelper.WriteAllText(buidpath, JsonMapper.ToJson(changeList)); Debug.Log("本地打包保存:" + buidpath); }
private static void AnalyzeResource(string[] paths, BuildTarget target, string outpath) { ManifestConfig manifestConfig = null; var configPath = Path.Combine(outpath, "Art/Config.json"); if (File.Exists(configPath)) { var content = File.ReadAllText(configPath); manifestConfig = new ManifestConfig(content); } else { manifestConfig = new ManifestConfig(); } int counter = 0; float curIndex = 0; foreach (var path in paths) { var _path = path.Replace("\\", "/"); EditorUtility.DisplayProgressBar("分析资源 -" + target.ToString(), "分析:" + Path.GetFileNameWithoutExtension(_path) + " 进度:" + curIndex + "/" + paths.Length, curIndex / paths.Length); curIndex++; //获取被依赖的路径 var dependsource = "Assets" + _path.Replace(Application.dataPath, ""); var allDependObjectPaths = AssetDatabase.GetDependencies(dependsource).ToList(); var manifestItem = manifestConfig.GetManifestItem(dependsource.ToLower()); var Uiid = GetMD5HashFromFile(_path); // var isEquals = manifestItem != null && Uiid == manifestItem.UIID; List <string> newAssets = new List <string>(); //处理依赖资源是否打包 for (int i = 0; i < allDependObjectPaths.Count; i++) { // var dependPath = allDependObjectPaths[i]; var ext = Path.GetExtension(dependPath).ToLower(); if (ext == ".cs" || ext == ".js") { continue; } // AssetImporter ai = AssetImporter.GetAtPath(dependPath); if (ai == null) { BDebug.Log("not find Resource " + dependPath); continue; } //重新组建ab名字,带上路径名 dependPath = Path.GetFullPath(dependPath); dependPath = dependPath.Replace("\\", "/"); //根据是否相等,判断是否打包 if (isEquals) { //本次不打包 ai.assetBundleName = null; } else { //本次打包 string derictory = "assets" + dependPath.Replace(Application.dataPath, ""); ai.assetBundleName = derictory.ToLower(); newAssets.Add(ai.assetBundleName); ai.assetBundleVariant = ""; } } //将现在的目录结构替换配置中的 if (newAssets.Count > 0) { manifestConfig.AddDepend(dependsource.ToLower(), Uiid, newAssets); counter++; } } Debug.Log("本地需要打包资源:" + counter); var direct = Path.GetDirectoryName(configPath); if (Directory.Exists(direct) == false) { Directory.CreateDirectory(direct); } //写入本地 File.WriteAllText(configPath, manifestConfig.ToString()); }
/// <summary> /// 分析资源 /// </summary> /// <param name="paths"></param> /// <param name="target"></param> /// <param name="outpath"></param> private static void AnalyzeResource(string[] paths, BuildTarget target, string outpath) { curManifestConfig = new ManifestConfig(); //加载存在的配置 ManifestConfig lastManifestConfig = null; var lastConfigPath = IPath.Combine(outpath, "Art/Config.json"); if (File.Exists(lastConfigPath)) { lastManifestConfig = new ManifestConfig(File.ReadAllText(lastConfigPath)); } else { lastManifestConfig = new ManifestConfig(); } List <string> changeList = new List <string>(); float curIndex = 0; foreach (var path in paths) { var _path = path.Replace("\\", "/"); EditorUtility.DisplayProgressBar("分析资源 -" + target.ToString(), "分析:" + Path.GetFileNameWithoutExtension(_path) + " 进度:" + curIndex + "/" + paths.Length, curIndex / paths.Length); curIndex++; //获取被依赖的路径 var dependsource = "Assets" + _path.Replace(Application.dataPath, ""); var allDependObjectPaths = AssetDatabase.GetDependencies(dependsource).ToList(); var UIID = GetMD5HashFromFile(_path); if (string.IsNullOrEmpty(UIID)) { continue; } List <string> dependAssets = new List <string>(); //处理依赖资源是否打包 for (int i = 0; i < allDependObjectPaths.Count; i++) { // var dependPath = allDependObjectPaths[i]; //脚本不打包 var ext = Path.GetExtension(dependPath).ToLower(); if (ext == ".cs" || ext == ".js") { continue; } // AssetImporter ai = AssetImporter.GetAtPath(dependPath); if (ai == null) { BDebug.Log("not find Resource " + dependPath); continue; } var dependObjPath = Application.dataPath + dependPath.TrimStart("Assets".ToCharArray()); var uiid = GetMD5HashFromFile(dependObjPath); if (string.IsNullOrEmpty(uiid)) { continue; } string abname = "assets" + dependObjPath.Replace(Application.dataPath, "").ToLower(); ManifestItem manifestItem = null; lastManifestConfig.Manifest.TryGetValue(abname, out manifestItem); //last没有或者 uiid不一致被改动, if (manifestItem == null || manifestItem.UIID != uiid) { ai.assetBundleName = abname; ai.assetBundleVariant = ""; changeList.Add(abname); } else { ai.assetBundleName = null; } //被依赖的文件,不保存其依赖信息 if (abname != dependsource.ToLower()) //依赖列表中会包含自己 { curManifestConfig.AddDepend(abname, uiid, new List <string>()); } dependAssets.Add(abname); } //保存主文件的依赖 if (dependAssets.Count > 0) { dependAssets.Remove(dependsource.ToLower()); curManifestConfig.AddDepend(dependsource.ToLower(), UIID, dependAssets); } } EditorUtility.ClearProgressBar(); Debug.Log("本地需要打包资源:" + changeList.Count); }
/// <summary> /// 拷贝更新资源包 /// </summary> public static void CopyUpdateAssetBundles(string output, string dest, string version, string cdn = null) { ManifestConfig remote = new ManifestConfig(); if (!string.IsNullOrEmpty(cdn)) { string url = cdn + "/data/conf/updatefile.json"; WWW www = new WWW(url); while (!www.isDone) { ; } if (string.IsNullOrEmpty(www.error) && www.progress == 1f) { TextAsset text = www.assetBundle.LoadAsset(Path.GetFileNameWithoutExtension(url)) as TextAsset; remote = JsonReader.Deserialize <ManifestConfig>(text.text); www.assetBundle.Unload(true); } www.Dispose(); } ManifestConfig local = JsonReader.Deserialize <ManifestConfig>(File.ReadAllText(assetPath + "/data/conf/updatefile.json")); if (local != null) { ManifestConfig manifestConfig = new ManifestConfig(); foreach (var data in local.data.Values) { if (remote.Contains(data.name) && remote.Get(data.name).MD5 == data.MD5) { continue; } manifestConfig.Add(data); } if (!Directory.Exists(dest)) { Directory.CreateDirectory(dest); } string updateFilePath = dest + "/updatefile.json"; string updateFileValue = JsonWriter.Serialize(manifestConfig); File.WriteAllText(updateFilePath, updateFileValue); AssetDatabase.Refresh(); manifestConfig.Add(new Manifest() { name = "data/conf/manifestfile.json" }); manifestConfig.Add(new Manifest() { name = "data/conf/updatefile.json" }); using (MemoryStream stream = new MemoryStream()) { using (ZipOutputStream zip = new ZipOutputStream(stream)) { zip.SetComment(version); foreach (var data in manifestConfig.data.Values) { ZipEntry entry = new ZipEntry(data.name); entry.DateTime = new DateTime(); entry.DosTime = 0; zip.PutNextEntry(entry); string filepPth = output + "/" + data.name; var bytes = File.ReadAllBytes(filepPth); zip.Write(bytes, 0, bytes.Length); } zip.Finish(); zip.Flush(); var fileBytes = new byte[stream.Length]; Array.Copy(stream.GetBuffer(), fileBytes, fileBytes.Length); string platform = "PC"; #if UNITY_ANDROID platform = "Android"; #elif UNITY_IOS platform = "iOS"; #endif DateTime dt = DateTime.Now; string date = string.Format("{0}.{1}.{2}_{3}.{4}.{5}", dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); string md5 = Util.GetMD5(fileBytes); File.WriteAllBytes(string.Format("{0}/{1}_{2}_{3}_{4}.zip", dest, platform, version, date, md5), fileBytes); } } File.Delete(updateFilePath); AssetDatabase.Refresh(); } }
/// <summary> /// 更新文件 /// </summary> /// <param name="output"></param> public static void BuildUpdateFile(string output, string cdn = null) { ManifestConfig newManifestConfig = GetManifest(output); ManifestConfig oldManifestConfig = newManifestConfig; if (!string.IsNullOrEmpty(cdn)) { string url = cdn + "/data/conf/manifestfile.json"; WWW www = new WWW(url); while (!www.isDone) { ; } if (string.IsNullOrEmpty(www.error) && www.progress == 1f) { TextAsset text = www.assetBundle.LoadAsset(Path.GetFileNameWithoutExtension(url)) as TextAsset; oldManifestConfig = JsonReader.Deserialize <ManifestConfig>(text.text); www.assetBundle.Unload(false); } www.Dispose(); } ManifestConfig manifestConfig = new ManifestConfig(); if (!string.IsNullOrEmpty(cdn)) { string url = cdn + "/data/conf/updatefile.json"; WWW www = new WWW(url); while (!www.isDone) { ; } if (string.IsNullOrEmpty(www.error) && www.progress == 1f) { TextAsset text = www.assetBundle.LoadAsset(Path.GetFileNameWithoutExtension(url)) as TextAsset; manifestConfig = JsonReader.Deserialize <ManifestConfig>(text.text); www.assetBundle.Unload(false); } www.Dispose(); } // 写入Manifest if (newManifestConfig != null && oldManifestConfig != null) { foreach (var data in newManifestConfig.data.Values) { if (oldManifestConfig.Contains(data.name) && oldManifestConfig.Get(data.name).MD5 == data.MD5) { continue; } manifestConfig.Add(data); } // 写入到文件 File.WriteAllText(assetPath + "/data/conf/updatefile.json", JsonWriter.Serialize(manifestConfig)); // 刷新 AssetDatabase.Refresh(); // Build清单文件 AssetBundleBuild[] builds = new AssetBundleBuild[1]; builds[0].assetBundleName = "data/conf/updatefile"; builds[0].assetBundleVariant = null; builds[0].assetNames = new string[1] { assetPath + "/data/conf/updatefile.json" }; BuildPipeline.BuildAssetBundles(output, builds, BuildAssetBundleOptions.ChunkBasedCompression, buildTarget); } }
/// <summary> /// 生成AssetBundle /// </summary> /// <param name="outPath">导出目录</param> /// <param name="target">平台</param> /// <param name="options">打包参数</param> /// <param name="isHashName">是否为hash name</param> public static void GenAssetBundle(string outPath, BuildTarget target, BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression, bool isHashName = false, string AES = "") { // var artOutpath = IPath.Combine(outPath, "Art"); var builinfoPath = IPath.Combine(outPath, "BuildInfo.json"); //初始化 allfileHashMap = new Dictionary <string, string>(); var assetPaths = BApplication.GetAllAssetsPath(); for (int i = 0; i < assetPaths.Count; i++) { assetPaths[i] = assetPaths[i].ToLower(); } /***********************新老资源依赖生成************************/ //获取老的配置 BuildInfo lastBuildInfo = new BuildInfo(); if (File.Exists(builinfoPath)) { var content = File.ReadAllText(builinfoPath); lastBuildInfo = JsonMapper.ToObject <BuildInfo>(content); } //获取当前配置 var newbuildInfo = GetAssetsInfo(assetPaths); //获取变动的数据 var changedAssetList = GetChangedAssets(lastBuildInfo, newbuildInfo); if (File.Exists(builinfoPath)) { string targetPath = outPath + "/BuildInfo.old.json"; File.Delete(targetPath); File.Move(builinfoPath, targetPath); } FileHelper.WriteAllText(builinfoPath, JsonMapper.ToJson(newbuildInfo)); /***********************整理依赖关系 减少消耗************************/ //保存buildinfo后, //整理runtime路径,减少加载时候的消耗 var runtimeStr = "/runtime/"; foreach (var asset in newbuildInfo.AssetDataMaps) { if (asset.Value.Name.Contains(runtimeStr)) { var newName = asset.Value.Name; //移除runtime之前的路径 var index = newName.IndexOf(runtimeStr); newName = newName.Substring(index + 1); //runtimeStr.Length); //去除后缀 newName = newName.Replace(Path.GetExtension(newName), ""); //刷新整个列表替换 foreach (var _asset in newbuildInfo.AssetDataMaps) { var oldName = asset.Key.ToLower(); //name替换 if (_asset.Value.Name == oldName) { _asset.Value.Name = newName; } //ab替换 if (_asset.Value.AB == oldName) { _asset.Value.AB = newName; } //依赖替换 for (int i = 0; i < _asset.Value.DependList.Count; i++) { if (_asset.Value.DependList[i] == oldName) { _asset.Value.DependList[i] = newName; } } } } } /***********************生成Config************************/ //根据buildinfo 生成ArtConfig ManifestConfig manifest = new ManifestConfig(); manifest.AES = AES; if (isHashName) { // foreach (var item in newbuildInfo.AssetDataMaps) // { // var dependlist = new List<string>(item.Value.DependList.Count); // for (int i = 0; i < dependlist.Count; i++) // { // var assetName = item.Value.DependList[i]; // // var asset = newbuildInfo.AssetDataMaps[assetName]; // dependlist[i] = asset.Hash; // } // // //添加manifest // var path = !string.IsNullOrEmpty(item.Value.AB) ? item.Value.AB : item.Key; // var mi = new ManifestItem(path, (ManifestItem.AssetTypeEnum) item.Value.Type, dependlist); // configMap[item.Key] = mi; // } } else { foreach (var item in newbuildInfo.AssetDataMaps) { //添加manifest var path = !string.IsNullOrEmpty(item.Value.AB) ? item.Value.AB : item.Value.Name; var mi = new ManifestItem(path, (ManifestItem.AssetTypeEnum)item.Value.Type, new List <string>(item.Value.DependList)); //runtime路径下,改成用Resources加载规则命名的key if (path.StartsWith("runtime/")) { manifest.AddManifest(item.Value.Name, mi); } else { manifest.AddManifest(item.Key, mi); } } } //hash命名 //写入 FileHelper.WriteAllText(artOutpath + "/Config.json", JsonMapper.ToJson(manifest)); /***********************开始设置build ab************************/ //设置AB name foreach (var asset in changedAssetList.AssetDataMaps) { string abname = ""; if (!string.IsNullOrEmpty(asset.Value.AB)) { abname = asset.Value.AB; } else { abname = asset.Value.Name; } var ai = GetAssetImporter(asset.Key); if (ai) { ai.assetBundleName = abname; } } //3.生成AssetBundle BuildAssetBundle(target, outPath, options); //4.清除AB Name RemoveAllAssetbundleName(); AssetImpoterCacheMap.Clear(); //the end.删除无用文件 var delFiles = Directory.GetFiles(artOutpath, "*", SearchOption.AllDirectories); foreach (var df in delFiles) { var ext = Path.GetExtension(df); if (ext == ".meta" || ext == ".manifest") { File.Delete(df); } } }
/// <summary> /// 分析资源 /// </summary> /// <param name="paths"></param> /// <param name="target"></param> /// <param name="outpath"></param> private static void AnalyzeResource(string[] paths, BuildTarget target, string outpath) { var lastConfigPath = IPath.Combine(outpath, "Art/Config.json"); if (File.Exists(lastConfigPath)) { curManifestConfig = new ManifestConfig(File.ReadAllText(lastConfigPath)); } else { curManifestConfig = new ManifestConfig(); } List <string> changeList = new List <string>(); float curIndex = 0; foreach (var path in paths) { var _path = path.Replace("\\", "/"); EditorUtility.DisplayProgressBar( "分析资源 -" + target, "分析:" + Path.GetFileNameWithoutExtension(_path) + " 进度:" + curIndex + "/" + paths.Length, curIndex / paths.Length); curIndex++; var UIID = GetMD5HashFromFile(_path); if (string.IsNullOrEmpty(UIID)) { continue; } //获取被依赖的路径 var dependsource = "Assets" + _path.Replace(Application.dataPath, ""); var allDependObjectPaths = AssetDatabase.GetDependencies(dependsource).ToList(); dependsource = dependsource.ToLower(); List <string> dependAssets = new List <string>(); GetCanBuildAssets(ref allDependObjectPaths); //判断是否重新打包 ManifestItem lastItem = null; curManifestConfig.Manifest.TryGetValue(dependsource, out lastItem); //对比列表 if (lastItem != null && lastItem.UIID == UIID) { continue; } //处理依赖资源打包 for (int i = 0; i < allDependObjectPaths.Count; i++) { var dp = allDependObjectPaths[i]; //脚本不打包 AssetImporter ai = AssetImporter.GetAtPath(dp); if (ai == null) { Debug.Log("资源不存在:" + dp); continue; } var dependObjPath = Application.dataPath + dp.TrimStart("Assets".ToCharArray()); var uiid = GetMD5HashFromFile(dependObjPath); if (string.IsNullOrEmpty(uiid)) { continue; } string abname = "Assets" + dependObjPath.Replace(Application.dataPath, ""); //判断是否要打在同一个包内 string packageName = null; var list = new List <string>(); //嵌套引用prefab if (dp.ToLower() != dependsource && Path.GetExtension(dp).ToLower().Equals(".prefab")) { list = AssetDatabase.GetDependencies(abname).ToList(); GetCanBuildAssets(ref list); //转换成全小写 for (int j = 0; j < list.Count; j++) { list[j] = list[j].ToLower(); } } abname = abname.ToLower(); if (IsMakePackage(abname, ref packageName)) { ai.assetBundleName = packageName; ai.assetBundleVariant = ""; //被依赖的文件,不保存其依赖信息 if (abname != dependsource) //依赖列表中会包含自己 { curManifestConfig.AddDepend(abname, uiid, list, packageName.ToLower()); } } else { ai.assetBundleName = abname; ai.assetBundleVariant = ""; //被依赖的文件,不保存其依赖信息 if (abname != dependsource) //依赖列表中会包含自己 { curManifestConfig.AddDepend(abname, uiid, list); } } dependAssets.Add(abname); } // changeList.Add(dependsource); //保存主文件的依赖 if (dependAssets.Count > 0) { dependAssets.Remove(dependsource); string packageName = null; if (IsMakePackage(dependsource, ref packageName)) { //单ab包 多资源模式 curManifestConfig.AddDepend(dependsource, UIID, dependAssets, packageName.ToLower()); } else { //单ab包 单资源模式 curManifestConfig.AddDepend(dependsource, UIID, dependAssets); } } } EditorUtility.ClearProgressBar(); Debug.Log("本地需要打包资源:" + changeList.Count); if (changeList.Count < 100) { Debug.Log("本地需要打包资源:" + JsonMapper.ToJson(changeList)); } }
private static void AnalyzeResource(string[] paths, BuildTarget target, string outpath) { manifestConfig = new ManifestConfig(); List <string> changeList = new List <string>(); //需要保留的ab UID集合 Dictionary <string, string> saveCache = new Dictionary <string, string>(); abCacheDict = GetABCache(outpath); float curIndex = 0; foreach (var path in paths) { var _path = path.Replace("\\", "/"); EditorUtility.DisplayProgressBar("分析资源 -" + target.ToString(), "分析:" + Path.GetFileNameWithoutExtension(_path) + " 进度:" + curIndex + "/" + paths.Length, curIndex / paths.Length); curIndex++; //获取被依赖的路径 var dependsource = "Assets" + _path.Replace(Application.dataPath, ""); var allDependObjectPaths = AssetDatabase.GetDependencies(dependsource).ToList(); var Uiid = GetMD5HashFromFile(_path); // List <string> newAssets = new List <string>(); //处理依赖资源是否打包 for (int i = 0; i < allDependObjectPaths.Count; i++) { // var dependPath = allDependObjectPaths[i]; //脚本不打包 var ext = Path.GetExtension(dependPath).ToLower(); if (ext == ".cs" || ext == ".js") { continue; } // AssetImporter ai = AssetImporter.GetAtPath(dependPath); if (ai == null) { BDebug.Log("not find Resource " + dependPath); continue; } var fullpath = Application.dataPath + dependPath.TrimStart("Assets".ToCharArray()); var uid = GetMD5HashFromFile(fullpath); string derictory = "assets" + fullpath.Replace(Application.dataPath, "").ToLower(); string cacheid; if (abCacheDict.TryGetValue(derictory, out cacheid)) { if (!cacheid.Equals(uid)) { ai.assetBundleName = derictory; ai.assetBundleVariant = ""; abCacheDict[derictory] = uid; changeList.Add(derictory); } else { //已经设置过abname的 if (!changeList.Contains(derictory)) { ai.assetBundleName = ""; } } } else { ai.assetBundleName = derictory; ai.assetBundleVariant = ""; abCacheDict[derictory] = uid; changeList.Add(derictory); } if (!saveCache.ContainsKey(derictory)) { saveCache.Add(derictory, uid); } else { saveCache[derictory] = uid; } newAssets.Add(derictory); } //将现在的目录结构替换配置中的 if (newAssets.Count > 0) { newAssets.Remove(dependsource.ToLower()); manifestConfig.AddDepend(dependsource.ToLower(), Uiid, newAssets); } } //保存用到的ab abCacheDict = SaveCache(saveCache, outpath); //移除没用到ab RemoveUnuseAb(outpath); EditorUtility.ClearProgressBar(); Debug.Log("本地需要打包资源:" + changeList.Count); }