DynamicGraphic.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using UnityEngine;
  8. using Debug = UnityEngine.Debug;
  9. using Object = UnityEngine.Object;
  10. namespace CrossgateToolkit
  11. {
  12. // 针对网游设置的可扩展动态图集
  13. // 包含图集建立、自动维护和清理
  14. public class DynamicGraphic
  15. {
  16. // 动态图集数据
  17. public class DynamicData
  18. {
  19. // 图集高度
  20. public int Width;
  21. // 图集宽度
  22. public int Height;
  23. // 图集Texture
  24. public Texture2D Texture2D;
  25. // 图集Sprite引用及编号银映射
  26. // public List<Sprite> RefSpriteList = new List<Sprite>();
  27. // public Dictionary<uint, int> RefSpriteMap = new Dictionary<uint, int>();
  28. // public Dictionary<uint,Color> PrimaryColor = new Dictionary<uint, Color>();
  29. // 二维数组存储当前所有像素点可用位置 - 此数组在4096*4096情况下,占用约16MB内存
  30. // public bool[,] PixelAvaliable;
  31. // 在二维空间中,记录当前可用的矩形区域
  32. public List<Rect> AvaliableRect = new List<Rect>();
  33. public bool avaliable = true;
  34. public bool CompressWhenFull = false;
  35. public void Init(bool Linear = false)
  36. {
  37. // 初始化时填补List的0位,避免序列号为0的图像无法获取
  38. // RefSpriteList.Add(null);
  39. AvaliableRect.Add(new Rect(0, 0, Width, Height));
  40. float time = Time.realtimeSinceStartup;
  41. Texture2D = new Texture2D(Width, Height, TextureFormat.RGBA4444, false);
  42. Texture2D.name = "DynamicGraphicTexture";
  43. Texture2D.filterMode = Linear ? FilterMode.Bilinear : FilterMode.Point;
  44. Texture2D.wrapMode = TextureWrapMode.Clamp;
  45. // Texture2D.Compress(true);
  46. // Texture2d填充透明像素
  47. // Color32[] transColor = new Color32[Width * Height];
  48. // for (int i = 0; i < Width * Height; i++)
  49. // {
  50. // transColor[i] = Color.clear;
  51. // }
  52. // Texture2D.SetPixels32(transColor);
  53. // Texture2D.Apply();
  54. // transColor = null;
  55. }
  56. private int bestLongSideFit;
  57. private int bestShortSideFit;
  58. // 查找最佳位置
  59. public Rect FindBestFitRect(int width, int height)
  60. {
  61. Rect bestRect = default;
  62. bestShortSideFit = int.MaxValue;
  63. bool hasAvaliable = false;
  64. for (int i = 0; i < AvaliableRect.Count; ++i)
  65. {
  66. if(AvaliableRect[i].width>64 && AvaliableRect[i].height>48) hasAvaliable = true;
  67. if (AvaliableRect[i].width >= width && AvaliableRect[i].height >= height)
  68. {
  69. int leftoverHoriz = Mathf.Abs((int)AvaliableRect[i].width - width);
  70. int leftoverVert = Mathf.Abs((int)AvaliableRect[i].height - height);
  71. int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
  72. int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
  73. if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit))
  74. {
  75. bestRect.x = AvaliableRect[i].x;
  76. bestRect.y = AvaliableRect[i].y;
  77. bestRect.width = width;
  78. bestRect.height = height;
  79. bestShortSideFit = shortSideFit;
  80. bestLongSideFit = longSideFit;
  81. }
  82. }
  83. }
  84. // 检测过程中判断是否还有有效空间
  85. avaliable = hasAvaliable;
  86. if (!avaliable)
  87. {
  88. // 当有效空间为0时,对texture压缩
  89. if(CompressWhenFull) Texture2D.Compress(true);
  90. }
  91. return bestRect;
  92. }
  93. // 插入位置
  94. public void InsertRect (Rect node)
  95. {
  96. // float time = Time.time;
  97. // Stopwatch stopwatch = new Stopwatch();
  98. // stopwatch.Start();
  99. int numRectanglesToProcess = AvaliableRect.Count;
  100. for (int i = 0; i < numRectanglesToProcess; ++i)
  101. {
  102. if (SplitFreeRect(AvaliableRect[i], ref node))
  103. {
  104. AvaliableRect.RemoveAt(i);
  105. --i;
  106. --numRectanglesToProcess;
  107. }
  108. }
  109. // 重新整理可用区域,去除重叠区域
  110. OptFreeList();
  111. // stopwatch.Stop();
  112. // Debug.Log("[CGTool] 插入位置耗时: " + stopwatch.ElapsedMilliseconds + "ms");
  113. }
  114. // 分割区域
  115. bool SplitFreeRect (Rect freeRect, ref Rect useRect)
  116. {
  117. Rect rect = default;
  118. if (useRect.x >= freeRect.x + freeRect.width || useRect.x + useRect.width <= freeRect.x ||
  119. useRect.y >= freeRect.y + freeRect.height || useRect.y + useRect.height <= freeRect.y)
  120. return false;
  121. if (useRect.x < freeRect.x + freeRect.width && useRect.x + useRect.width > freeRect.x)
  122. {
  123. if (useRect.y > freeRect.y && useRect.y < freeRect.y + freeRect.height)
  124. {
  125. rect = freeRect;
  126. rect.height = useRect.y - rect.y;
  127. AvaliableRect.Add(rect);
  128. }
  129. if (useRect.y + useRect.height < freeRect.y + freeRect.height)
  130. {
  131. rect = freeRect;
  132. rect.y = useRect.y + useRect.height;
  133. rect.height = freeRect.y + freeRect.height - (useRect.y + useRect.height);
  134. AvaliableRect.Add(rect);
  135. }
  136. }
  137. if (useRect.y < freeRect.y + freeRect.height && useRect.y + useRect.height > freeRect.y)
  138. {
  139. if (useRect.x > freeRect.x && useRect.x < freeRect.x + freeRect.width)
  140. {
  141. rect = freeRect;
  142. rect.width = useRect.x - rect.x;
  143. AvaliableRect.Add(rect);
  144. }
  145. if (useRect.x + useRect.width < freeRect.x + freeRect.width)
  146. {
  147. rect = freeRect;
  148. rect.x = useRect.x + useRect.width;
  149. rect.width = freeRect.x + freeRect.width - (useRect.x + useRect.width);
  150. AvaliableRect.Add(rect);
  151. }
  152. }
  153. return true;
  154. }
  155. // 重新整理可用区域,去除重叠区域
  156. void OptFreeList ()
  157. {
  158. for (int i = 0; i < AvaliableRect.Count; ++i)
  159. for (int j = i + 1; j < AvaliableRect.Count; ++j)
  160. {
  161. if (IsIntersect(AvaliableRect[i], AvaliableRect[j]))
  162. {
  163. AvaliableRect.RemoveAt(i);
  164. --i;
  165. break;
  166. }
  167. if (IsIntersect(AvaliableRect[j], AvaliableRect[i]))
  168. {
  169. AvaliableRect.RemoveAt(j);
  170. --j;
  171. }
  172. }
  173. }
  174. // 比对两个矩形是否重叠
  175. bool IsIntersect (Rect a, Rect b)
  176. {
  177. return a.x >= b.x && a.y >= b.y
  178. && a.x + a.width <= b.x + b.width
  179. && a.y + a.height <= b.y + b.height;
  180. }
  181. }
  182. private class ClearMono:MonoBehaviour
  183. {
  184. public void Clear()
  185. {
  186. Destroy(gameObject);
  187. }
  188. }
  189. // 动态图集最大宽度
  190. public int MaxGraphicWidth;
  191. // 动态图集最大高度
  192. public int MaxGraphicHeight;
  193. // 动态图集内部间隔
  194. public int Padding;
  195. // 动态图集是否使用线性采样
  196. public bool Linear;
  197. // 动态图集是否使用100ppu
  198. public bool PPU100;
  199. // 当动态图档无可用空间时进行压缩
  200. public bool CompressWhenFull;
  201. // 使用调色板编号
  202. public int PaletIndex;
  203. // 动态图集固定图像尺寸
  204. public Vector2Int FixedSize = default;
  205. // 当前动态图集包含的图集数据
  206. public List<DynamicData> DynamicDatas = new List<DynamicData>();
  207. // Sprite缓存
  208. public Dictionary<uint, Sprite> SpriteCache = new Dictionary<uint, Sprite>();
  209. // 主色缓存
  210. public Dictionary<uint, Color> PrimaryColorCache = new Dictionary<uint, Color>();
  211. private static ClearMono clearMono;
  212. /// <summary>
  213. /// 创建动态图集
  214. /// </summary>
  215. /// <param name="GraphicWidth">图集最大宽度</param>
  216. /// <param name="GraphicHeight">图集最大高度</param>
  217. /// <param name="GraphicPadding">图集各图档间隔</param>
  218. /// <param name="palet">调色板编号</param>
  219. /// <param name="linear">线性过滤</param>
  220. /// <param name="ppu100">以100的PPU方式生成Sprite对象</param>
  221. /// <param name="compressWhenFull">当图集对象无可用空间时,对Texture进行压缩</param>
  222. public DynamicGraphic(int GraphicWidth, int GraphicHeight, int GraphicPadding = 0,int palet = 0,bool linear = false,bool ppu100 = false,bool compressWhenFull = false)
  223. {
  224. MaxGraphicWidth = GraphicWidth;
  225. MaxGraphicHeight = GraphicHeight;
  226. Padding = GraphicPadding;
  227. PaletIndex = palet;
  228. Linear = linear;
  229. PPU100 = ppu100;
  230. CompressWhenFull = compressWhenFull;
  231. if (clearMono == null) clearMono = new GameObject("DynamicGraphicClear").AddComponent<ClearMono>();
  232. }
  233. // 清理图集图像
  234. public void Clear(bool autoGC = false)
  235. {
  236. // clearMono.StartCoroutine(ClearCoroutine(DynamicDatas, autoGC));
  237. ClearCoroutine(DynamicDatas, autoGC);
  238. DynamicDatas = new List<DynamicData>();
  239. }
  240. // 获取图像
  241. public Sprite GetSprite(uint Serial)
  242. {
  243. Sprite sprite = null;
  244. // 检查缓存
  245. SpriteCache.TryGetValue(Serial, out sprite);
  246. if (sprite != null) return sprite;
  247. // 获取图档数据
  248. GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoData(Serial);
  249. if (graphicInfoData == null)
  250. {
  251. Debug.LogError("[CGTool] 无法获取图档数据: " + Serial);
  252. return null;
  253. }
  254. // Debug.Log("[CGTool] 获取图档数据: " + Serial + " " + graphicInfoData.Width + "x" + graphicInfoData.Height);
  255. // 更新尺寸,增加Padding
  256. int width = (int)graphicInfoData.Width + Padding;
  257. int height = (int)graphicInfoData.Height + Padding;
  258. // 首先检查最新的图集数据是否有空间
  259. DynamicData lastDynamicData = null;
  260. bool avaliable = true;
  261. Rect avaliableRect = default;
  262. // 检测是否有图集数据
  263. if (DynamicDatas.Count > 0)
  264. {
  265. for (var i = 0; i < DynamicDatas.Count; i++)
  266. {
  267. lastDynamicData = DynamicDatas[i];
  268. if (!lastDynamicData.avaliable)
  269. {
  270. lastDynamicData = null;
  271. continue;
  272. }
  273. avaliableRect = lastDynamicData.FindBestFitRect(width, height);
  274. if (avaliableRect != default) break;
  275. }
  276. // lastDynamicData = DynamicDatas[^1];
  277. // avaliableRect = lastDynamicData.FindBestFitRect(width, height);
  278. }
  279. else avaliable = false;
  280. // 如果没有可用数据集或没有空间则创建新的图集
  281. if (!avaliable || avaliableRect==default)
  282. {
  283. DynamicData newDynamicData = new DynamicData();
  284. newDynamicData.Width = MaxGraphicWidth;
  285. newDynamicData.Height = MaxGraphicHeight;
  286. // 初始化
  287. newDynamicData.Init(Linear);
  288. newDynamicData.CompressWhenFull = CompressWhenFull;
  289. DynamicDatas.Add(newDynamicData);
  290. lastDynamicData = newDynamicData;
  291. avaliableRect = lastDynamicData.FindBestFitRect(width, height);
  292. Debug.Log("[CGTool] 创建新图集: " + newDynamicData.Width + "x" + newDynamicData.Height + " 当前图集数量: " +
  293. DynamicDatas.Count);
  294. }
  295. // 获取图档像素
  296. List<Color32> color32s;
  297. Color PrimaryColor = Color.clear;
  298. Texture texture = null;
  299. bool useCache = true;
  300. // 填充图集,填充方式为可用区域的左下角开始
  301. if (useCache)
  302. {
  303. // 二级缓存模式
  304. GraphicDetail graphicDetail = GraphicData.GetGraphicDetail(graphicInfoData, PaletIndex, -1, Linear, true);
  305. if (graphicDetail == null) return null;
  306. texture = graphicDetail.Sprite.texture;
  307. PrimaryColor = graphicDetail.PrimaryColor;
  308. Graphics.CopyTexture(texture, 0, 0, 0,0,
  309. (int)graphicInfoData.Width, (int)graphicInfoData.Height, lastDynamicData.Texture2D, 0, 0, (int)avaliableRect.x, (int)avaliableRect.y);
  310. }
  311. else
  312. {
  313. // 非缓存模式
  314. color32s = GraphicData.UnpackGraphic(graphicInfoData, PaletIndex);
  315. if (color32s == null || color32s.Count == 0) return null;
  316. // 去除最后一位主色
  317. // lastDynamicData.PrimaryColor[Serial] = color32s[^1];
  318. PrimaryColor = color32s[^1];
  319. color32s.RemoveAt(color32s.Count - 1);
  320. lastDynamicData.Texture2D.SetPixels32((int)avaliableRect.x, (int)avaliableRect.y,
  321. (int)graphicInfoData.Width, (int)graphicInfoData.Height, color32s.ToArray(), 0);
  322. lastDynamicData.Texture2D.Apply(false, false);
  323. color32s = null;
  324. }
  325. //直接通过Texture2D做偏移,并转为Sprite的偏移量
  326. Vector2 offset = new Vector2(0f, 1f);
  327. offset.x += -(graphicInfoData.OffsetX * 1f) / graphicInfoData.Width;
  328. offset.y -= (-graphicInfoData.OffsetY * 1f) / graphicInfoData.Height;
  329. // 创建Sprite
  330. sprite = Sprite.Create(lastDynamicData.Texture2D,
  331. new Rect(avaliableRect.x, avaliableRect.y, graphicInfoData.Width, graphicInfoData.Height),
  332. offset, PPU100 ? 100 : 1, 0, SpriteMeshType.FullRect);
  333. sprite.name = "DG-" + Serial;
  334. SpriteCache[Serial] = sprite;
  335. PrimaryColorCache[Serial] = PrimaryColor;
  336. // 更新当前可用区域
  337. lastDynamicData.InsertRect(avaliableRect);
  338. return sprite;
  339. }
  340. public IEnumerator GetSpriteSync(uint Serial, Action<Sprite> callback)
  341. {
  342. Sprite sprite = null;
  343. SpriteCache.TryGetValue(Serial, out sprite);
  344. if (sprite!=null)
  345. {
  346. callback?.Invoke(sprite);
  347. yield break;
  348. }
  349. yield return null;
  350. sprite = GetSprite(Serial);
  351. callback?.Invoke(sprite);
  352. }
  353. // 获取主色
  354. public Color GetPrimaryColor(uint Serial)
  355. {
  356. SpriteCache.TryGetValue(Serial, out var sprite);
  357. if(sprite==null) clearMono.StartCoroutine(GetSpriteSync(Serial, s => sprite = s));
  358. PrimaryColorCache.TryGetValue(Serial, out var color);
  359. // if(color==null) color = Color.clear;
  360. return color;
  361. }
  362. private void ClearCoroutine(List<DynamicData> dynamicDatas, bool autoGC = false)
  363. {
  364. PrimaryColorCache.Clear();
  365. Dictionary<uint, Sprite> spriteCache = SpriteCache;
  366. SpriteCache = new Dictionary<uint, Sprite>();
  367. List<Texture> textures = new List<Texture>();
  368. foreach (var keyValuePair in spriteCache)
  369. {
  370. Sprite sprite = keyValuePair.Value;
  371. if (sprite == null) continue;
  372. if(!textures.Contains(sprite.texture)) textures.Add(sprite.texture);
  373. Object.Destroy(sprite);
  374. sprite = null;
  375. GraphicData.ClearCache(keyValuePair.Key);
  376. }
  377. foreach (var texture in textures)
  378. {
  379. Object.Destroy(texture);
  380. // Resources.UnloadAsset(texture);
  381. }
  382. DynamicDatas.Clear();
  383. if (autoGC)
  384. {
  385. Resources.UnloadUnusedAssets();
  386. // System.GC.Collect();
  387. }
  388. }
  389. // 协程清理有可能导致无法清除干净,暂时不用
  390. // IEnumerator ClearCoroutine(List<DynamicData> dynamicDatas, bool autoGC = false)
  391. // {
  392. // PrimaryColorCache.Clear();
  393. // Dictionary<uint, Sprite> spriteCache = SpriteCache;
  394. // SpriteCache = new Dictionary<uint, Sprite>();
  395. //
  396. // List<Texture> textures = new List<Texture>();
  397. //
  398. // foreach (var keyValuePair in spriteCache)
  399. // {
  400. // Sprite sprite = keyValuePair.Value;
  401. // if (sprite == null) continue;
  402. // if(!textures.Contains(sprite.texture)) textures.Add(sprite.texture);
  403. // spriteCache.Remove(keyValuePair.Key);
  404. // sprite = null;
  405. // // Resources.UnloadAsset(sprite);
  406. // }
  407. // yield return null;
  408. // foreach (var texture in textures)
  409. // {
  410. // Object.Destroy(texture);
  411. // yield return null;
  412. // }
  413. //
  414. // DynamicDatas.Clear();
  415. // yield return null;
  416. // if (autoGC)
  417. // {
  418. // Resources.UnloadUnusedAssets();
  419. // // System.GC.Collect();
  420. // }
  421. // }
  422. }
  423. }