using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Xml.Serialization; using BundleV2; using Games.GlobeDefine; using GCGame.Table; using UnityEditor; using UnityEditor.Animations; using UnityEngine; using UnityEngine.Events; using UnityEngine.Timeline; using Object = UnityEngine.Object; public static class ClassifyBundles { public const string dummyMaterialPath = "Assets/Project/GameRes/DummyMaterial"; public const string dependBundleHeader = "depend/"; public const string tablePath = "Assets/Project3D/BundleData/Tables3D/"; private static readonly Regex _bundleRegex = new Regex("(?<=^ assetBundleName: ).*$", RegexOptions.Multiline); private static readonly Regex _variantRegex = new Regex("(?<=^ assetBundleVariant: ).*$", RegexOptions.Multiline); private static readonly Regex _spriteRegex = new Regex("(?<=^ spritePackingTag: ).*$", RegexOptions.Multiline); private static string uiPermanentBundle { get { return LoadAssetBundle.uiSpriteBundleHeader.Open("permanent"); } } private static void AddBundleDict(Dictionary bundleDict, string assetPath, string bundleName = default(string)) { if (string.IsNullOrEmpty(assetPath)) { Debug.LogError("试图向BundleDict中添加空路径"); } else { assetPath = assetPath.ToUrl(); AssetImporterBundleSetting bundleSetting; if (!bundleDict.TryGetValue(assetPath, out bundleSetting)) { bundleSetting = AssetImporterBundleSetting.Create(assetPath); if (bundleSetting != null) bundleDict.Add(assetPath, bundleSetting); } if (bundleSetting != null) { // 不主动制定Bundle名称的视为依赖包,将在展开时再执行配置 if (!string.IsNullOrEmpty(bundleName)) { bundleName = bundleName.ToLower().ToUrl(); bundleSetting.bundleName = bundleName; } } } } private static void ClassifyUiPrefabBundles(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("UI").Open("Prefab"), ".prefab"); var uiFileNames = new List(); var uiPathInfos = typeof(UIInfo).GetFields(BindingFlags.Static | BindingFlags.Public); foreach (var uiInfo in uiPathInfos) if (uiInfo.FieldType == typeof(UIPathData)) { var uiPathInfo = uiInfo.GetValue(null) as UIPathData; if (uiPathInfo != null) uiFileNames.Add(uiPathInfo.name); } for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle UI Prefab", assetPath, (float) i / assetPaths.Count); var fileName = Path.GetFileNameWithoutExtension(assetPath); if (uiFileNames.Contains(fileName)) { var bundleName = GetUiPrefabBundleName(assetPath); AddBundleDict(bundleDic, assetPath, bundleName); CheckInvalidPath(assetPath, "第五版/通用UI元素/界面二级"); } } EditorUtility.ClearProgressBar(); } private static void ClassifyUiPrefabMarketingActs(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("UI").Open("Prefab").Open("MarketingActs"), ".prefab"); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle UI Marketing Prefab", assetPath, (float) i / assetPaths.Count); var bundleName = GetUiPrefabBundleName(assetPath); AddBundleDict(bundleDic, assetPath, bundleName); CheckInvalidPath(assetPath, "第五版/通用UI元素/界面二级"); } EditorUtility.ClearProgressBar(); } private static string GetUiPrefabBundleName(string assetPath) { var result = assetPath.Contains(LoadAssetBundle.BUNDLE_MAIN_BASE_UI) ? LoadAssetBundle.BUNDLE_MAIN_BASE_UI : Path.GetFileNameWithoutExtension(assetPath); result = ("ui/" + result).ToLower(); return result; } private static void ClassifyUiPrefabEmoji(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("UI").Open("Prefab").Open("Emoji"), ".prefab"); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle UI Emoji Prefab", assetPath, (float) i / assetPaths.Count); var bundleName = ("ui/emoji/" + Path.GetFileNameWithoutExtension(assetPath)).ToLower(); AddBundleDict(bundleDic, assetPath, bundleName); CheckInvalidPath(assetPath, "第五版/通用UI元素/界面二级"); } EditorUtility.ClearProgressBar(); } public static string GetStringHash(string bundlePath) { return unchecked((uint) Animator.StringToHash(bundlePath)).ToString(); } private static void ClassifyUiSpriteBundles(Dictionary bundleDic) { // 2019.02.28:原“ui/sprite/commonitem”Bundle下资源按文件夹细分到各自Bundle;保持原有调用接口; // 因此额外建立一个对比表格来转换原通用Bundle路径为各自路径; // 运行时在AssetLoadManager中进行实质Bundle名转换 var assetPaths = GetAssetPathForType(LoadAssetBundle.uiSpriteAssetPath, string.Empty); var pathConvert = new Dictionary(); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle UI Sprite", assetPath, (float) i / assetPaths.Count); if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) == typeof(Texture2D)) { var bundlePath = Path.GetDirectoryName(assetPath); var assetName = Path.GetFileNameWithoutExtension(assetPath); System.Diagnostics.Debug.Assert(bundlePath != null, "bundleTail != null"); System.Diagnostics.Debug.Assert(assetName != null, "assetName != null"); assetName = assetName.ToLower(); bundlePath = bundlePath.Substring(LoadAssetBundle.uiSpriteAssetPath.Length + 1).ToLower().ToUrl(); // 如果包含中文或其他不被AssetBundle支持的名称,统一削减为hash码 if (EditorCommonUtility.ContainInvalidPath(bundlePath)) { var temp = bundlePath; bundlePath = GetStringHash(bundlePath); Debug.LogWarning(string.Format("Asset Bundle 路径 {0} 包含非法字符,被校正为{1}" , LoadAssetBundle.uiSpriteBundleHeader.Open(temp) , LoadAssetBundle.uiSpriteBundleHeader.Open(bundlePath))); } bundlePath = LoadAssetBundle.uiSpriteBundleHeader.Open(bundlePath); if (pathConvert.ContainsKey(assetName)) Debug.LogError(string.Format("{0} 拥有重复的资源名称 {1}", LoadAssetBundle.uiCommonSpriteBundle, assetName)); else pathConvert[assetName] = bundlePath; AddBundleDict(bundleDic, assetPath, bundlePath); } } var builder = new StringBuilder(); // 注:AppendLine在不同平台可能留下不同的换行符,因此避开 foreach (var keyValue in pathConvert) builder.Append(string.Format("{0}\t{1}\n", keyValue.Key, keyValue.Value)); // 注:暂时只有这一个特殊表,因此不用太多管理;有更多特殊表时,需要建立命名规则或者其他管理手段 var atlasTable = tablePath.Open(LoadAssetBundle.atlasPathConvertTable) + AssetConst.bytesExtension; File.WriteAllText(EditorCommonUtility.AssetToFilePath(atlasTable), builder.ToString()); AssetDatabase.Refresh(); AddBundleDict(bundleDic, atlasTable, "tables"); EditorUtility.ClearProgressBar(); } private static void ClassifyUiTextureBundles(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("UI").Open("Texture"), string.Empty); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle UI Texture", assetPath, (float) i / assetPaths.Count); if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) == typeof(Texture2D)) { var bundleName = GetNoHeaderNoExtensionName(assetPath); AddBundleDict(bundleDic, assetPath, bundleName); //SetTextureImporter(assetPath); } } EditorUtility.ClearProgressBar(); } private static void ClassifySoundsBundles(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("Sounds"), string.Empty); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle Sounds", assetPath, (float) i / assetPaths.Count); if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) == typeof(AudioClip)) { var bundleName = GetNoHeaderNoExtensionName(assetPath); AddBundleDict(bundleDic, assetPath, bundleName); } } EditorUtility.ClearProgressBar(); } private static string GetNoHeaderNoExtensionName(string assetPath) { var bundleName = assetPath.Substring(LoadAssetBundle.assetPathHeader.Length + 1); var extension = Path.GetExtension(bundleName); if (!string.IsNullOrEmpty(extension)) bundleName = bundleName.Remove(bundleName.Length - extension.Length); bundleName = bundleName.ToLower(); return bundleName; } private static void ClassifySceneBundles(Dictionary bundleDic) { if (_sceneList == null) RefreshSceneList(); System.Diagnostics.Debug.Assert(_sceneList != null, "_sceneList == null"); for (var i = 0; i < _sceneList.Count; i++) { var sceneName = _sceneList[i]; var assetPath = SceneDisassembly.sceneDisassembledPath.Open(sceneName); EditorUtility.DisplayProgressBar("Bundle Scene", assetPath, (float) i / (_sceneList.Count + 1)); var bundleName = ("scene/" + Path.GetFileNameWithoutExtension(assetPath)).ToLower(); AddBundleDict(bundleDic, assetPath, bundleName); var areaPath = SceneDisassembly.sceneDisassembledPath.Open(Path.GetFileNameWithoutExtension(sceneName)) + "/"; var areaPaths = from areaAssetPath in AssetDatabase.GetAllAssetPaths() where areaAssetPath.StartsWith(areaPath) select areaAssetPath; foreach (var areaAsset in areaPaths) { var subBundleName = ("scene/" + Path.GetFileNameWithoutExtension(areaAsset)).ToLower(); AddBundleDict(bundleDic, areaAsset, subBundleName); } } // 2019.03.28 - RoleSelect的SubScene系统已经移除 - 单独标记表格上不配置的RoleSelect场景 // 2018.12.11 - RoleSelect场景将使用源文件。之前复制流程会导致RoleSelect在Bundle中存在两份 var roleSelectName = GlobeVar.sceneRole + SceneDisassembly.sceneExtension; var roleSelect = (from assetPath in AssetDatabase.GetAllAssetPaths() where assetPath.StartsWith(SceneDisassembly.sceneRoot) where Path.GetFileName(assetPath) == roleSelectName select assetPath).First(); if (roleSelect == null) { Debug.LogError(string.Format("Cannot find role select scene {0} in path {1}", roleSelectName, SceneDisassembly.sceneRoot)); } else { EditorUtility.DisplayProgressBar("Bundle Scene", roleSelect, 1f); AddBundleDict(bundleDic, roleSelect, "scene/" + GlobeVar.sceneRole.ToLower()); } EditorUtility.ClearProgressBar(); } private static void ClassifyModelBundles(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("Model"), AssetConst.prefabExtension); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle Model", assetPath, (float) i / assetPaths.Count); var bundleName = ("model/" + Path.GetFileNameWithoutExtension(assetPath)).ToLower(); AddBundleDict(bundleDic, assetPath, bundleName); } EditorUtility.ClearProgressBar(); } private static void ClassifyEffectBundles(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("Effect"), AssetConst.prefabExtension); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle Effect", assetPath, (float) i / assetPaths.Count); var bundleName = ("effect/" + Path.GetFileNameWithoutExtension(assetPath)).ToLower(); AddBundleDict(bundleDic, assetPath, bundleName); } EditorUtility.ClearProgressBar(); } private static void ClassifyOtherBundles(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("Other"), string.Empty); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; if (!string.IsNullOrEmpty(Path.GetExtension(assetPath))) { EditorUtility.DisplayProgressBar("Bundle Other", assetPath, (float) i / assetPaths.Count); var bundleName = ("other/" + Path.GetFileNameWithoutExtension(assetPath)).ToLower(); AddBundleDict(bundleDic, assetPath, bundleName); } } EditorUtility.ClearProgressBar(); } private static void ClassifyGameRes(Dictionary bundleDic) { var assetPaths = GetAssetPathForType("Assets/Project/GameRes/", string.Empty); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; if (!assetPath.StartsWith(dummyMaterialPath)) { var extension = Path.GetExtension(assetPath); if (!string.IsNullOrEmpty(extension) && !extension.Equals(".cginc", StringComparison.OrdinalIgnoreCase)) { EditorUtility.DisplayProgressBar("Bundle GameRes", assetPath, (float) i / assetPaths.Count); if (!string.IsNullOrEmpty(Path.GetExtension(assetPath))) AddBundleDict(bundleDic, assetPath, LoadAssetBundle.gameResBundleName); } } } EditorUtility.ClearProgressBar(); } private static void ClassifyTable(Dictionary bundleDic) { var assetPaths = GetAssetPathForType(LoadAssetBundle.tablePath, ".txt"); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle Table", assetPath, (float) i / assetPaths.Count); AddBundleDict(bundleDic, assetPath, "tables"); } EditorUtility.ClearProgressBar(); } private static void ClassifyScript(Dictionary bundleDic) { var assetPaths = GetAssetPathForType("Assets".Open("Project").Open("Script").Open("LuaScripts"), ".txt"); for (var i = 0; i < assetPaths.Count; i++) { var assetPath = assetPaths[i]; EditorUtility.DisplayProgressBar("Bundle Scripts", assetPath, (float) i / assetPaths.Count); AddBundleDict(bundleDic, assetPath, "script"); } EditorUtility.ClearProgressBar(); } private static List GetAssetsInBundles() { AssetDatabase.RemoveUnusedAssetBundleNames(); var result = new List(); var bundleNames = AssetDatabase.GetAllAssetBundleNames(); for (var i = 0; i < bundleNames.Length; i++) { var bundleAssets = AssetDatabase.GetAssetPathsFromAssetBundle(bundleNames[i]); for (var j = 0; j < bundleAssets.Length; j++) if (!result.Contains(bundleAssets[j])) result.Add(bundleAssets[j]); } return result; } // 剔除内置资源的关联 private static void TrimCSAssets(IDictionary bundleDict) { // 注:简单复制Key列表 var keys = bundleDict.Keys.ToArray(); foreach (var key in keys) if (key.StartsWith(AssetConst.csAssetHeader)) bundleDict.Remove(key); } private static void TrimUnusedAssetBundleName(IDictionary bundleDict) { var usingAssets = GetAssetsInBundles(); var trimList = new List(); for (var i = 0; i < usingAssets.Count; i++) { var assetPath = usingAssets[i]; if (!assetPath.StartsWith(dummyMaterialPath) && !bundleDict.ContainsKey(assetPath)) trimList.Add(assetPath); } for (var i = 0; i < trimList.Count; i++) { EditorUtility.DisplayProgressBar("Remove Asset from Bundle", trimList[i], (float) i / trimList.Count); UpdateBundleName(trimList[i], string.Empty); UpdateSpriteTag(trimList[i], string.Empty); } } // 注:Unity支持一个Bundle中有不同类型的同名资源,因此这个方法现在仅仅报警,不会强制修改资源BundleName private static void ConflictBundleRes() { var bundles = AssetDatabase.GetAllAssetBundleNames(); foreach (var bundle in bundles) { var assets = AssetDatabase.GetAssetPathsFromAssetBundle(bundle); var assetRefs = new BundleAssetRef[assets.Length]; for (var i = 0; i < assetRefs.Length; i++) assetRefs[i] = new BundleAssetRef(assets[i]); for (var i = 0; i < assetRefs.Length; i++) { var source = assetRefs[i]; for (var j = i + 1; j < assetRefs.Length; j++) { var target = assetRefs[j]; if (source.assetName == target.assetName && source.assetType == target.assetType) Debug.LogError(bundle + " 包含同类重名资源 " + source.assetPath + " 以及 " + target.assetPath + " ,AssetBundle将无法编译!"); } } } } private static void UpdateBundleName(string assetPath, string bundleName) { string variant; if (string.IsNullOrEmpty(bundleName)) { bundleName = string.Empty; variant = string.Empty; } else variant = AssetConst.bundleVariant.Substring(1); var filePath = EditorCommonUtility.AssetToFilePath(assetPath + AssetConst.metaExtension); var text = File.ReadAllText(filePath); text = _variantRegex.Replace(_bundleRegex.Replace(text, bundleName), variant); File.WriteAllText(filePath, text); } private static void UpdateSpriteTag(string assetPath, string spriteTag) { var filePath = EditorCommonUtility.AssetToFilePath(assetPath + AssetConst.metaExtension); var text = File.ReadAllText(filePath); text = _spriteRegex.Replace(text, spriteTag); File.WriteAllText(filePath, text); } private class BundleAssetRef { public readonly string assetName; public readonly string assetPath; public readonly Type assetType; public BundleAssetRef(string assetPath) { this.assetPath = assetPath; assetName = Path.GetFileNameWithoutExtension(assetPath); assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath); } } public class AssetImporterBundleSetting : ListItemBase { public string bundleName; public readonly HashSet refSet = new HashSet(); private readonly AssetImporter _importer; public static AssetImporterBundleSetting Create(string assetPath) { var importer = AssetImporter.GetAtPath(assetPath); var result = importer ? new AssetImporterBundleSetting(importer) : null; if (result == null) Debug.LogError(string.Format("Unable to get AssetImporter at path {0}!", assetPath)); return result; } private AssetImporterBundleSetting(AssetImporter importer) { id = importer.assetPath; _importer = importer; } public Type SetAssetImporter() { var assetType = AssetDatabase.GetMainAssetTypeAtPath(id); // 特殊处理贴图类型的资源 if (assetType == typeof(Texture2D)) SetTextureImporter(); else if (assetType == typeof(AudioClip)) SetImporter(); else if (assetType == typeof(Material)) SetImporter(); else if (assetType == typeof(TimelineAsset)) SetImporter(); // 特殊处理Shader类型的资源,统一推送到GameRes挂住 else if (assetType == typeof(Shader)) { bundleName = LoadAssetBundle.gameResBundleName; SetImporter(); } else if (assetType == typeof(LightingDataAsset)) SetLightingDataImporter(); else SetImporter(); return assetType; } private void SetImporter() { if (string.IsNullOrEmpty(bundleName) && refSet.Count > 1) bundleName = GetDependencyBundleName(id); var oldName = AssetDatabase.GetImplicitAssetBundleName(id); if (oldName != bundleName) SetImporterBundleName(); } private void SetTextureImporter() { var textureImporter = _importer as TextureImporter; if (textureImporter == null) { SetImporter(); Debug.LogError(id + "的资源不是贴图,但是被判定为贴图!"); } else { SetTextureImporter(textureImporter); } } // SpriteTag策略 - 指定Tag的情况下,使用指定的SpriteTag;不指定Tag的情况下,使用所在文件夹路径的Hash值; private void SetTextureImporter(TextureImporter textureImporter) { // 跳过Lightmap类型,光照贴图由LightingDataAsset直接标记 if (textureImporter.textureType == TextureImporterType.Lightmap) return; var spriteTag = string.Empty; // 特殊处理Sprite系的BundleName,使其可以和同路径下的其他Sprite形成图集 if (textureImporter.textureType == TextureImporterType.Sprite) { if (textureImporter.assetPath.StartsWith(EditorAssetConst.emojiSpritePath)) { bundleName = "emoji"; spriteTag = bundleName; } else if (string.IsNullOrEmpty(bundleName)) { bundleName = GetDependencyBundleName(Path.GetDirectoryName(textureImporter.assetPath)); bundleName = LoadAssetBundle.uiSpriteBundleHeader + bundleName; spriteTag = Path.GetFileNameWithoutExtension(bundleName); } else { spriteTag = Path.GetFileNameWithoutExtension(bundleName); } } else { if (string.IsNullOrEmpty(bundleName)) if (refSet.Count > 1) bundleName = GetDependencyBundleName(textureImporter.assetPath); } SetImporterBundleName(); if (textureImporter.spritePackingTag != spriteTag) UpdateSpriteTag(textureImporter.assetPath, spriteTag); } // 注:特殊处理LightingDataAsset - 这个编辑器类型不能打包到AssetBundle中,因此其包含的Lightmap文件需要独立标记 // Lightmap系资源不会被标记两次 - SetTextureImporter会直接跳过Lightmap类型 private void SetLightingDataImporter() { if (string.IsNullOrEmpty(bundleName) && refSet.Count > 1) bundleName = GetDependencyBundleName(id); SetImporterBundleName(); var dependencies = AssetDatabase.GetDependencies(id); for (var i = 0; i < dependencies.Length; i++) { var textureImporter = AssetImporter.GetAtPath(dependencies[i]) as TextureImporter; if (textureImporter != null && textureImporter.textureType == TextureImporterType.Lightmap) UpdateBundleName(dependencies[i], string.Empty); } } private void SetImporterBundleName() { if (_importer.assetBundleName != bundleName) UpdateBundleName(_importer.assetPath, bundleName); } } #region texture import modify private static string GetDependencyBundleName(string assetPath) { // 增加一段减少单路径下资源数 var hash = GetStringHash(assetPath); return dependBundleHeader + string.Format("{0}/{1}", hash[0], hash); } #endregion #region 2018.08.13 Scene Bundle private static void CollectBundleDependencies(Dictionary dictionary) { var activeItems = dictionary.Values.ToArray(); for (var i = 0; i < activeItems.Length; i++) { var activeItem = activeItems[i]; EditorUtility.DisplayProgressBar("Collect Dependency", activeItem.id, (float) i / activeItems.Length); var extension = Path.GetExtension(activeItem.id); // 注:场景的依赖将在ClassifyScene的流程中用特殊方式进行扫描 if (!string.IsNullOrEmpty(extension)) AddDependenciesForOneAsset(dictionary, activeItem.id); } EditorUtility.ClearProgressBar(); } // 注:可能资源自己依赖自己路径内的子资源,但是这个情况会在装入字典时被过滤,因此没有问题 private static void AddDependenciesForOneAsset(IDictionary bundleDict, string assetPath) { var dependencies = AssetDatabase.GetDependencies(assetPath, false); var uniquePathList = new List(); for (var i = 0; i < dependencies.Length; i++) if (!dependencies[i].Equals(assetPath)) { var assetType = AssetDatabase.GetMainAssetTypeAtPath(dependencies[i]); // 注:依赖包仅仅允许以下类型的资源 if (assetType == typeof(Texture2D) || assetType == typeof(GameObject) || assetType == typeof(Shader) || assetType == typeof(AnimatorController) || assetType == typeof(RuntimeAnimatorController) || assetType == typeof(AnimationClip) || assetType == typeof(Material) || assetType == typeof(AudioClip) || assetType == typeof(Font) || // 注:忽略LightingDataAsset 是Editor Only 资源之类的警告 // 不独立标记LightingDataAsset 会导致共享的Lightmap贴图被复制到多个Bundle中 assetType == typeof(LightingDataAsset) || // 实际应该会被GameObject包含 assetType == typeof(Mesh)) { AssetImporterBundleSetting record; if (!bundleDict.TryGetValue(dependencies[i], out record)) { record = AssetImporterBundleSetting.Create(dependencies[i]); bundleDict.Add(record.id, record); uniquePathList.Add(record.id); } record.refSet.Add(assetPath); } } for (var i = 0; i < uniquePathList.Count; i++) AddDependenciesForOneAsset(bundleDict, uniquePathList[i]); } public static void SetBundleNameOnImporters(Dictionary dictionary) { var shaderList = new List(); var assetImporterSettings = dictionary.Values.ToArray(); for (var i = 0; i < assetImporterSettings.Length; i++) { var importerSetting = assetImporterSettings[i]; EditorUtility.DisplayProgressBar("Reimport Assets", importerSetting.id, (float) i / assetImporterSettings.Length); if (importerSetting.SetAssetImporter() == typeof(Shader)) shaderList.Add(assetImporterSettings[i].id); } EditorUtility.DisplayProgressBar("Create Shader Collection", "Total Shaders: " + shaderList.Count, 1f); ShaderVariantTool.CreateAllDummyMaterials(shaderList, LoadAssetBundle.BUNDLE_PATH_DUMMY); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); EditorUtility.ClearProgressBar(); } private static void CheckInvalidPath(string assetPath, params string[] invalidPathParts) { var asset = AssetDatabase.LoadMainAssetAtPath(assetPath); if (asset == null) { Debug.LogError(string.Format("无法加载资源于路径{0}", assetPath)); } else { var items = EditorUtility.CollectDependencies(new[] {asset}); var uniquePathList = new List(); for (var i = 0; i < items.Length; i++) { var itemPath = AssetDatabase.GetAssetPath(items[i]); if (itemPath.IsCommonAssetPath() && !uniquePathList.Contains(itemPath)) uniquePathList.Add(itemPath); } for (var i = 0; i < uniquePathList.Count; i++) { var uniquePath = uniquePathList[i]; for (var j = 0; j < invalidPathParts.Length; j++) if (uniquePath.Contains(invalidPathParts[j], StringComparison.OrdinalIgnoreCase)) { Debug.LogError(string.Format("物品{0}引用过时资源{1}", assetPath, uniquePath)); break; } } } } [MenuItem("ProTool/ClassifyBundles/AllChange/Rebuild Atlas", false, 1)] public static void ClassifyAllChange_RebuildAtlas() { _spriteAdditive = false; _saveSpriteData = true; if (EditorUtility.DisplayDialog("完全重建图集", "这次操作将会放弃之前精灵图集数据,重建所有图集!\n这会导致大量资源包修改,仅仅用于大版本更新。", "确认", "取消")) StartClassifyAll(); } [MenuItem("ProTool/ClassifyBundles/AllChange/Add Sprite", false, 2)] public static void ClassifyAllChange_Additive() { _spriteAdditive = true; _saveSpriteData = true; StartClassifyAll(); } [MenuItem("ProTool/ClassifyBundles/AllChange/Test Build", false, 3)] public static void ClassifyAllChange_AdditiveNoSave() { _spriteAdditive = true; _saveSpriteData = false; if (EditorUtility.DisplayDialog("测试编译", "这次操作不会保存精灵图集数据!\n这会导致下次编译不继承精灵数据,仅仅用于测试版本。", "确认", "取消")) StartClassifyAll(); } private static void StartClassifyAll() { Debug.LogWarning("Classify All Start at " + DateTime.Now); DispatchScene(ClassifyAllBundles); } // [MenuItem("ProTool/ClassifyBundles/Classify Only", false, 5)] // public static void ClassifyWithoutScene() // { // ClassifyAllBundles(true, null); // } private static void ClassifyAllBundles(bool success, DisassembleAllScenesAction action) { if (success) { var bundleDict = new Dictionary(); ClassifySceneBundles(bundleDict); ClassifyModelBundles(bundleDict); ClassifyUiPrefabBundles(bundleDict); ClassifyEffectBundles(bundleDict); ClassifyUiPrefabMarketingActs(bundleDict); ClassifyUiPrefabEmoji(bundleDict); ClassifyOtherBundles(bundleDict); ClassifyUiTextureBundles(bundleDict); ClassifyUiSpriteBundles(bundleDict); ClassifySoundsBundles(bundleDict); ClassifyScript(bundleDict); ClassifyGameRes(bundleDict); ClassifyTable(bundleDict); CollectBundleDependencies(bundleDict); SetSpriteTag(bundleDict, OnSpriteGatherFinish); } } private static void OnSpriteGatherFinish(Dictionary bundleDict) { var action = new PurgeSpriteAction(bundleDict); action.onFinish += OnSpriteMarkFinish; action.Start(); } private static void OnSpriteMarkFinish(bool success, EditorPerFrameActionBase action) { var purgeAction = action as PurgeSpriteAction; if (purgeAction != null) { var bundleDict = purgeAction.bundleDict; TrimCSAssets(bundleDict); TrimUnusedAssetBundleName(bundleDict); SetBundleNameOnImporters(bundleDict); AssetDatabase.RemoveUnusedAssetBundleNames(); AssetDatabase.Refresh(); ConflictBundleRes(); Debug.LogWarning("Classify All Success at " + DateTime.Now); } } // private static string GetPlatformName() // { // #if UNITY_IOS || UNITY_IPHONE // const string platform = "iOS"; // #else // const string platform = "Android"; // #endif // return platform; // } // // [MenuItem("ProTool/Copy Bundles for Current", false, 2)] // public static void CopyCurrentPlatformBundles() // { // var toMove = new HashSet(); // var platform = GetPlatformName(); // var core = platform + LoadAssetBundle.bundleFileExtension; // const string dummyBundle = LoadAssetBundle.BUNDLE_PATH_DUMMY + LoadAssetBundle.bundleFileExtension; // var root = Application.dataPath.MoveUp().Open("AssetBundles/" + platform); // toMove.Add(core); // var manifestBundle = AssetBundle.LoadFromFile(root.Open(core)); // var manifest = manifestBundle.LoadAsset("AssetBundleManifest"); // var allBundles = manifest.GetAllAssetBundles(); // manifestBundle.Unload(false); // foreach (var bundle in allBundles) // if (!bundle.Equals(dummyBundle, StringComparison.Ordinal)) // toMove.Add(bundle); // var moveTarget = root.MoveUp().Open("ValidBundles"); // if (Directory.Exists(moveTarget)) // Directory.Delete(moveTarget, true); // foreach (var path in toMove) // { // var source = root.Open(path); // var target = moveTarget.Open(path); // var dir = Path.GetDirectoryName(target); // if (!Directory.Exists(dir)) // Directory.CreateDirectory(dir); // File.Copy(source, target, true); // } // } //private static void CopyItemsWithExtension(string sourceDir, string targetDir, string extension) //{ // var itemPaths = Directory.GetFiles(sourceDir, "*" + extension, SearchOption.AllDirectories); // foreach (var itemPath in itemPaths) // { // var targetPath = targetDir.Open(itemPath.Substring(sourceDir.Length)); // if (File.Exists(targetPath)) // File.Delete(targetPath); // CopyOneAssetBundle(itemPath, targetPath); // } //} // private static void CopyOneAssetBundle(string sourcePath, string targetPath) // { // var targetDir = Path.GetDirectoryName(targetPath); // if (!Directory.Exists(targetDir)) // Directory.CreateDirectory(targetDir); // File.Copy(sourcePath, targetPath); // // 不拷贝Manifest文件 // // const string manifestExtension = ".manifest"; // // File.Copy(sourcePath + manifestExtension, targetPath + manifestExtension); // } // 最后一次扫描时的有效场景记录 // 临时使用这种流程来实现场景扫描和Classify流程的分离 // 列表为空表示场景文件列表未初始化 private static List _sceneList; private static void RefreshSceneList() { _sceneList = new List(); var assetPaths = AssetDatabase.GetAllAssetPaths(); var sceneDataList = EditorTableManager.GetTable(); for (var i = 0; i < assetPaths.Length; i++) if (assetPaths[i].StartsWith(SceneDisassembly.sceneRoot)) { var extension = Path.GetExtension(assetPaths[i]); if (!string.IsNullOrEmpty(extension) && extension.Equals(SceneDisassembly.sceneExtension, StringComparison.OrdinalIgnoreCase)) { var sceneName = Path.GetFileNameWithoutExtension(assetPaths[i]); if (!string.IsNullOrEmpty(sceneName) && !sceneName.Equals(GlobeVar.sceneLogin, StringComparison.OrdinalIgnoreCase) && !sceneName.Equals(GlobeVar.sceneLoading, StringComparison.OrdinalIgnoreCase) && !sceneName.Equals(GlobeVar.sceneRole, StringComparison.OrdinalIgnoreCase)) { var sceneData = sceneDataList.Find(a => a.ResName == sceneName); if (sceneData == null) Debug.LogError(string.Format("场景{0}数据不存在!", sceneName)); else // 移除文件名前的路径 _sceneList.Add(assetPaths[i].Substring(SceneDisassembly.sceneRoot.Length + 1)); } } } } [MenuItem("ProTool/ClassifyBundles/DispatchScene", false, 4)] public static void DispatchScene() { DispatchScene(null); } [MenuItem("Bundle V2/BuiltinAssets/CopyBuiltinAssets")] public static void CopyBuiltinAssets() { BuiltinAssetBuilder.ExtractBuiltinAssets(); } [MenuItem("Bundle V2/BuiltinAssets/ReplaceWithBuiltinAssets")] public static void ReplaceBuiltInAssets() { BuiltinAssetBuilder.ReplaceForPath("Assets/"); } // 分解全部场景到场景路径 // 注:已知RoleSelect没有执行替换操作 public static void DispatchScene(UnityAction onFinish) { RefreshSceneList(); var action = new DisassembleAllScenesAction(_sceneList); if (onFinish != null) action.onFinish += onFinish; action.Start(); } public static List GetAssetPathForType(string headPath, string extension) { var result = new List(); var allAssetPaths = AssetDatabase.GetAllAssetPaths(); for (var i = 0; i < allAssetPaths.Length; i++) { var assetPath = allAssetPaths[i]; if (assetPath.StartsWith(headPath) && (string.IsNullOrEmpty(extension) || extension.Equals(Path.GetExtension(assetPath), StringComparison.OrdinalIgnoreCase))) result.Add(assetPath); } return result; } #endregion #region single menus // Single Menu仅仅提供增量处理,如果在运行一次全局之前直接使用Single Menu,可能产生不可预知的问题 // 这个操作可能会把部分有编号的Bundle洗为依赖包,慎用 private static void ClassifySingleMenu(UnityAction> action) { var bundleDict = new Dictionary(); action.Invoke(bundleDict); CollectBundleDependencies(bundleDict); SetBundleNameOnImporters(bundleDict); ConflictBundleRes(); } [MenuItem("ProTool/ClassifyBundles/Table")] public static void ClassifyTable() { ClassifySingleMenu(ClassifyTable); } [MenuItem("ProTool/ClassifyBundles/Script")] public static void ClassifyScript() { ClassifySingleMenu(ClassifyScript); } // [MenuItem("ProTool/ClassifyBundles/UI/Prefab")] // public static void ClassifyUIPrefab() // { // ClassifySingleMenu(ClassifyUiPrefabBundles); // } // // [MenuItem("ProTool/ClassifyBundles/GameRes")] // public static void ClassifyGameRes() // { // ClassifySingleMenu(ClassifyGameRes); // } // // [MenuItem("ProTool/ClassifyBundles/UI/MarketingActs")] // public static void ClassifyUIMarketingActPrefab() // { // ClassifySingleMenu(ClassifyUiPrefabMarketingActs); // } // // [MenuItem("ProTool/ClassifyBundles/UI/Emoji")] // public static void ClassifyUIEmoji() // { // ClassifySingleMenu(ClassifyUiPrefabEmoji); // } // // [MenuItem("ProTool/ClassifyBundles/UI/Sprite")] // public static void ClassifyUISprite() // { // ClassifySingleMenu(ClassifyUiSpriteBundles); // } // // [MenuItem("ProTool/ClassifyBundles/UI/Texture")] // public static void ClassifyUITexture() // { // ClassifySingleMenu(ClassifyUiTextureBundles); // } // // [MenuItem("ProTool/ClassifyBundles/Sounds")] // public static void ClassifySound() // { // ClassifySingleMenu(ClassifySoundsBundles); // } // // [MenuItem("ProTool/ClassifyBundles/Model")] // public static void ClassifyModel() // { // ClassifySingleMenu(ClassifyModelBundles); // } // // [MenuItem("ProTool/ClassifyBundles/Effect")] // public static void ClassifyEffect() // { // ClassifySingleMenu(ClassifyEffectBundles); // } // [MenuItem("ProTool/ClassifyBundles/Other")] // public static void ClassifyOther() // { // ClassifySingleMenu(ClassifyOtherBundles); // } #endregion #region Improved Sprite Collector // ****** 永久Ui元素 - 这些物体的Sprite将会强制输出到同一个Atlas上 ****** // 拥有这个Bundle名的 UiInfo中的记录,均记录为Permanent public const string permanentUiPath = "ui/mainbase"; // 除以上元素外,额外需要加入的部分 public static readonly UIPathData[] permanentUiPaths = { UIInfo.PlayerHeadInfo, UIInfo.DropItemHeadInfo, UIInfo.FellowHeadInfo, UIInfo.HarryHeadInfo, UIInfo.NPCHeadInfo, UIInfo.OtherPlayerHeadInfo, UIInfo.DamageBoard, UIInfo.AnchoredText, UIInfo.ExtraFunTipRoot, UIInfo.GuideRoot, UIInfo.LoadingTips, UIInfo.MarketingActsRoot, UIInfo.MissionClickPanel, UIInfo.RollNotice, UIInfo.SceneItemUseTip, UIInfo.VipIdoitTips }; // 数据的路径 public const string spriteDataPath = "Assets/Editor/Config/AtlasData.bytes"; public static string spriteDataFilePath { get { return Application.dataPath.MoveUp().Open(spriteDataPath); } } /// /// Atlas填充率 /// public const float atlasFillRatio = 0.9f; /// /// Altas最大边长 /// public const float atlasWidth = 2048; /// /// Altas最大尺寸 /// public const float atlasMaxSize = atlasWidth * atlasWidth * atlasFillRatio; // 暂时使用这种标记位来记录启动数据 // 是否采用增量分配Sprite Atlas private static bool _spriteAdditive; // 是否保存Sprite Atlas分配数据 private static bool _saveSpriteData; private static void SetSpriteTag(Dictionary bundleDict, UnityAction> afterAction) { var prefabs = from assetPath in bundleDict.Keys where ".prefab".Equals(Path.GetExtension(assetPath), StringComparison.OrdinalIgnoreCase) select assetPath; var action = new GatherSpriteAction(prefabs.ToArray(), bundleDict, afterAction); action.onFinish += OnGatherSpriteFinish; action.Start(); } private static void OnGatherSpriteFinish(bool isSuccess, EditorPerFrameActionBase action) { if (isSuccess) { var spriteAction = action as GatherSpriteAction; if (spriteAction == null) { Debug.LogError("Error on gather sprite action listening!"); } else { var bundleDict = spriteAction.bundleDict; // 裁剪CommonItem类型的Sprite套组 var permanentList = spriteAction.permanentSpritePaths .Where(a => !a.StartsWith(LoadAssetBundle.uiSpriteAssetPath)).ToArray(); foreach (var spriteId in permanentList) { AssetImporterBundleSetting setting; if (bundleDict.TryGetValue(spriteId, out setting)) setting.bundleName = uiPermanentBundle; } // 裁剪CommonItem类型的Sprite套组 var spriteList = spriteAction.spriteReferenceList; for (var i = spriteList.Count - 1; i >= 0; i--) if (spriteList[i].id.StartsWith(LoadAssetBundle.uiSpriteAssetPath)) spriteList.RemoveAt(i); Debug.LogWarning(string.Format("Sprite Distribution, Permanent {0}, Normal {1}", permanentList.Length, spriteList.Count)); // 按照引用位置整理Sprite列表 List stableGroupList = null; // 采用叠加模式打包时,预先载入原有Sprite分配流程 if (_spriteAdditive) if (File.Exists(spriteDataFilePath)) using (var fs = File.OpenRead(spriteDataFilePath)) { try { var xmlSerializer = new XmlSerializer(typeof(List)); stableGroupList = xmlSerializer.Deserialize(fs) as List; } catch (Exception e) { Debug.LogError(e.ToString()); stableGroupList = null; } } var groupListA = new List(); if (stableGroupList != null) { var allAssetPaths = (from assetPath in AssetDatabase.GetAllAssetPaths() where assetPath.IsCommonAssetPath() select assetPath).ToArray(); // Sprite增量打包流程 for (var i = stableGroupList.Count - 1; i >= 0; i--) { stableGroupList[i].PopulateGroupData(spriteList, allAssetPaths); // 需要重新组合的Atlas,则执行一次重组 if (stableGroupList[i].willRebuild) { groupListA.Add(stableGroupList[i]); stableGroupList.RemoveAt(i); } } } foreach (var reference in spriteAction.spriteReferenceList) { reference.referenceList.Sort(StringComparer.OrdinalIgnoreCase); var add = true; for (var i = 0; i < groupListA.Count; i++) if (groupListA[i].TryAddData(reference)) { add = false; break; } if (add) groupListA.Add(new SpriteReferenceGroupData(reference)); } for (var i = 0; i < groupListA.Count; i++) groupListA[i].SortList(); //var groupListB = new List(); // #1 试图合并距离最小的引用组 - 仅仅合并距离差距在1的组 // 执行两次,实际合并到差距为2 MergeAtlasByDistance(groupListA, 1); MergeAtlasByDistance(groupListA, 1); if (stableGroupList != null) for (var i = 0; i < stableGroupList.Count; i++) groupListA.Add(stableGroupList[i]); for (var i = 0; i < groupListA.Count; i++) groupListA[i].SetSpriteTags(bundleDict); if (_saveSpriteData) { var directory = Path.GetDirectoryName(spriteDataFilePath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) Directory.CreateDirectory(directory); using (var fs = File.Create(spriteDataFilePath)) { var xmlSerializer = new XmlSerializer(typeof(List)); xmlSerializer.Serialize(fs, groupListA); } } if (spriteAction.afterAction != null) spriteAction.afterAction.Invoke(bundleDict); } } } private static void MergeAtlasByDistance(List groupList, int maxDist) { var mergeList = new List(); for (var i = 0; i < groupList.Count - 1; i++) for (var j = i + 1; j < groupList.Count; j++) { var merge = new SpriteReferenceDistance(groupList[i], groupList[j]); if (merge.distance <= maxDist && merge.size < atlasMaxSize) mergeList.Add(merge); } mergeList.Sort(); while (mergeList.Count > 0) if (mergeList[0].size > atlasMaxSize) { mergeList.RemoveAt(0); } else { var merge = mergeList[0]; mergeList.RemoveAt(0); merge.a.MergeGroup(merge.b); groupList.Remove(merge.b); for (var i = mergeList.Count - 1; i >= 0; i--) if (mergeList[i].Contains(merge.a) || mergeList[i].Contains(merge.b)) mergeList.RemoveAt(i); } } private class PurgeSpriteAction : EditorPerFrameActionBase { private readonly string[] _assetPaths; public readonly Dictionary bundleDict; private int _index; public PurgeSpriteAction(Dictionary bundleDict) { this.bundleDict = bundleDict; var assetPaths = from assetPath in AssetDatabase.GetAllAssetPaths() where assetPath.IsCommonAssetPath() let extension = Path.GetExtension(assetPath) where ".png".Equals(extension, StringComparison.OrdinalIgnoreCase) || ".jpg".Equals(extension, StringComparison.OrdinalIgnoreCase) || ".tga".Equals(extension, StringComparison.OrdinalIgnoreCase) where !bundleDict.ContainsKey(assetPath) select assetPath; _assetPaths = assetPaths.ToArray(); } protected override int GetCurrentIndex() { return _index; } protected override int GetTotalCount() { return _assetPaths.Length; } protected override void PerFrameAction() { var assetPath = _assetPaths[_index]; var importer = AssetImporter.GetAtPath(assetPath) as TextureImporter; if (importer != null && !string.IsNullOrEmpty(importer.spritePackingTag)) { importer.spritePackingTag = string.Empty; importer.SaveAndReimport(); } _index++; if (_index % 100 == 0) Resources.UnloadUnusedAssets(); } } private class GatherSpriteAction : EditorPerFrameActionBase { // 永久存在的Ui窗口 - 将强制输出到同一张Atlas上 private readonly string[] _permanentPrefabPaths; // 非永久存在的Ui窗口 private readonly string[] _prefabPaths; public readonly UnityAction> afterAction; public readonly Dictionary bundleDict; public readonly HashSet permanentSpritePaths = new HashSet(); public readonly List spriteReferenceList = new List(); private int _index; public GatherSpriteAction(string[] assetPaths, Dictionary bundleDict, UnityAction> afterAction) { var permanentNames = new HashSet(); foreach (var uiPath in permanentUiPaths) permanentNames.Add(uiPath.name); var fields = typeof(UIInfo).GetFields(BindingFlags.Public | BindingFlags.Static); for (var i = 0; i < fields.Length; i++) { var pathData = fields[i].GetValue(null) as UIPathData; if (pathData != null && pathData.path.ToLower() == permanentUiPath) permanentNames.Add(pathData.name); } _permanentPrefabPaths = (from assetPath in assetPaths let fileName = Path.GetFileNameWithoutExtension(assetPath) where permanentNames.Contains(fileName) select assetPath).ToArray(); _prefabPaths = assetPaths.Except(_permanentPrefabPaths).Where(a => !a.Contains("/UI/Prefab/Emoji/")).ToArray(); Debug.LogWarning(string.Format("Total Ui Prefab Count {0}, Permanent Prefab Count {1}, Normal Count {2}", assetPaths.Length, _permanentPrefabPaths.Length, _prefabPaths.Length)); this.bundleDict = bundleDict; this.afterAction = afterAction; } protected override int GetCurrentIndex() { return _index; } protected override int GetTotalCount() { return _prefabPaths.Length + _permanentPrefabPaths.Length; } protected override void PerFrameAction() { string assetPath; bool isPermanent; if (_index < _permanentPrefabPaths.Length) { assetPath = _permanentPrefabPaths[_index]; isPermanent = true; } else { assetPath = _prefabPaths[_index - _permanentPrefabPaths.Length]; isPermanent = false; } _index++; var prefab = AssetDatabase.LoadAssetAtPath(assetPath); var dependencies = EditorUtility.CollectDependencies(new Object[] {prefab}); for (var i = 0; i < dependencies.Length; i++) { var texture = dependencies[i] as Texture2D; if (texture != null) { var texturePath = AssetDatabase.GetAssetPath(texture); var textureImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter; if (textureImporter != null && textureImporter.textureType == TextureImporterType.Sprite) if (isPermanent) { permanentSpritePaths.Add(texturePath); } else if (!permanentSpritePaths.Contains(texturePath)) { var data = spriteReferenceList.GetItem(texturePath, ListGetMode.create); data.width = texture.width; data.height = texture.height; if (!data.referenceList.Contains(assetPath)) data.referenceList.Add(assetPath); } } } if (_index % 100 == 0) Resources.UnloadUnusedAssets(); } } public class SpriteReferenceData : ListItemBase { public readonly List referenceList = new List(); public int height; public int width; } [Serializable] public class SpriteReferenceGroupData { public List referenceList; [XmlIgnore] public float size; public List spriteList; [XmlIgnore] public bool willRebuild; [XmlIgnore] private List _spriteNameList; public uint? atlasHash; public SpriteReferenceGroupData() { } public SpriteReferenceGroupData(SpriteReferenceData firstData) { spriteList = new List(); referenceList = firstData.referenceList; AddData(firstData); willRebuild = true; } /// /// 初始化引用组的数据 /// public void PopulateGroupData(List allSpriteList, string[] allAssetPaths) { for (var i = spriteList.Count - 1; i >= 0; i--) { var valid = false; var sprite = spriteList[i]; // 仅仅需要Sprite存在,不试图移除旧Sprite if (allAssetPaths.Contains(sprite)) { var importer = AssetImporter.GetAtPath(sprite) as TextureImporter; if (importer != null && importer.textureType == TextureImporterType.Sprite) { var args = new object[2]; var mi = typeof(TextureImporter).GetMethod("GetWidthAndHeight", BindingFlags.NonPublic | BindingFlags.Instance); if (mi != null) mi.Invoke(importer, args); // 粗略计算Atlas尺寸 if (args[0] == null || args[1] == null) { Debug.LogError("无法获得Sprite尺寸,于" + importer.assetPath); } else { size += (int) args[0] * (int) args[1]; valid = true; } } } if (valid) { var index = allSpriteList.GetIndex(sprite); // 如果列表中的Sprite不再存在,则Atlas需要重建 if (index >= 0) allSpriteList.RemoveAt(index); } else { // 如果Sprite不正常,allSpriteList必然也没有记录 willRebuild = true; spriteList.RemoveAt(i); } } if (willRebuild) Debug.LogWarning("Will Rebuild Atlas " + (atlasHash == null ? "Null" : atlasHash.Value.ToString())); } public void MergeGroup(SpriteReferenceGroupData groupData) { for (var i = 0; i < groupData.referenceList.Count; i++) { var reference = groupData.referenceList[i]; if (!referenceList.Contains(reference)) referenceList.Add(reference); } for (var i = 0; i < groupData.spriteList.Count; i++) { var sprite = groupData.spriteList[i]; spriteList.Add(sprite); } size += groupData.size; } public bool TryAddData(SpriteReferenceData data) { var result = MatchList(data.referenceList); if (result) AddData(data); return result; } private void AddData(SpriteReferenceData data) { spriteList.Add(data.id); size += data.width * data.height; } private bool MatchList(IList list) { var result = true; if (list.Count != referenceList.Count) result = false; else for (var i = 0; i < list.Count; i++) if (list[i] != referenceList[i]) { result = false; break; } return result; } public bool Contain(SpriteReferenceGroupData other) { var index = 0; var contain = false; for (var i = 0; i < other.referenceList.Count; i++) { contain = false; while (index < referenceList.Count) if (referenceList[index] == other.referenceList[i]) { index++; contain = true; break; } else { index++; } if (!contain) break; } return contain; } public bool CheckSpriteName(SpriteReferenceGroupData other) { var index = 0; var otherIndex = 0; var result = true; while (index < _spriteNameList.Count && otherIndex < other._spriteNameList.Count) { var compare = string.CompareOrdinal(_spriteNameList[index], other._spriteNameList[otherIndex]); if (compare < 0) { index++; } else if (compare > 0) { otherIndex++; } else { result = false; break; } } return result; } public void SortList() { spriteList.Sort(StringComparer.Ordinal); referenceList.Sort(StringComparer.Ordinal); _spriteNameList = new List(); for (var i = 0; i < spriteList.Count; i++) _spriteNameList.Add(Path.GetFileNameWithoutExtension(spriteList[i])); _spriteNameList.Sort(StringComparer.Ordinal); } public bool Clamp(SpriteReferenceGroupData other) { var intersect = referenceList.Intersect(other.referenceList); return intersect.Any(); } public void SetSpriteTags(Dictionary bundleDict) { if (atlasHash == null) { var hash = 0; foreach (var item in referenceList) unchecked { hash += Animator.StringToHash(item); } atlasHash = unchecked((uint) hash); } var bundleName = atlasHash.Value.ToString(); bundleName = LoadAssetBundle.uiSpriteBundleHeader + bundleName; // 试图重命名名称重复的Sprite var list = new List>(); for (var i = 0; i < spriteList.Count; i++) list.Add(new MyTuple(Path.GetFileNameWithoutExtension(spriteList[i]), spriteList[i])); for (var i = 0; i < list.Count; i++) { EditorUtility.DisplayProgressBar("Valid sprite for atlas " + bundleName, string.Format("{0} / {1}", i, list.Count), (float) i / list.Count); // 注:默认文件系统是不可能有重复项的 if (DuplicateInList(list, list[i].first) > 1) { var assetPath = list[i].second; var folder = assetPath.MoveUp(); var extension = Path.GetExtension(assetPath); var fileName = Path.GetFileNameWithoutExtension(assetPath); System.Diagnostics.Debug.Assert(fileName != null, "fileName != null"); var index = fileName.LastIndexOf('('); if (index >= 0) fileName = fileName.Remove(index); var count = 1; string newFileName; string newPath; do { newFileName = string.Format("{0}({1})", fileName, count); newPath = folder.Open(newFileName) + extension; if (DuplicateInList(list, newFileName) < 1) { var assets = from path in AssetDatabase.GetAllAssetPaths() where path.StartsWith(folder) select path; if (!assets.Contains(newPath)) break; } count++; } while (true); list[i].first = newFileName; list[i].second = newPath; RenameAsset(bundleDict, assetPath, newPath); } } EditorUtility.ClearProgressBar(); for (var i = 0; i < list.Count; i++) { AssetImporterBundleSetting bundleSetting; if (bundleDict.TryGetValue(list[i].second, out bundleSetting)) bundleSetting.bundleName = bundleName; } } private int DuplicateInList(List> list, string fileName) { var count = 0; for (var i = 0; i < list.Count; i++) if (list[i].first == fileName) count++; return count; } private void RenameAsset(Dictionary bundleDict, string oldPath, string newPath) { AssetDatabase.MoveAsset(oldPath, newPath); AssetImporterBundleSetting bundleSetting; if (bundleDict.TryGetValue(oldPath, out bundleSetting)) { bundleDict.Remove(oldPath); bundleSetting.id = newPath; bundleDict.Add(newPath, bundleSetting); } Debug.LogWarning(string.Format("Rename sprite {0} to {1}", oldPath, newPath)); } } public class SpriteReferenceDistance : IComparable { public readonly SpriteReferenceGroupData a; public readonly SpriteReferenceGroupData b; public readonly int count; public readonly int distance; public readonly float size; public SpriteReferenceDistance(SpriteReferenceGroupData a, SpriteReferenceGroupData b) { this.a = a; this.b = b; var temp = a.referenceList.Union(b.referenceList); count = temp.Count(); // 注:距离按照最大可能附带的额外Sprite计算,因此使用Max而不是加法 distance = Mathf.Max(count - a.referenceList.Count, count - b.referenceList.Count); size = a.size + b.size; } public int CompareTo(SpriteReferenceDistance other) { var result = distance.CompareTo(other.distance); // 尽量合并所需合并次数最低的Sprite if (result == 0) result = -size.CompareTo(other.size); if (result == 0) result = -count.CompareTo(other.count); return result; } public bool Contains(SpriteReferenceGroupData data) { return a == data || b == data; } } #endregion }