using XGame.Editor.Asset; using XGame.Framework.Asset.Addressable.Data; using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using XGame.Framework.i18n; namespace XGame.Editor.Build.AssetBundles { /// /// assbundle的上下文 /// public class BundleContext { public readonly string ResStaticDir = $"/{PathDefine.ResStaticName}/"; public readonly string UiPrefabDir = "/UI/Prefabs/"; public readonly string EditorDir = "/Editor/"; public readonly string ResourcesDir = "/Resources/"; public readonly string PrefabExt = ".prefab"; public readonly string ShaderExt = ".shader"; /// /// key:guid /// value:AssetInfo /// private Dictionary _assetInfoMap; /// /// key:assetPath /// value:guid /// private Dictionary _assetPathToGuidMap; public Dictionary bundleDataMap; /// /// key:bundleId /// value:dependencies /// private Dictionary _bundleDependenciesMap; public HashSet bundleAssetPaths; public Dictionary dependenciesMap; #region Addressable资源 public string[] assetsRoots; /// /// 等待处理的资源 /// public HashSet waittingAssetPaths; #endregion /// /// 贴图资源没有依赖,缓存一份贴图路径数据节省一些判断 /// public HashSet allTexturePaths; public AssetBundleNameMode bundleNameMode; /// /// 是否合并shader /// public bool isMergeShader; /// /// ab分组名字 /// key:assetPath /// value:groupName /// public Dictionary groupNameMap; /// /// 排序好的AssetBundle数据 /// private List _bundleDataLst; /// /// 排序好的AssetBundle数据 /// public List BundleDataLst => _bundleDataLst; public BundleContext(Dictionary assetInfoMap) { _assetInfoMap = assetInfoMap; bundleDataMap = new Dictionary(); _bundleDependenciesMap = new Dictionary(); bundleAssetPaths = new HashSet(); dependenciesMap = new Dictionary(); waittingAssetPaths = new HashSet(); allTexturePaths = new HashSet(); groupNameMap = new Dictionary(); Init(); } private void Init() { _assetPathToGuidMap = new Dictionary(); foreach (var guid in _assetInfoMap.Keys) { var assetPath = AssetDatabase.GUIDToAssetPath(guid); if (!string.IsNullOrEmpty(assetPath)) { _assetPathToGuidMap.Add(assetPath, guid); } } } #region addressable public bool IsAddressableAsset(string guid) { return _assetInfoMap.ContainsKey(guid); } public bool TryGetAddressableNameByGuid(string guid, out string addressableName) { if (_assetInfoMap.TryGetValue(guid, out var info)) { addressableName = info.addressableName; return true; } addressableName = string.Empty; return false; } public bool TryGetAddressableNameByPath(string assetPath, out string addressableName) { if (_assetPathToGuidMap.TryGetValue(assetPath, out var guid)) { addressableName = _assetInfoMap[guid].addressableName; return true; } addressableName = string.Empty; return false; } /// /// key:assetPath /// value:addressableName /// /// public Dictionary GetAddressableInfoMap() { var map = new Dictionary(); foreach(var item in _assetPathToGuidMap) { var assetInfo = _assetInfoMap[item.Value]; if (!string.IsNullOrEmpty(assetInfo.relativePath)) { //忽略Resources的资源 continue; } map.Add(item.Key, assetInfo.addressableName); } return map; } /// /// 资源路径是否有效 /// /// /// public bool IsValidPath(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { return false; } foreach (var root in assetsRoots) { if (assetPath.StartsWith(root)) { return true; } } return false; } #endregion /// /// 支持addressable加载 /// 多语言的单独分文件夹 /// 根据分组配置划分文件夹 /// originGuid 用于生成bundleId,一般使用guid /// 默认为资源所属文件夹、图集为*.spriteatlas、单资源打包则为资源自己的guid /// /// /// public void AddAddressableBundle(string[] assetPaths, string originGuid) { string groupName; var firstAsset = assetPaths[0]; var langFlag = AddressableHelper.GetLanguageTypeByAssetPath(firstAsset); if (langFlag == LanguageType.NONE) { // 取Group配置 groupNameMap.TryGetValue(firstAsset, out groupName); } else { groupName = langFlag.ToString().ToLower(); } var originName = bundleNameMode == AssetBundleNameMode.Guid ? originGuid : AssetDatabase.GUIDToAssetPath(originGuid); var bundleId = Crc32.GetCrc32(originName); var count = assetPaths.Length; var addressableNames = new string[count]; var isTextureBundle = true; for (var i = 0; i < count; i++) { var assetPath = assetPaths[i]; if (isTextureBundle && allTexturePaths.Contains(assetPath) == false) { // 有一个资源非贴图 isTextureBundle = false; } if (TryGetAddressableNameByPath(assetPath, out var addressableName) == false) { Debug.LogWarning($"Asset can't find addressableName. Path:{assetPath}"); addressableName = assetPath; } addressableNames[i] = addressableName; // 移除等待 waittingAssetPaths.Remove(assetPath); } var bundle = new AssetBundleData() { assetBundleName = string.IsNullOrEmpty(groupName) ? bundleId.ToString() : $"{groupName}/{bundleId}", assetBundleVariant = Framework.Asset.Define.BUNDLE_VARIANT, assetNames = assetPaths, addressableNames = addressableNames, bundleId = bundleId, originBundleName = originName, isTextureBundle = isTextureBundle, bundleType = AssetBundleType.Addressable }; TryAddAssetBundle(ref bundle); } /// /// 普通的ab包,不支持addressable加载 /// 全部生成在根目录下,不支持分组 /// originName 用于生成bundleId,一般使用guid /// 默认为资源所属文件夹、图集为*.spriteatlas、单资源打包则为资源自己的guid /// /// /// /// /// public AssetBundleData AddRawAssetBundle(string[] assetPaths, string originName, bool isDependency) { if (bundleNameMode == AssetBundleNameMode.AssetPath && isDependency == false) { var path = AssetDatabase.GUIDToAssetPath(originName); if (string.IsNullOrEmpty(path)) { Log.Error($"AddRawAssetBundle originName is not guid. originName:{originName}"); } else { originName = path; } } var bundleId = Crc32.GetCrc32(originName); var bundle = new AssetBundleData() { assetBundleName = bundleId.ToString(), assetBundleVariant = Framework.Asset.Define.BUNDLE_VARIANT, assetNames = assetPaths, addressableNames = assetPaths, bundleId = bundleId, originBundleName = originName, isTextureBundle = IsTexturePaths(assetPaths), bundleType = isDependency ? AssetBundleType.Dependency : AssetBundleType.Raw }; TryAddAssetBundle(ref bundle); return bundle; } private bool IsTexturePaths(string[] assetPaths) { foreach(var assetPath in assetPaths) { if (allTexturePaths.Contains(assetPath) == false) { return false; } } return true; } private void TryAddAssetBundle(ref AssetBundleData bundle) { var bundleId = bundle.bundleId; bundleAssetPaths.UnionWith(bundle.assetNames); if (bundleDataMap.ContainsKey(bundleId)) { var lastName = bundleDataMap[bundleId].originBundleName; var nextName = bundle.originBundleName; Debug.LogError($"[XBuild] AssetBundleId重复. Id:{bundleId} LastName:{lastName} LastPath:{AssetDatabase.GUIDToAssetPath(lastName)} NextName:{nextName} NextPath:{AssetDatabase.GUIDToAssetPath(nextName)}"); return; } bundleDataMap.Add(bundleId, bundle); } private List tempLst = new List(); public string[] ToArrayBySort(HashSet assetPaths) { if (assetPaths.Count > 1) { tempLst.AddRange(assetPaths); tempLst.Sort((a, b) => string.Compare(a, b, StringComparison.OrdinalIgnoreCase)); var array = tempLst.ToArray(); tempLst.Clear(); return array; } return assetPaths.ToArray(); } /// /// 将所有AssetBundleData转成List并排序 /// public List SortAssetBundleDatas() { _bundleDataLst = bundleDataMap.Values.ToList(); _bundleDataLst.Sort(CompareAssetBundle); return _bundleDataLst; } public int CompareAssetBundle(AssetBundleData bundleA, AssetBundleData bundleB) { if (bundleNameMode == AssetBundleNameMode.AssetPath) { var guidA = AssetDatabase.AssetPathToGUID(bundleA.originBundleName); var guidB = AssetDatabase.AssetPathToGUID(bundleB.originBundleName); //有assetPath的bundle排前面 var idx_0 = string.IsNullOrEmpty(guidA) == false ? -1 : 1; var idx_1 = string.IsNullOrEmpty(guidB) == false ? -1 : 1; var remain = idx_0 - idx_1; if (remain != 0) { return remain; } } else { var pathA = AssetDatabase.GUIDToAssetPath(bundleA.originBundleName); var pathB = AssetDatabase.GUIDToAssetPath(bundleB.originBundleName); //有assetPath的bundle排前面 var idx_0 = string.IsNullOrEmpty(pathA) == false ? -1 : 1; var idx_1 = string.IsNullOrEmpty(pathB) == false ? -1 : 1; var remain = idx_0 - idx_1; if (remain != 0) { return remain; } if (idx_0 == -1) { return string.Compare(pathA, pathB, StringComparison.OrdinalIgnoreCase); } } return string.Compare(bundleA.originBundleName, bundleB.originBundleName, StringComparison.OrdinalIgnoreCase); } public System.Diagnostics.Stopwatch bundleDependenciesSw = new System.Diagnostics.Stopwatch(); public string[] GetDependencies(uint bundleId, string[] assetNames) { if (_bundleDependenciesMap.TryGetValue(bundleId, out var dependencies) == false) { bundleDependenciesSw.Start(); dependencies = AssetDatabase.GetDependencies(assetNames, true); bundleDependenciesSw.Stop(); _bundleDependenciesMap.Add(bundleId, dependencies); } return dependencies; } } }