using XGame.Framework.i18n; using XGame.Framework.Asset.Addressable; using XGame.Framework.Asset.Addressable.Data; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; namespace XGame.Editor.Asset { public static partial class AddressableHelper { private static readonly AddressableClassify[] QueryOrder = { AddressableClassify.Product, AddressableClassify.BuiltIn, AddressableClassify.Tests }; /// /// 检测AddressableId的唯一性 /// //[MenuItem("Tools/AssetBundle/VerifyAddressableId")] public static void VerifyAddressableIdUniqueness() { var assetInfoMap = new Dictionary(); VerifyAddressableIdUniqueness(LoadAssetManifest(AddressableClassify.BuiltIn), ref assetInfoMap); VerifyAddressableIdUniqueness(LoadAssetManifest(), ref assetInfoMap); VerifyAddressableIdUniqueness(LoadAssetManifest(AddressableClassify.Tests), ref assetInfoMap); Debug.Log("VerifyAddressableIdUniqueness completed."); } private static void VerifyAddressableIdUniqueness(AddressableInfosSo manifest, ref Dictionary assetInfoMap) { if (manifest == null || manifest.assetInfos == null) { //Debug.Log("Verify AddressableId Uniqueness. But can't find AddressableAssetInfoManifest."); return; } //key:addressableId ////var assetInfoMap = new Dictionary(); foreach (var assetInfo in manifest.assetInfos) { if (assetInfoMap.ContainsKey(assetInfo.addressableId)) { Debug.LogError($"AddressableId repeat. Last:{assetInfoMap[assetInfo.addressableId]} Next:{assetInfo}"); } else { assetInfoMap.Add(assetInfo.addressableId, assetInfo); } } } /// /// 资源目录 /// public static string[] GetAssetsRoots(AddressableClassify classify = AddressableClassify.Product) { var roots = new List(); switch (classify) { case AddressableClassify.BuiltIn: if (Directory.Exists(PathDefine.BuiltInResourcesRelative)) { roots.Add(PathDefine.BuiltInResourcesRelative); } break; case AddressableClassify.Tests: if (Directory.Exists(PathDefine.TestsToAddressable)) { roots.Add(PathDefine.TestsToAddressable); } if (Directory.Exists(PathDefine.BuiltInResTests)) { roots.Add(PathDefine.BuiltInResTests); } break; default: return FileUtil.GetProductAssetsRoots(true); //{ // if (Directory.Exists(PathDefine.ResAddressableRelative)) // { // roots.Add(PathDefine.ResAddressableRelative); // } // if (Directory.Exists(PathDefine.ResourcesRelative)) // { // roots.Add(PathDefine.ResourcesRelative); // } // if (Directory.Exists(PathDefine.I18nAssetsRelative)) // { // var directorys = Directory.GetDirectories(PathDefine.I18nAssetsRelative, PathDefine.ResAddressableName, SearchOption.AllDirectories); // foreach(var directory in directorys) // { // roots.Add(directory.Replace("\\", "/")); // } // } //} //break; } return roots.ToArray(); } public static bool TryAssetPathToClassify(string assetPath, out AddressableClassify classify) { classify = AddressableClassify.Product; if (string.IsNullOrEmpty(assetPath)) { return false; } //assetPath = FileUtil.ToRelativePath(assetPath); if (IsValidPath(assetPath, AddressableClassify.Product)) { //在 Res/Addressable || Resources 目录下 classify = AddressableClassify.Product; return true; } if (IsValidPath(assetPath, AddressableClassify.BuiltIn)) { //在Package Framework目录下 classify = AddressableClassify.BuiltIn; return true; } if (IsValidPath(assetPath, AddressableClassify.Tests)) { //在ResStatic目录下 classify = AddressableClassify.Tests; return true; } return false; } public static LanguageType GetLanguageTypeByAssetPath(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { return LanguageType.NONE; } var match = i18nAssetRegex.Match(assetPath); if (match.Success) { var i18nName = match.Groups[1].Value; return LanguageUtils.ToLanguageType(i18nName); } return LanguageType.NONE; } /// /// i18n资源路径正则 /// private static Regex i18nAssetRegex = new Regex(@"^Assets/i18n/(.+?)/Res/Addressable"); /// /// 是否是国际化资源 /// /// /// public static bool IsI18nAssetPath(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { return false; } if (i18nAssetRegex.IsMatch(assetPath)) { //在国际化资源目录下 return true; } return false; } /// /// 检测路径是否是指定类型的资源路径 /// /// /// /// public static bool IsValidPath(string assetPath, AddressableClassify classify) { if (string.IsNullOrEmpty(assetPath)) { return false; } switch (classify) { case AddressableClassify.BuiltIn: if (assetPath.StartsWith(PathDefine.XGamePackageRelative)) { //在Package Framework目录下 return true; } break; case AddressableClassify.Tests: if (assetPath.StartsWith(PathDefine.TestsToAddressable) || assetPath.StartsWith(PathDefine.BuiltInResTests)) { //在ResStatic目录下 或者在 在Package ResStatic目录下 return true; } break; default: if (assetPath.StartsWith(PathDefine.ResAddressableRelative)) { //在Res/Addressable目录下 return true; } if (assetPath.StartsWith(PathDefine.ResourcesRelative)) { //在Resources目录下 return true; } if (IsI18nAssetPath(assetPath)) { //在国际化资源目录下 return true; } break; } return false; } /// /// 检测一个资源路径是否有效 /// 用于判断指定Asset是否可以生成AddressableName /// /// 使用Unity的相对路径 /// public static bool IsValidPath(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { return false; } if (!File.Exists(assetPath)) { Debug.LogWarning($"File can't find. Path:{assetPath}"); return false; } assetPath = FileUtil.ToRelativePath(assetPath); if (IsValidPath(assetPath, AddressableClassify.Product)) { //项目资源 return true; } if (IsValidPath(assetPath, AddressableClassify.BuiltIn)) { //在Package Framework目录下 return true; } if (IsValidPath(assetPath, AddressableClassify.Tests)) { //在ResStatic目录下 return true; } return false; } public static bool IsInResources(string assetPath, out string relativePath) { relativePath = string.Empty; if (string.IsNullOrEmpty(assetPath)) { return false; } if (!File.Exists(assetPath)) { Debug.LogWarning($"File can't find. Path:{assetPath}"); return false; } //项目资源 assetPath = FileUtil.ToRelativePath(assetPath); var resRelative = $"{PathDefine.ResourcesRelative}/"; if (assetPath.StartsWith(resRelative)) { //在Resources目录下 relativePath = assetPath.Substring(resRelative.Length); relativePath = relativePath.Replace(Path.GetExtension(relativePath), ""); //Debug.LogWarning($"assetPath: {assetPath} GetDirectoryName: {Path.GetDirectoryName(assetPath)} ResourcesPath: {resRelative} file:{Path.GetFileNameWithoutExtension(assetPath)} relativePath:{relativePath}"); return true; } //包内资源 var pkgResources = $"{PathDefine.BuiltInResourcesRelative}/"; if (assetPath.StartsWith(pkgResources)) { //在Resources目录下 relativePath = assetPath.Substring(pkgResources.Length); relativePath = relativePath.Replace(Path.GetExtension(relativePath), ""); //Debug.LogWarning($"assetPath: {assetPath} GetDirectoryName: {Path.GetDirectoryName(assetPath)} ResourcesPath: {pkgResources} file:{Path.GetFileNameWithoutExtension(assetPath)} relativePath:{relativePath}"); return true; } return false; } /// /// 取文件名 /// 若名字为数字,则加上父文件夹名 /// /// /// public static string GetFileName(string assetPath) { if (string.IsNullOrEmpty(assetPath)) return string.Empty; var fileName = Path.GetFileNameWithoutExtension(assetPath); // ReSharper disable once UnusedVariable if (FileUtil.IsNumberic(fileName, out var result)) { fileName = $"{FileUtil.GetDirectoryName(assetPath, false)}_{fileName}"; } //删除空格,将'.'转为'_',然后转小写 fileName = fileName.Replace(" ", "").Replace('.', '_').ToLower(); return fileName; } public static string GetFileNameIncludeI18n(string assetPath) { var i18nName = string.Empty; var langFlag = GetLanguageTypeByAssetPath(assetPath); if (langFlag != LanguageType.NONE) { i18nName = langFlag.ToString().ToLower(); } var fileName = GetFileName(assetPath); if (langFlag != LanguageType.NONE) { fileName = $"{i18nName}_{fileName}"; } return fileName; } /// /// GUID转AddressableName /// /// /// public static string GUIDToName(string guid) { var addressableName = string.Empty; if (!string.IsNullOrEmpty(guid)) { var assetPath = AssetDatabase.GUIDToAssetPath(guid); if (TryAssetPathToClassify(assetPath, out var classify)) { LoadAssetManifest(classify)?.TryGetAddressableNameByGUID(guid, out addressableName); } } return addressableName; } /// /// AssetPath转AddressableName /// /// /// public static string AssetPathToName(string assetPath) { var addressableName = string.Empty; if (TryAssetPathToClassify(assetPath, out var classify)) { var guid = AssetDatabase.AssetPathToGUID(assetPath); LoadAssetManifest(classify)?.TryGetAddressableNameByGUID(guid, out addressableName); } return addressableName; } /// /// AddressableName转AssetPath /// /// /// public static string NameToAssetPath(string addressableName) { if (!string.IsNullOrEmpty(addressableName)) addressableName = addressableName.ToLower(); for (int i = 0; i < QueryOrder.Length; i++) { var assetPath = NameToAssetPath(addressableName, QueryOrder[i]); if (!string.IsNullOrEmpty(assetPath)) { return assetPath; } } return string.Empty; } private static string NameToAssetPath(string addressableName, AddressableClassify classify) { var manifest = LoadAssetManifest(classify); if (manifest != null && manifest.assetInfos != null) { var assetInfos = manifest.assetInfos; var index = System.Array.FindIndex(assetInfos, (info => info.addressableName.Equals(addressableName.ToLower()))); if (index >= 0) { return assetInfos[index].GetAssetPath(); } } return string.Empty; } /// /// LoadAsset /// /// /// /// public static T LoadAssetByName(string addressableName) where T : Object { var assetPath = NameToAssetPath(addressableName); if (string.IsNullOrEmpty(assetPath)) { return default(T); } return AssetDatabase.LoadAssetAtPath(assetPath); } /// /// 加载源资源(非克隆对象) /// /// /// /// public static T LoadMainAssetByName(string addressableName) where T : Object { var assetPath = NameToAssetPath(addressableName); if (string.IsNullOrEmpty(assetPath)) { return default(T); } return AssetDatabase.LoadMainAssetAtPath(assetPath) as T; } public static bool IsValidName(string addressableName) { var assetPath = NameToAssetPath(addressableName); return string.IsNullOrEmpty(assetPath) == false; } /// /// 根据assetPath判断是否LOD资源 /// /// /// internal static bool IsLodAssetPath(string assetPath) { if (string.IsNullOrEmpty(assetPath)) return false; var fileName = Path.GetFileNameWithoutExtension(assetPath).ToLower(); return fileName.EndsWith(PathDefine.LodHigh) || fileName.EndsWith(PathDefine.LodLow); } } }