Files
JJBB/Assets/Editor/Scripts/ClassifyBundles.cs
2024-08-23 15:49:34 +08:00

1722 lines
69 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> bundleDic)
{
var assetPaths = GetAssetPathForType(LoadAssetBundle.assetPathHeader.Open("UI").Open("Prefab"), ".prefab");
var uiFileNames = new List<string>();
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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> bundleDic)
{
// 2019.02.28原“ui/sprite/commonitem”Bundle下资源按文件夹细分到各自Bundle保持原有调用接口
// 因此额外建立一个对比表格来转换原通用Bundle路径为各自路径
// 运行时在AssetLoadManager中进行实质Bundle名转换
var assetPaths = GetAssetPathForType(LoadAssetBundle.uiSpriteAssetPath, string.Empty);
var pathConvert = new Dictionary<string, string>();
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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> 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<string> GetAssetsInBundles()
{
AssetDatabase.RemoveUnusedAssetBundleNames();
var result = new List<string>();
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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> bundleDict)
{
var usingAssets = GetAssetsInBundles();
var trimList = new List<string>();
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<string>
{
public string bundleName;
public readonly HashSet<string> refSet = new HashSet<string>();
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<string, AssetImporterBundleSetting> 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<string, AssetImporterBundleSetting> bundleDict,
string assetPath)
{
var dependencies = AssetDatabase.GetDependencies(assetPath, false);
var uniquePathList = new List<string>();
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<string, AssetImporterBundleSetting> dictionary)
{
var shaderList = new List<string>();
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<string>();
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<string, AssetImporterBundleSetting>();
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<string, AssetImporterBundleSetting> 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<string>();
// 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>("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<string> _sceneList;
private static void RefreshSceneList()
{
_sceneList = new List<string>();
var assetPaths = AssetDatabase.GetAllAssetPaths();
var sceneDataList = EditorTableManager.GetTable<Tab_SceneClass>();
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<bool, DisassembleAllScenesAction> onFinish)
{
RefreshSceneList();
var action = new DisassembleAllScenesAction(_sceneList);
if (onFinish != null)
action.onFinish += onFinish;
action.Start();
}
public static List<string> GetAssetPathForType(string headPath, string extension)
{
var result = new List<string>();
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<Dictionary<string, AssetImporterBundleSetting>> action)
{
var bundleDict = new Dictionary<string, AssetImporterBundleSetting>();
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); }
}
/// <summary>
/// Atlas填充率
/// </summary>
public const float atlasFillRatio = 0.9f;
/// <summary>
/// Altas最大边长
/// </summary>
public const float atlasWidth = 2048;
/// <summary>
/// Altas最大尺寸
/// </summary>
public const float atlasMaxSize = atlasWidth * atlasWidth * atlasFillRatio;
// 暂时使用这种标记位来记录启动数据
// 是否采用增量分配Sprite Atlas
private static bool _spriteAdditive;
// 是否保存Sprite Atlas分配数据
private static bool _saveSpriteData;
private static void SetSpriteTag(Dictionary<string, AssetImporterBundleSetting> bundleDict,
UnityAction<Dictionary<string, AssetImporterBundleSetting>> 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<SpriteReferenceGroupData> stableGroupList = null;
// 采用叠加模式打包时预先载入原有Sprite分配流程
if (_spriteAdditive)
if (File.Exists(spriteDataFilePath))
using (var fs = File.OpenRead(spriteDataFilePath))
{
try
{
var xmlSerializer = new XmlSerializer(typeof(List<SpriteReferenceGroupData>));
stableGroupList = xmlSerializer.Deserialize(fs) as List<SpriteReferenceGroupData>;
}
catch (Exception e)
{
Debug.LogError(e.ToString());
stableGroupList = null;
}
}
var groupListA = new List<SpriteReferenceGroupData>();
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<SpriteReferenceGroupData>();
// #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<SpriteReferenceGroupData>));
xmlSerializer.Serialize(fs, groupListA);
}
}
if (spriteAction.afterAction != null)
spriteAction.afterAction.Invoke(bundleDict);
}
}
}
private static void MergeAtlasByDistance(List<SpriteReferenceGroupData> groupList, int maxDist)
{
var mergeList = new List<SpriteReferenceDistance>();
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<string, AssetImporterBundleSetting> bundleDict;
private int _index;
public PurgeSpriteAction(Dictionary<string, AssetImporterBundleSetting> 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<Dictionary<string, AssetImporterBundleSetting>> afterAction;
public readonly Dictionary<string, AssetImporterBundleSetting> bundleDict;
public readonly HashSet<string> permanentSpritePaths = new HashSet<string>();
public readonly List<SpriteReferenceData> spriteReferenceList = new List<SpriteReferenceData>();
private int _index;
public GatherSpriteAction(string[] assetPaths, Dictionary<string, AssetImporterBundleSetting> bundleDict,
UnityAction<Dictionary<string, AssetImporterBundleSetting>> afterAction)
{
var permanentNames = new HashSet<string>();
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<GameObject>(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<string>
{
public readonly List<string> referenceList = new List<string>();
public int height;
public int width;
}
[Serializable]
public class SpriteReferenceGroupData
{
public List<string> referenceList;
[XmlIgnore] public float size;
public List<string> spriteList;
[XmlIgnore] public bool willRebuild;
[XmlIgnore] private List<string> _spriteNameList;
public uint? atlasHash;
public SpriteReferenceGroupData()
{
}
public SpriteReferenceGroupData(SpriteReferenceData firstData)
{
spriteList = new List<string>();
referenceList = firstData.referenceList;
AddData(firstData);
willRebuild = true;
}
/// <summary>
/// 初始化引用组的数据
/// </summary>
public void PopulateGroupData(List<SpriteReferenceData> 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<string> 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<string>();
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<string, AssetImporterBundleSetting> 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<MyTuple<string, string>>();
for (var i = 0; i < spriteList.Count; i++)
list.Add(new MyTuple<string, string>(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<MyTuple<string, string>> 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<string, AssetImporterBundleSetting> 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<SpriteReferenceDistance>
{
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
}