AtlasBuildTask.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. using XGame.Editor.Asset.Processor;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using UnityEditor;
  6. using UnityEditor.U2D;
  7. using UnityEngine;
  8. using UnityEngine.U2D;
  9. using Object = UnityEngine.Object;
  10. namespace XGame.Editor.Asset
  11. {
  12. [Serializable]
  13. public struct AtlasRoot
  14. {
  15. public string path;
  16. public string tag;
  17. }
  18. public partial class AtlasBuildTask
  19. {
  20. /// <summary>
  21. /// 图集Package标签,文件夹名以标签结尾的表示该文件夹下的所有内容需要合并在一个图集里
  22. /// </summary>
  23. private const string packageTag = "pkg";
  24. private readonly string packageTag2 = $"_{packageTag}";
  25. private readonly string _ResStaticDir = $"/{PathDefine.ResStaticName}/";
  26. private HashSet<string> _depAssetPaths;
  27. /// <summary>
  28. /// 图集名命名类型
  29. /// 0:tag+文件夹名
  30. /// 1:文件夹路径
  31. /// 2:guid
  32. /// </summary>
  33. private int _atlasNameType;
  34. //private string[] GetAtlasPaths()
  35. //{
  36. // var roots = AssetsConfig.AtlasRoots;
  37. // //var count = 1; //临时改下
  38. // var count = roots.Length;
  39. // var paths = new string[count];
  40. // for (var i = 0; i < count; i++)
  41. // {
  42. // paths[i] = roots[i].path;
  43. // }
  44. // return paths;
  45. //}
  46. /// <summary>
  47. /// 创建图集配置
  48. /// </summary>
  49. public void Run()
  50. {
  51. _atlasNameType = AssetsConfig.AtlasNameType;
  52. SetSpritePackerMode();
  53. CollectDependencies();
  54. CreateSpriteAtlas();
  55. }
  56. private void SetSpritePackerMode()
  57. {
  58. if (EditorSettings.spritePackerMode == UnityEditor.SpritePackerMode.AlwaysOnAtlas ||
  59. EditorSettings.spritePackerMode == UnityEditor.SpritePackerMode.BuildTimeOnlyAtlas)
  60. {
  61. return;
  62. }
  63. EditorSettings.spritePackerMode = UnityEditor.SpritePackerMode.BuildTimeOnlyAtlas;
  64. }
  65. /// <summary>
  66. /// 收集创建所有图集的spriteatlas
  67. /// </summary>
  68. private void CreateSpriteAtlas()
  69. {
  70. var directories = FileUtil.GetAtlasRoots();
  71. foreach (var directory in directories)
  72. {
  73. if (!Directory.Exists(directory))
  74. {
  75. Debug.LogWarning($"Can't find atlas root:{directory}");
  76. continue;
  77. }
  78. Debug.Log($"CreateAtlas Start. Root:{directory}");
  79. var isRaw = directory.Contains(_ResStaticDir);
  80. CreateAtlas(directory, 0, isRaw);
  81. }
  82. AssetDatabase.SaveAssets();
  83. AssetDatabase.Refresh();
  84. Debug.Log($"CreateAtlas Finish. Time: {DateTime.Now}");
  85. }
  86. private bool CreateAtlas(string atlasDirectory, int layer, bool isRaw)
  87. {
  88. if (!Directory.Exists(atlasDirectory))
  89. {
  90. Debug.LogError($"Directory is not Exists. Path:{atlasDirectory}");
  91. return false;
  92. }
  93. //Debug.Log($"CreateAtlas atlasDirectory:{atlasDirectory}");
  94. var havePkgTag = HavePackageTag(atlasDirectory);
  95. var entities = Directory.GetFileSystemEntries(atlasDirectory);
  96. var texturePaths = new List<string>();
  97. var spriteAtlases = new List<string>();
  98. var hasSub = false;
  99. var layerLimit = AssetsConfig.AtlasLayerLimit;
  100. foreach (var entity in entities)
  101. {
  102. var entityPath = entity.Replace("\\", "/");
  103. if (Directory.Exists(entityPath))
  104. {//是文件夹
  105. if (havePkgTag || layer >= layerLimit)
  106. {
  107. //最多递归3次
  108. //所有子文件夹贴图
  109. var subFolders = new string[] { entityPath };
  110. var textureGuids = AssetDatabase.FindAssets("t:Texture", subFolders);
  111. foreach(var textureGuid in textureGuids)
  112. {
  113. texturePaths.Add(AssetDatabase.GUIDToAssetPath(textureGuid));
  114. hasSub = true;
  115. }
  116. //图集配置文件
  117. var saGuids = AssetDatabase.FindAssets("t:SpriteAtlas", subFolders);
  118. foreach(var saGuid in saGuids)
  119. {
  120. spriteAtlases.Add(AssetDatabase.GUIDToAssetPath(saGuid));
  121. }
  122. }
  123. else
  124. {
  125. if (CreateAtlas(entityPath, layer + 1, isRaw))
  126. {
  127. hasSub = true;
  128. }
  129. }
  130. }
  131. else
  132. {
  133. if (FileUtil.IsTextureFile(entityPath))
  134. {
  135. texturePaths.Add(entityPath);
  136. }
  137. else if (FileUtil.IsSpriteAtlas(entityPath))
  138. {
  139. spriteAtlases.Add(entityPath);
  140. }
  141. }
  142. }
  143. //ResStatic下过滤没有使用的贴图
  144. if (isRaw && texturePaths.Count > 0)
  145. {
  146. for (int i = texturePaths.Count - 1; i >= 0; i--)
  147. {
  148. if (!IsUsing(texturePaths[i]))
  149. {
  150. texturePaths.RemoveAt(i);
  151. }
  152. }
  153. }
  154. var noAlpha = false;
  155. if (texturePaths.Count > 0)
  156. {
  157. var assetPath = texturePaths[0];
  158. try
  159. {
  160. //取首尾两张图的属性确认该图集是否需要alpha
  161. var textureImport = AssetImporter.GetAtPath(assetPath) as TextureImporter;
  162. if (textureImport != null && !textureImport.DoesSourceTextureHaveAlpha())
  163. {
  164. assetPath = texturePaths[texturePaths.Count - 1];
  165. textureImport = AssetImporter.GetAtPath(assetPath) as TextureImporter;
  166. if (textureImport != null && !textureImport.DoesSourceTextureHaveAlpha())
  167. {
  168. noAlpha = true;
  169. }
  170. }
  171. }
  172. catch (Exception ex)
  173. {
  174. Debug.LogError($"Read texture alpha failed. Path:{assetPath}", AssetDatabase.LoadMainAssetAtPath(assetPath));
  175. Debug.LogException(ex);
  176. }
  177. }
  178. var atlasPath = GetAtlasPath(atlasDirectory);
  179. foreach (var lastAtlas in spriteAtlases)
  180. { //删除旧的图集
  181. if (texturePaths.Count > 0 && lastAtlas.Equals(atlasPath))
  182. { //保留同名图集
  183. continue;
  184. }
  185. //Debug.Log($"Delete Atlas: {lastAtlas}");
  186. UnityEditor.FileUtil.DeleteFileOrDirectory(lastAtlas);
  187. }
  188. if (texturePaths.Count > 0)
  189. {
  190. var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasPath);
  191. var isNew = false;
  192. if (atlas == null)
  193. {
  194. atlas = new SpriteAtlas();
  195. isNew = true;
  196. }
  197. var preset = AssetsConfig.SpriteAtlasPreset;
  198. if (preset != null)
  199. {
  200. preset.ApplyTo(atlas);
  201. if (noAlpha)
  202. {
  203. //Android
  204. var androidPlatformSettings = atlas.GetPlatformSettings("Android");
  205. androidPlatformSettings.overridden = true;
  206. androidPlatformSettings.format = TextureImporterFormat.ETC2_RGB4;
  207. atlas.SetPlatformSettings(androidPlatformSettings);
  208. }
  209. }
  210. else
  211. {
  212. var packingSettings = atlas.GetPackingSettings();
  213. packingSettings.enableRotation = false;
  214. packingSettings.enableTightPacking = false;
  215. atlas.SetPackingSettings(packingSettings);
  216. //设置Texture属性
  217. var textureSettings = atlas.GetTextureSettings();
  218. textureSettings.generateMipMaps = false;
  219. textureSettings.readable = false;
  220. atlas.SetTextureSettings(textureSettings);
  221. //设置Platform属性
  222. //default
  223. var defaultPlatformSettings = atlas.GetPlatformSettings("DefaultTexturePlatform");
  224. defaultPlatformSettings.format = TextureImporterFormat.Automatic;
  225. defaultPlatformSettings.maxTextureSize = 2048;
  226. defaultPlatformSettings.textureCompression = TextureImporterCompression.Compressed;
  227. atlas.SetPlatformSettings(defaultPlatformSettings);
  228. //Android
  229. var androidPlatformSettings = atlas.GetPlatformSettings("Android");
  230. androidPlatformSettings.overridden = true;
  231. androidPlatformSettings.allowsAlphaSplitting = false;
  232. androidPlatformSettings.androidETC2FallbackOverride = AndroidETC2FallbackOverride.UseBuildSettings;
  233. androidPlatformSettings.format = noAlpha ? TextureImporterFormat.ETC2_RGB4 : TextureImporterFormat.ETC2_RGBA8;
  234. atlas.SetPlatformSettings(androidPlatformSettings);
  235. //iOS
  236. var iosPlatformSettings = atlas.GetPlatformSettings("iPhone");
  237. iosPlatformSettings.overridden = true;
  238. iosPlatformSettings.allowsAlphaSplitting = false;
  239. iosPlatformSettings.format = TextureImporterFormat.ASTC_5x5;
  240. atlas.SetPlatformSettings(iosPlatformSettings);
  241. }
  242. var atlasName = Path.GetFileNameWithoutExtension(atlasPath);
  243. atlas.name = atlasName;
  244. //先删除旧数据
  245. atlas.Remove(atlas.GetPackables());
  246. if (isRaw || hasSub)
  247. {
  248. // 排序
  249. var count = texturePaths.Count;
  250. //if (count > 1) texturePaths.Sort();
  251. var textureObjs = new Object[count];
  252. for (var i = 0; i < count; i++)
  253. {
  254. textureObjs[i] = AssetDatabase.LoadMainAssetAtPath(texturePaths[i]);
  255. }
  256. atlas.Add(textureObjs);
  257. }
  258. else
  259. {
  260. atlas.Add(new Object[] { AssetDatabase.LoadMainAssetAtPath(atlasDirectory) });
  261. }
  262. if (isNew)
  263. {
  264. AssetDatabase.CreateAsset(atlas, atlasPath);
  265. }
  266. EditorUtility.SetDirty(atlas);
  267. //Debug.Log($"Save Atlas: {atlasPath}");
  268. return true;
  269. }
  270. return hasSub;
  271. }
  272. /// <summary>
  273. /// 判断一个图集文件夹是否有Package标签
  274. /// </summary>
  275. /// <param name="path"></param>
  276. /// <returns></returns>
  277. private bool HavePackageTag(string path)
  278. {
  279. return path.EndsWith(packageTag, StringComparison.OrdinalIgnoreCase) || path.Contains(packageTag2, StringComparison.OrdinalIgnoreCase);
  280. }
  281. private System.Text.StringBuilder tempSb = new System.Text.StringBuilder();
  282. private string GetAtlasPath(string path)
  283. {
  284. switch (_atlasNameType)
  285. {
  286. case 2:
  287. {
  288. //使用guid做图集名
  289. var guid = AssetDatabase.AssetPathToGUID(path);
  290. return $"{path}/atlas_{guid.ToLower()}.spriteatlas";
  291. }
  292. case 1:
  293. //使用文件夹路径做图集名
  294. return $"{path}/{path.Substring(PathDefine.AssetsRelative.Length).ToLower().Replace('/', '_')}.spriteatlas";
  295. default:
  296. {
  297. var startIdx = 0;
  298. int index;
  299. while ((index = path.IndexOf('/', startIdx)) != -1 && index < path.Length)
  300. {
  301. // 取文件夹名首字符合并做tag,这样做可能会有重复的图集名
  302. tempSb.Append(path[index + 1]);
  303. startIdx = index + 1;
  304. }
  305. var tag = tempSb.ToString().ToLower();
  306. tempSb.Clear();
  307. //tag加文件夹名字
  308. return $"{path}/atlas_{tag}_{Path.GetFileName(path).ToLower()}.spriteatlas";
  309. }
  310. }
  311. }
  312. #region 引用记录
  313. private void CollectDependencies()
  314. {
  315. var productAssetsRoots = FileUtil.GetProductAssetsRoots(true);
  316. var assetGuids = AssetDatabase.FindAssets("", productAssetsRoots);
  317. var assetPaths = new List<string>();
  318. foreach (var assetGuid in assetGuids)
  319. {
  320. var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
  321. if (Directory.Exists(assetPath) || FileUtil.IsFileIgnore(assetPath) || FileUtil.IsTextureFile(assetPath))
  322. continue;
  323. assetPaths.Add(assetPath);
  324. }
  325. var sw = new System.Diagnostics.Stopwatch();
  326. sw.Start();
  327. var dependencies = AssetDatabase.GetDependencies(assetPaths.ToArray(), true);
  328. sw.Stop();
  329. var depAssetPaths = new HashSet<string>();
  330. //var rawPaths = new HashSet<string>();
  331. //var roots = AssetsConfig.AtlasRoots;
  332. //foreach (var root in roots)
  333. //{
  334. // if (root.path.Contains(PathDefine.ResStaticName))
  335. // {
  336. // rawPaths.Add(root.path);
  337. // }
  338. //}
  339. foreach (var dep in dependencies)
  340. {
  341. if (dep.Contains(_ResStaticDir, StringComparison.OrdinalIgnoreCase))
  342. {
  343. depAssetPaths.Add(dep);
  344. }
  345. }
  346. _depAssetPaths = depAssetPaths;
  347. Debug.Log($"[XBuild] Atlas GetDependencies Time:{sw.ElapsedMilliseconds}");
  348. }
  349. private bool IsUsing(string path)
  350. {
  351. return _depAssetPaths.Contains(path);
  352. }
  353. #endregion
  354. }
  355. }