AddressableHelper.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. using XGame.Framework.i18n;
  2. using XGame.Framework.Asset.Addressable;
  3. using XGame.Framework.Asset.Addressable.Data;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Text.RegularExpressions;
  7. using UnityEditor;
  8. using UnityEngine;
  9. namespace XGame.Editor.Asset
  10. {
  11. public static partial class AddressableHelper
  12. {
  13. private static readonly AddressableClassify[] QueryOrder = { AddressableClassify.Product, AddressableClassify.BuiltIn, AddressableClassify.Tests };
  14. /// <summary>
  15. /// 检测AddressableId的唯一性
  16. /// </summary>
  17. //[MenuItem("Tools/AssetBundle/VerifyAddressableId")]
  18. public static void VerifyAddressableIdUniqueness()
  19. {
  20. var assetInfoMap = new Dictionary<long, AssetInfo>();
  21. VerifyAddressableIdUniqueness(LoadAssetManifest(AddressableClassify.BuiltIn), ref assetInfoMap);
  22. VerifyAddressableIdUniqueness(LoadAssetManifest(), ref assetInfoMap);
  23. VerifyAddressableIdUniqueness(LoadAssetManifest(AddressableClassify.Tests), ref assetInfoMap);
  24. Debug.Log("VerifyAddressableIdUniqueness completed.");
  25. }
  26. private static void VerifyAddressableIdUniqueness(AddressableInfosSo manifest, ref Dictionary<long, AssetInfo> assetInfoMap)
  27. {
  28. if (manifest == null || manifest.assetInfos == null)
  29. {
  30. //Debug.Log("Verify AddressableId Uniqueness. But can't find AddressableAssetInfoManifest.");
  31. return;
  32. }
  33. //key:addressableId
  34. ////var assetInfoMap = new Dictionary<long, AddressableAssetInfo>();
  35. foreach (var assetInfo in manifest.assetInfos)
  36. {
  37. if (assetInfoMap.ContainsKey(assetInfo.addressableId))
  38. {
  39. Debug.LogError($"AddressableId repeat. Last:{assetInfoMap[assetInfo.addressableId]} Next:{assetInfo}");
  40. }
  41. else
  42. {
  43. assetInfoMap.Add(assetInfo.addressableId, assetInfo);
  44. }
  45. }
  46. }
  47. /// <summary>
  48. /// 资源目录
  49. /// </summary>
  50. public static string[] GetAssetsRoots(AddressableClassify classify = AddressableClassify.Product)
  51. {
  52. var roots = new List<string>();
  53. switch (classify)
  54. {
  55. case AddressableClassify.BuiltIn:
  56. if (Directory.Exists(PathDefine.BuiltInResourcesRelative))
  57. {
  58. roots.Add(PathDefine.BuiltInResourcesRelative);
  59. }
  60. break;
  61. case AddressableClassify.Tests:
  62. if (Directory.Exists(PathDefine.TestsToAddressable))
  63. {
  64. roots.Add(PathDefine.TestsToAddressable);
  65. }
  66. if (Directory.Exists(PathDefine.BuiltInResTests))
  67. {
  68. roots.Add(PathDefine.BuiltInResTests);
  69. }
  70. break;
  71. default:
  72. return FileUtil.GetProductAssetsRoots(true);
  73. //{
  74. // if (Directory.Exists(PathDefine.ResAddressableRelative))
  75. // {
  76. // roots.Add(PathDefine.ResAddressableRelative);
  77. // }
  78. // if (Directory.Exists(PathDefine.ResourcesRelative))
  79. // {
  80. // roots.Add(PathDefine.ResourcesRelative);
  81. // }
  82. // if (Directory.Exists(PathDefine.I18nAssetsRelative))
  83. // {
  84. // var directorys = Directory.GetDirectories(PathDefine.I18nAssetsRelative, PathDefine.ResAddressableName, SearchOption.AllDirectories);
  85. // foreach(var directory in directorys)
  86. // {
  87. // roots.Add(directory.Replace("\\", "/"));
  88. // }
  89. // }
  90. //}
  91. //break;
  92. }
  93. return roots.ToArray();
  94. }
  95. public static bool TryAssetPathToClassify(string assetPath, out AddressableClassify classify)
  96. {
  97. classify = AddressableClassify.Product;
  98. if (string.IsNullOrEmpty(assetPath))
  99. {
  100. return false;
  101. }
  102. //assetPath = FileUtil.ToRelativePath(assetPath);
  103. if (IsValidPath(assetPath, AddressableClassify.Product))
  104. {
  105. //在 Res/Addressable || Resources 目录下
  106. classify = AddressableClassify.Product;
  107. return true;
  108. }
  109. if (IsValidPath(assetPath, AddressableClassify.BuiltIn))
  110. {
  111. //在Package Framework目录下
  112. classify = AddressableClassify.BuiltIn;
  113. return true;
  114. }
  115. if (IsValidPath(assetPath, AddressableClassify.Tests))
  116. {
  117. //在ResStatic目录下
  118. classify = AddressableClassify.Tests;
  119. return true;
  120. }
  121. return false;
  122. }
  123. public static LanguageType GetLanguageTypeByAssetPath(string assetPath)
  124. {
  125. if (string.IsNullOrEmpty(assetPath))
  126. {
  127. return LanguageType.NONE;
  128. }
  129. var match = i18nAssetRegex.Match(assetPath);
  130. if (match.Success)
  131. {
  132. var i18nName = match.Groups[1].Value;
  133. return LanguageUtils.ToLanguageType(i18nName);
  134. }
  135. return LanguageType.NONE;
  136. }
  137. /// <summary>
  138. /// i18n资源路径正则
  139. /// </summary>
  140. private static Regex i18nAssetRegex = new Regex(@"^Assets/i18n/(.+?)/Res/Addressable");
  141. /// <summary>
  142. /// 是否是国际化资源
  143. /// </summary>
  144. /// <param name="assetPath"></param>
  145. /// <returns></returns>
  146. public static bool IsI18nAssetPath(string assetPath)
  147. {
  148. if (string.IsNullOrEmpty(assetPath))
  149. {
  150. return false;
  151. }
  152. if (i18nAssetRegex.IsMatch(assetPath))
  153. {
  154. //在国际化资源目录下
  155. return true;
  156. }
  157. return false;
  158. }
  159. /// <summary>
  160. /// 检测路径是否是指定类型的资源路径
  161. /// </summary>
  162. /// <param name="assetPath"></param>
  163. /// <param name="classify"></param>
  164. /// <returns></returns>
  165. public static bool IsValidPath(string assetPath, AddressableClassify classify)
  166. {
  167. if (string.IsNullOrEmpty(assetPath))
  168. {
  169. return false;
  170. }
  171. switch (classify)
  172. {
  173. case AddressableClassify.BuiltIn:
  174. if (assetPath.StartsWith(PathDefine.XGamePackageRelative))
  175. {
  176. //在Package Framework目录下
  177. return true;
  178. }
  179. break;
  180. case AddressableClassify.Tests:
  181. if (assetPath.StartsWith(PathDefine.TestsToAddressable) || assetPath.StartsWith(PathDefine.BuiltInResTests))
  182. {
  183. //在ResStatic目录下 或者在 在Package ResStatic目录下
  184. return true;
  185. }
  186. break;
  187. default:
  188. if (assetPath.StartsWith(PathDefine.ResAddressableRelative))
  189. {
  190. //在Res/Addressable目录下
  191. return true;
  192. }
  193. if (assetPath.StartsWith(PathDefine.ResourcesRelative))
  194. {
  195. //在Resources目录下
  196. return true;
  197. }
  198. if (IsI18nAssetPath(assetPath))
  199. {
  200. //在国际化资源目录下
  201. return true;
  202. }
  203. break;
  204. }
  205. return false;
  206. }
  207. /// <summary>
  208. /// 检测一个资源路径是否有效
  209. /// 用于判断指定Asset是否可以生成AddressableName
  210. /// </summary>
  211. /// <param name="assetPath">使用Unity的相对路径</param>
  212. /// <returns></returns>
  213. public static bool IsValidPath(string assetPath)
  214. {
  215. if (string.IsNullOrEmpty(assetPath))
  216. {
  217. return false;
  218. }
  219. if (!File.Exists(assetPath))
  220. {
  221. Debug.LogWarning($"File can't find. Path:{assetPath}");
  222. return false;
  223. }
  224. assetPath = FileUtil.ToRelativePath(assetPath);
  225. if (IsValidPath(assetPath, AddressableClassify.Product))
  226. {
  227. //项目资源
  228. return true;
  229. }
  230. if (IsValidPath(assetPath, AddressableClassify.BuiltIn))
  231. {
  232. //在Package Framework目录下
  233. return true;
  234. }
  235. if (IsValidPath(assetPath, AddressableClassify.Tests))
  236. {
  237. //在ResStatic目录下
  238. return true;
  239. }
  240. return false;
  241. }
  242. public static bool IsInResources(string assetPath, out string relativePath)
  243. {
  244. relativePath = string.Empty;
  245. if (string.IsNullOrEmpty(assetPath))
  246. {
  247. return false;
  248. }
  249. if (!File.Exists(assetPath))
  250. {
  251. Debug.LogWarning($"File can't find. Path:{assetPath}");
  252. return false;
  253. }
  254. //项目资源
  255. assetPath = FileUtil.ToRelativePath(assetPath);
  256. var resRelative = $"{PathDefine.ResourcesRelative}/";
  257. if (assetPath.StartsWith(resRelative))
  258. {
  259. //在Resources目录下
  260. relativePath = assetPath.Substring(resRelative.Length);
  261. relativePath = relativePath.Replace(Path.GetExtension(relativePath), "");
  262. //Debug.LogWarning($"assetPath: {assetPath} GetDirectoryName: {Path.GetDirectoryName(assetPath)} ResourcesPath: {resRelative} file:{Path.GetFileNameWithoutExtension(assetPath)} relativePath:{relativePath}");
  263. return true;
  264. }
  265. //包内资源
  266. var pkgResources = $"{PathDefine.BuiltInResourcesRelative}/";
  267. if (assetPath.StartsWith(pkgResources))
  268. {
  269. //在Resources目录下
  270. relativePath = assetPath.Substring(pkgResources.Length);
  271. relativePath = relativePath.Replace(Path.GetExtension(relativePath), "");
  272. //Debug.LogWarning($"assetPath: {assetPath} GetDirectoryName: {Path.GetDirectoryName(assetPath)} ResourcesPath: {pkgResources} file:{Path.GetFileNameWithoutExtension(assetPath)} relativePath:{relativePath}");
  273. return true;
  274. }
  275. return false;
  276. }
  277. /// <summary>
  278. /// 取文件名
  279. /// 若名字为数字,则加上父文件夹名
  280. /// </summary>
  281. /// <param name="assetPath"></param>
  282. /// <returns></returns>
  283. public static string GetFileName(string assetPath)
  284. {
  285. if (string.IsNullOrEmpty(assetPath))
  286. return string.Empty;
  287. var fileName = Path.GetFileNameWithoutExtension(assetPath);
  288. // ReSharper disable once UnusedVariable
  289. if (FileUtil.IsNumberic(fileName, out var result))
  290. {
  291. fileName = $"{FileUtil.GetDirectoryName(assetPath, false)}_{fileName}";
  292. }
  293. //删除空格,将'.'转为'_',然后转小写
  294. fileName = fileName.Replace(" ", "").Replace('.', '_').ToLower();
  295. return fileName;
  296. }
  297. public static string GetFileNameIncludeI18n(string assetPath)
  298. {
  299. var i18nName = string.Empty;
  300. var langFlag = GetLanguageTypeByAssetPath(assetPath);
  301. if (langFlag != LanguageType.NONE)
  302. {
  303. i18nName = langFlag.ToString().ToLower();
  304. }
  305. var fileName = GetFileName(assetPath);
  306. if (langFlag != LanguageType.NONE)
  307. {
  308. fileName = $"{i18nName}_{fileName}";
  309. }
  310. return fileName;
  311. }
  312. /// <summary>
  313. /// GUID转AddressableName
  314. /// </summary>
  315. /// <param name="guid"></param>
  316. /// <returns></returns>
  317. public static string GUIDToName(string guid)
  318. {
  319. var addressableName = string.Empty;
  320. if (!string.IsNullOrEmpty(guid))
  321. {
  322. var assetPath = AssetDatabase.GUIDToAssetPath(guid);
  323. if (TryAssetPathToClassify(assetPath, out var classify))
  324. {
  325. LoadAssetManifest(classify)?.TryGetAddressableNameByGUID(guid, out addressableName);
  326. }
  327. }
  328. return addressableName;
  329. }
  330. /// <summary>
  331. /// AssetPath转AddressableName
  332. /// </summary>
  333. /// <param name="assetPath"></param>
  334. /// <returns></returns>
  335. public static string AssetPathToName(string assetPath)
  336. {
  337. var addressableName = string.Empty;
  338. if (TryAssetPathToClassify(assetPath, out var classify))
  339. {
  340. var guid = AssetDatabase.AssetPathToGUID(assetPath);
  341. LoadAssetManifest(classify)?.TryGetAddressableNameByGUID(guid, out addressableName);
  342. }
  343. return addressableName;
  344. }
  345. /// <summary>
  346. /// AddressableName转AssetPath
  347. /// </summary>
  348. /// <param name="addressableName"></param>
  349. /// <returns></returns>
  350. public static string NameToAssetPath(string addressableName)
  351. {
  352. if (!string.IsNullOrEmpty(addressableName))
  353. addressableName = addressableName.ToLower();
  354. for (int i = 0; i < QueryOrder.Length; i++)
  355. {
  356. var assetPath = NameToAssetPath(addressableName, QueryOrder[i]);
  357. if (!string.IsNullOrEmpty(assetPath))
  358. {
  359. return assetPath;
  360. }
  361. }
  362. return string.Empty;
  363. }
  364. private static string NameToAssetPath(string addressableName, AddressableClassify classify)
  365. {
  366. var manifest = LoadAssetManifest(classify);
  367. if (manifest != null && manifest.assetInfos != null)
  368. {
  369. var assetInfos = manifest.assetInfos;
  370. var index = System.Array.FindIndex(assetInfos, (info => info.addressableName.Equals(addressableName.ToLower())));
  371. if (index >= 0)
  372. {
  373. return assetInfos[index].GetAssetPath();
  374. }
  375. }
  376. return string.Empty;
  377. }
  378. /// <summary>
  379. /// LoadAsset
  380. /// </summary>
  381. /// <typeparam name="T"></typeparam>
  382. /// <param name="addressableName"></param>
  383. /// <returns></returns>
  384. public static T LoadAssetByName<T>(string addressableName) where T : Object
  385. {
  386. var assetPath = NameToAssetPath(addressableName);
  387. if (string.IsNullOrEmpty(assetPath))
  388. {
  389. return default(T);
  390. }
  391. return AssetDatabase.LoadAssetAtPath<T>(assetPath);
  392. }
  393. /// <summary>
  394. /// 加载源资源(非克隆对象)
  395. /// </summary>
  396. /// <param name="addressableName"></param>
  397. /// <typeparam name="T"></typeparam>
  398. /// <returns></returns>
  399. public static T LoadMainAssetByName<T>(string addressableName) where T : Object
  400. {
  401. var assetPath = NameToAssetPath(addressableName);
  402. if (string.IsNullOrEmpty(assetPath))
  403. {
  404. return default(T);
  405. }
  406. return AssetDatabase.LoadMainAssetAtPath(assetPath) as T;
  407. }
  408. public static bool IsValidName(string addressableName)
  409. {
  410. var assetPath = NameToAssetPath(addressableName);
  411. return string.IsNullOrEmpty(assetPath) == false;
  412. }
  413. /// <summary>
  414. /// 根据assetPath判断是否LOD资源
  415. /// </summary>
  416. /// <param name="assetPath"></param>
  417. /// <returns></returns>
  418. internal static bool IsLodAssetPath(string assetPath)
  419. {
  420. if (string.IsNullOrEmpty(assetPath)) return false;
  421. var fileName = Path.GetFileNameWithoutExtension(assetPath).ToLower();
  422. return fileName.EndsWith(PathDefine.LodHigh) || fileName.EndsWith(PathDefine.LodLow);
  423. }
  424. }
  425. }