Browse Source

3.0性能更新

HonorLee 3 months ago
parent
commit
07266a4eba

+ 42 - 66
CrossgateToolkit/Anime.cs

@@ -56,7 +56,7 @@ namespace CrossgateToolkit
         public GraphicInfoData GraphicInfo;
         //动画Sprite
         // public Dictionary<int,Sprite> AnimeSprites = new Dictionary<int, Sprite>();
-        public Dictionary<int,Dictionary<bool,Sprite>> AnimeSprites = new Dictionary<int, Dictionary<bool, Sprite>>();
+        public Dictionary<int,Dictionary<bool,GraphicDetail>> AnimeSprites = new Dictionary<int, Dictionary<bool, GraphicDetail>>();
     }
 
     //动画数据
@@ -80,7 +80,7 @@ namespace CrossgateToolkit
         // 高版本 - 调色板
         public int Palet;
         // 高版本 - 图像反转
-        public AnimeFlag FLAG;
+        public AnimeFlag FLAG = AnimeFlag.NULL;
         // 高版本 - 结束标识
         public byte[] FLAG_END;
         // public Dictionary<int,Texture2D> AnimeTextures = new Dictionary<int, Texture2D>();
@@ -90,12 +90,14 @@ namespace CrossgateToolkit
         // public byte[] unknown;
     }
 
-    public class AnimeFlag
+    [Flags]
+    public enum AnimeFlag
     {
-        public bool REVERSE_X;
-        public bool REVERSE_Y;
-        public bool LOCK_PAL;
-        public bool LIGHT_THROUGH;
+        NULL = 0,
+        REVERSE_X = 1<<0,
+        REVERSE_Y = 1<<1,
+        LOCK_PAL = 1<<2,
+        LIGHT_THROUGH = 1<<3
     }
     
     //动画相关Enum类型
@@ -211,7 +213,6 @@ namespace CrossgateToolkit
             animeInfo.AnimeDatas = new Dictionary<int, Dictionary<int, AnimeDetail>>();
             for (int j = 0; j < animeInfo.ActionCount; j++)
             {
-                
                 // 高版本标识
                 bool isHighVersion = false;
                 animeInfo.DataReader.BaseStream.Position += 16;
@@ -234,15 +235,15 @@ namespace CrossgateToolkit
                     int flag = animeInfo.DataReader.ReadUInt16();
                     if (flag > 0)
                     {
-                        animeData.FLAG = new AnimeFlag();
-                        if ((flag & 1) == (1 << 0)) animeData.FLAG.REVERSE_X = true;
-                        if ((flag & 2) == (1 << 1)) animeData.FLAG.REVERSE_Y = true;
-                        if ((flag & 4) == (1 << 2)) animeData.FLAG.LOCK_PAL = true;
-                        if ((flag & 8) == (1 << 3)) animeData.FLAG.LIGHT_THROUGH = true;    
+                        if ((flag & 1) == (1 << 0)) animeData.FLAG |= AnimeFlag.REVERSE_X;
+                        if ((flag & 2) == (1 << 1)) animeData.FLAG |= AnimeFlag.REVERSE_Y;
+                        if ((flag & 4) == (1 << 2)) animeData.FLAG |= AnimeFlag.LOCK_PAL;
+                        if ((flag & 8) == (1 << 3)) animeData.FLAG |= AnimeFlag.LIGHT_THROUGH;
                     }
                     
                     animeData.FLAG_END = animeInfo.DataReader.ReadBytes(4);
                 }
+                
                 animeData.AnimeFrameInfos = new List<AnimeFrameInfo>();
                 
                 // if (animeInfo.Index == 101201) Debug.Log("----------------------------------");
@@ -315,82 +316,57 @@ namespace CrossgateToolkit
         }
 
         //预处理动画图形合批烘焙
-        public static void BakeAnimeFrames(AnimeDetail animeDetail,int palet = 0, bool linear = false)
+        public static void BakeAnimeFrames(AnimeDetail animeDetail,int palet = 0, bool linear = false,bool compress = false)
         {
-            if (animeDetail.AnimeTextures.ContainsKey(palet))
+            // 查看是否存在缓存Texture数据
+            animeDetail.AnimeTextures.TryGetValue(palet, out var textureDict);
+            if(textureDict!=null)
             {
-                if(animeDetail.AnimeTextures[palet].ContainsKey(linear)) return;
+                if(textureDict.ContainsKey(linear)) return;
             }
+            
             //所有帧的图形数据
             GraphicDetail[] graphicDetails = new GraphicDetail[animeDetail.FrameCount];
             
             //合并后的Texture2D尺寸
             uint textureWidth = 0;
             uint textureHeight = 0;
-            
-            
+            List<GraphicInfoData> graphicInfoDatas = new List<GraphicInfoData>();
+            Dictionary<uint,GraphicInfoData> graphicInfoDataDict = new Dictionary<uint, GraphicInfoData>();
+            int subPaletIndex = -1;
+            if (animeDetail.IsHighVersion) subPaletIndex = (int)animeDetail.Serial;
             for (var i = 0; i < animeDetail.FrameCount; i++)
             {
                 //载入图档
                 GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoDataByIndex(animeDetail.Version,animeDetail.AnimeFrameInfos[i].GraphicIndex);
                 if (graphicInfoData == null) continue;
-                int subPaletIndex = 0;
-                if (animeDetail.IsHighVersion) subPaletIndex = (int)animeDetail.Serial;
-                GraphicDetail graphicDetail = GraphicData.GetGraphicDetail(graphicInfoData, palet, subPaletIndex, linear);
-                if(graphicDetail == null) continue;
-                graphicDetails[i] = graphicDetail;
-                if(graphicDetail.Height > textureHeight) textureHeight = graphicDetail.Height;
-                textureWidth += graphicDetail.Width + 5;
-                animeDetail.AnimeFrameInfos[i].Width = (int) graphicDetail.Width;
-                animeDetail.AnimeFrameInfos[i].Height = (int) graphicDetail.Height;
+                graphicInfoDatas.Add(graphicInfoData);
+                graphicInfoDataDict[graphicInfoData.Index] = graphicInfoData;
+                animeDetail.AnimeFrameInfos[i].GraphicInfo = graphicInfoData;
                 animeDetail.AnimeFrameInfos[i].OffsetX = (int) graphicInfoData.OffsetX;
                 animeDetail.AnimeFrameInfos[i].OffsetY = (int) graphicInfoData.OffsetY;
-                animeDetail.AnimeFrameInfos[i].GraphicInfo = graphicInfoData;
             }
-            //合并图档
-            Texture2D texture2dMix = new Texture2D((int) textureWidth, (int) textureHeight, TextureFormat.RGBA4444, false,linear);
-            if(linear) texture2dMix.filterMode = FilterMode.Bilinear;
-            else texture2dMix.filterMode = FilterMode.Point;
-            Color32 transparentColor = new Color32(0, 0, 0, 0);
-            Color32[] transparentColors = new Color32[texture2dMix.width * texture2dMix.height];
-            for (var i = 0; i < transparentColors.Length; i++)
-            {
-                transparentColors[i] = transparentColor;
-            }
-            texture2dMix.SetPixels32(transparentColors,0);
-            
-            int offsetX = 0;
+
+            Dictionary<uint, GraphicDetail> graphicDetailDict =
+                GraphicData.BakeGraphics(graphicInfoDatas, false, palet, subPaletIndex, linear, 2048, 5, compress);
+            Texture2D texture2dMix = null;
             for (var i = 0; i < animeDetail.FrameCount; i++)
             {
-                GraphicDetail graphicDetail = graphicDetails[i];
+                graphicDetailDict.TryGetValue(animeDetail.AnimeFrameInfos[i].GraphicInfo.Index,out var graphicDetail);
                 if(graphicDetail == null) continue;
-                texture2dMix.SetPixels32((int) offsetX, 0, (int) graphicDetail.Width,
-                    (int) graphicDetail.Height,
-                    graphicDetail.Sprite.texture.GetPixels32());
-                offsetX += (int) graphicDetail.Width + 5;
+                graphicDetails[i] = graphicDetail;
+                if (texture2dMix == null) texture2dMix = graphicDetail.Sprite.texture;
+                
+                AnimeFrameInfo animeFrameInfo = animeDetail.AnimeFrameInfos[i];
+                animeFrameInfo.Width = (int) graphicDetail.Width;
+                animeFrameInfo.Height = (int) graphicDetail.Height;
+                if(!animeFrameInfo.AnimeSprites.ContainsKey(palet)) animeFrameInfo.AnimeSprites[palet] = new Dictionary<bool, GraphicDetail>();
+                if(!animeFrameInfo.AnimeSprites[palet].ContainsKey(linear)) animeFrameInfo.AnimeSprites[palet]
+                    .Add(linear,graphicDetail);
             }
-            texture2dMix.Apply();
             
             if(!animeDetail.AnimeTextures.ContainsKey(palet)) animeDetail.AnimeTextures.Add(palet,new Dictionary<bool, Texture2D>());
-
-            animeDetail.AnimeTextures[palet].Add(linear, texture2dMix);
-            
-            //创建动画每帧Sprite
-            offsetX = 0;
-            for (var l = 0; l < animeDetail.FrameCount; l++)
-            {
-                if(graphicDetails[l] == null) continue;
-                AnimeFrameInfo animeFrameInfo = animeDetail.AnimeFrameInfos[l];
-                Vector2 pivot = new Vector2(0f, 1f);
-                pivot.x += -(animeFrameInfo.OffsetX * 1f) / animeFrameInfo.Width;
-                pivot.y -= (-animeFrameInfo.OffsetY * 1f) / animeFrameInfo.Height;
-                Sprite sprite = Sprite.Create(texture2dMix, new Rect(offsetX, 0,
-                        animeDetail.AnimeFrameInfos[l].Width, animeDetail.AnimeFrameInfos[l].Height),
-                    pivot, 1, 1, SpriteMeshType.FullRect);
-                offsetX += animeDetail.AnimeFrameInfos[l].Width + 5;
-                if(!animeFrameInfo.AnimeSprites.ContainsKey(palet)) animeFrameInfo.AnimeSprites.Add(palet,new Dictionary<bool, Sprite>());
-                animeFrameInfo.AnimeSprites[palet].Add(linear, sprite);
-            }
+            animeDetail.AnimeTextures[palet][linear] = texture2dMix;
             
         }
         

+ 235 - 94
CrossgateToolkit/AnimePlayer.cs

@@ -8,9 +8,12 @@
  * AnimePlayer.cs 动画播放器-挂载类
  */
 
+using System;
 using System.Collections.Generic;
 using UnityEngine;
-using UnityEngine.UI;
+using UnityEngine.EventSystems;
+using UnityEngine.UIElements;
+using Image = UnityEngine.UI.Image;
 
 namespace CrossgateToolkit
 {
@@ -22,9 +25,15 @@ namespace CrossgateToolkit
     
     //动画音频帧监听
     public delegate void AnimeAudioListener(int audioIndex);
-    
+
+    public enum MouseType
+    {
+        Enter,
+        Exit,
+        Click
+    }
     //鼠标移入事件监听
-    public delegate void MouseListener(AnimePlayer animePlayer);
+    public delegate void MouseListener(MouseType mouseType);
     
     /**
      * 动画播放器,用于播放CG动画,支持多动画队列播放
@@ -36,7 +45,7 @@ namespace CrossgateToolkit
      * 目前已知的动作帧有:
      * 击中 伤害结算
      */
-    public class AnimePlayer : MonoBehaviour
+    public class AnimePlayer : MonoBehaviour,IPointerEnterHandler,IPointerExitHandler,IPointerClickHandler
     {
         //动画帧数据
         private class AnimeFrame
@@ -61,11 +70,14 @@ namespace CrossgateToolkit
             public AnimeEffectListener onEffectListener;
             public int CurrentFrame = 0;
             public bool KeepFrame = false;
+            public bool _effectOverCalled = false;
+            public bool _finishedCalled = false;
+            public bool _keepCallback = false;
         }
         
         //当前播放
         private uint _currentSerial;
-        private AnimeOption _currentAnime;
+        private AnimeOption _currentAnime = null;
         private AnimeFrame[] _frames;
         // private int _currentFrame;
         
@@ -89,7 +101,7 @@ namespace CrossgateToolkit
         // }
         
         //待播放队列
-        private readonly List<AnimeOption> _animeQueue = new List<AnimeOption>();
+        private readonly List<AnimeOption> _animeQueue = new List<AnimeOption>(10);
         
         //计时器
         private float _timer;
@@ -99,7 +111,10 @@ namespace CrossgateToolkit
         //绑定渲染对象
         [SerializeField,Header("Image渲染")] public bool isRenderByImage = false;
         [SerializeField,Header("序列帧合批")] public bool isFrameBatch = false;
+        [SerializeField, Header("合批压缩")] public bool isBatchCompress;
         [SerializeField,Header("线性过滤")] public bool isLinearFilter = false;
+        [SerializeField,Header("PPU100模式")] public bool isPPU100 = false;
+        
         [Header("序列帧Texture")] public Texture2D frameTexture;
         
         private SpriteRenderer _spriteRenderer;
@@ -112,7 +127,10 @@ namespace CrossgateToolkit
             set
             {
                 _paletIndex = value;
-                if (_currentAnime != null) _play(_currentAnime);
+                if (_currentAnime != null && value != _paletIndex)
+                {
+                    _play();
+                }
             }
         }
         
@@ -122,10 +140,8 @@ namespace CrossgateToolkit
         //动画动作帧监听
         // public AnimeEffectListener onEffectListener;
         public AnimeAudioListener onAudioListener;
-        //鼠标移入事件监听
-        public MouseListener onMouseEnterListener;
-        //鼠标移出事件监听
-        public MouseListener onMouseExitListener;
+        //鼠标事件监听
+        public MouseListener onMouseListener;
 
         //获取偏移量(无用)
         public Vector2 offset
@@ -158,18 +174,18 @@ namespace CrossgateToolkit
             _updateRenderMode();
         }
 
-        //鼠标移入监听
-        private void OnMouseEnter()
+        private void OnDisable()
         {
-            onMouseEnterListener?.Invoke(this);
+            // 被隐藏后及时清理数据
+            // Stop();
+            Pause();
         }
 
-        //鼠标移出监听
-        private void OnMouseExit()
+        private void OnEnable()
         {
-            onMouseExitListener?.Invoke(this);
+            if(_currentAnime!=null) Resume();
         }
-        
+
         // 使用Image模式渲染
         public bool RenderByImage
         {
@@ -228,6 +244,8 @@ namespace CrossgateToolkit
                 }
             }
         }
+        
+        public AnimeCallback OnCycleCallback;
 
         // 更新渲染模式
         private void _updateRenderMode()
@@ -257,49 +275,57 @@ namespace CrossgateToolkit
         /// <returns>AnimePlayer</returns>
         public AnimePlayer play(uint serial, Anime.DirectionType Direction = Anime.DirectionType.North, 
             Anime.ActionType actionType = Anime.ActionType.Idle, Anime.PlayType playType = Anime.PlayType.Once,
-            float Speed = 1f,AnimeEffectListener onEffectListener = null,AnimeCallback onFinishCallback = null)
+            float Speed = 1f,AnimeEffectListener onEffectListener = null,AnimeCallback onFinishCallback = null,bool keepCallback = false)
         {
-            AnimeOption animeOption = CreateAnimeOption(serial, Direction, actionType, playType, Speed,onEffectListener, onFinishCallback);
+            AnimeOption animeOption = CreateAnimeOption(serial, Direction, actionType, playType, Speed,onEffectListener, onFinishCallback,keepCallback);
             if (animeOption == null)
             {
                 onFinishCallback?.Invoke(actionType);
                 // Debug.Log("AnimePlayer:AnimeOption create failed");
                 return this;
             }
+            
+            if (_currentAnime!=null && _currentAnime._keepCallback && isPlayable)
+            {
+                Pause();
+                if(!_currentAnime._effectOverCalled) _currentAnime.onEffectListener?.Invoke(Anime.EffectType.HitOver);
+                if(!_currentAnime._finishedCalled) _currentAnime.onFinishCallback?.Invoke(_currentAnime.actionType);
+            }
             //清空播放队列
             _animeQueue.Clear();
             _animeQueue.Add(animeOption);
-            _play(animeOption);
+            _currentAnime = null;
+            _play();
             
             //链式调用,后续可通过nextPlay方法添加动画到播放队列
             return this;
         }
 
         //播放动画
-        public AnimePlayer play(uint serial, Anime.PlayType playType, float speed = 1f,AnimeEffectListener onEffectListener = null,AnimeCallback onFinishCallback = null)
+        public AnimePlayer play(uint serial, Anime.PlayType playType, float speed = 1f,AnimeEffectListener onEffectListener = null,AnimeCallback onFinishCallback = null,bool keepCallback = false)
         {
-            return play(serial,Anime.DirectionType.North,Anime.ActionType.Idle,playType,speed,onEffectListener,onFinishCallback);
+            return play(serial,Anime.DirectionType.North,Anime.ActionType.Idle,playType,speed,onEffectListener,onFinishCallback,keepCallback);
         }
         
         //不改变Serial情况下播放动画
-        public AnimePlayer play(Anime.DirectionType directionType,Anime.ActionType actionType,Anime.PlayType playType,float Speed=1f,AnimeEffectListener onEffectListener=null,AnimeCallback onFinishCallback=null)
+        public AnimePlayer play(Anime.DirectionType directionType,Anime.ActionType actionType,Anime.PlayType playType,float Speed=1f,AnimeEffectListener onEffectListener=null,AnimeCallback onFinishCallback=null,bool keepCallback = false)
         {
             return play(_currentSerial, directionType, actionType, playType,
-                Speed,onEffectListener, onFinishCallback);
+                Speed,onEffectListener, onFinishCallback,keepCallback);
         }
 
         //播放一次
-        public AnimePlayer playOnce(Anime.DirectionType directionType,Anime.ActionType actionType,float Speed=1f,AnimeEffectListener onEffectListener=null,AnimeCallback onFinishCallback=null)
+        public AnimePlayer playOnce(Anime.DirectionType directionType,Anime.ActionType actionType,float Speed=1f,AnimeEffectListener onEffectListener=null,AnimeCallback onFinishCallback=null,bool keepCallback = false)
         {
             return play(_currentSerial, directionType, actionType, Anime.PlayType.Once,
-                Speed, onEffectListener,onFinishCallback);
+                Speed, onEffectListener,onFinishCallback,keepCallback);
         }
         
         //播放循环
-        public AnimePlayer playLoop(Anime.DirectionType directionType,Anime.ActionType actionType,float Speed=1f,AnimeEffectListener onEffectListener=null,AnimeCallback onFinishCallback=null)
+        public AnimePlayer playLoop(Anime.DirectionType directionType,Anime.ActionType actionType,float Speed=1f,AnimeEffectListener onEffectListener=null,AnimeCallback onFinishCallback=null,bool keepCallback = false)
         {
             return play(_currentSerial, directionType, actionType, Anime.PlayType.Loop,
-                Speed, onEffectListener,onFinishCallback);
+                Speed, onEffectListener,onFinishCallback,keepCallback);
         }
 
         //调整动画方向
@@ -308,9 +334,15 @@ namespace CrossgateToolkit
             if (directionType == _currentAnime.Direction || directionType == Anime.DirectionType.NULL) return;
             // _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, directionType, _currentAnime.actionType,
             //     _currentAnime.playType, _currentAnime.Speed, _currentAnime.onEffectListener,_currentAnime.onFinishCallback);
+            AnimeOption animeOption = CreateAnimeOption(_currentAnime.AnimeSerial, directionType, _currentAnime.actionType,
+                _currentAnime.playType, _currentAnime.Speed);
             _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, directionType, _currentAnime.actionType,
                 _currentAnime.playType, _currentAnime.Speed);
-            _play(_currentAnime);
+            if (animeOption == null) return;
+            _currentAnime = animeOption;
+            if(_animeQueue.Count>0) _animeQueue[0] = _currentAnime;
+            else _animeQueue.Add(_currentAnime);
+            _play();
         }
         public Anime.DirectionType DirectionType
         {
@@ -323,6 +355,23 @@ namespace CrossgateToolkit
                 }
             }
         }
+
+        public void Rotate(bool clockwise = true)
+        {
+            if (_currentAnime == null) return;
+            int direction = (int)_currentAnime.Direction;
+            if (clockwise)
+            {
+                direction += 1;
+                if(direction>7) direction = 0;
+            }
+            else
+            {
+                direction -= 1;
+                if(direction<0) direction = 7;
+            }
+            changeDirection((Anime.DirectionType)direction);
+        }
         
         //调整动画动作类型
         public void changeActionType(Anime.ActionType actionType)
@@ -330,9 +379,13 @@ namespace CrossgateToolkit
             if (actionType == _currentAnime.actionType) return;
             // _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, _currentAnime.Direction,actionType,
             //     _currentAnime.playType, _currentAnime.Speed, _currentAnime.onEffectListener,_currentAnime.onFinishCallback);
-            _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, _currentAnime.Direction,actionType,
+            AnimeOption animeOption = CreateAnimeOption(_currentAnime.AnimeSerial, _currentAnime.Direction,actionType,
                 _currentAnime.playType, _currentAnime.Speed);
-            _play(_currentAnime);
+            if (animeOption == null) return;
+            _currentAnime = animeOption;
+            if(_animeQueue.Count>0) _animeQueue[0] = _currentAnime;
+            else _animeQueue.Add(_currentAnime);
+            _play();
         }
         public Anime.ActionType ActionType
         {
@@ -347,18 +400,19 @@ namespace CrossgateToolkit
         }
 
         //播放
-        private void _play(AnimeOption animeOption)
+        private void _play()
         {
             isPlayable = false;
             _currentAnime = null;
-            
+         
+            AnimeOption animeOption = _animeQueue[0];
             // Debug.Log("AnimePlayer:play " + animeOption.AnimeSerial + "  " + animeOption.actionType);
             
             AnimeFrame[] frames = new AnimeFrame[animeOption.AnimeDetail.FrameCount];
 
             if (isFrameBatch)
             {
-                Anime.BakeAnimeFrames(animeOption.AnimeDetail, _paletIndex, isLinearFilter);
+                Anime.BakeAnimeFrames(animeOption.AnimeDetail, _paletIndex, isLinearFilter, isBatchCompress);
                 //获取动画帧数据
                 for (int i = 0; i < animeOption.AnimeDetail.AnimeFrameInfos.Count; i++)
                 {
@@ -368,7 +422,9 @@ namespace CrossgateToolkit
                     frames[i] = new AnimeFrame();
                     frames[i].Index = i;
                     frames[i].GraphicInfo = animeOption.AnimeDetail.AnimeFrameInfos[i].GraphicInfo;
-                    frames[i].Sprite = animeOption.AnimeDetail.AnimeFrameInfos[i].AnimeSprites[_paletIndex][isLinearFilter];
+                    GraphicDetail graphicDetail =
+                        animeOption.AnimeDetail.AnimeFrameInfos[i].AnimeSprites[_paletIndex][isLinearFilter];
+                    frames[i].Sprite = isPPU100 ? graphicDetail.SpritePPU100 : graphicDetail.Sprite;
                     frames[i].AnimeFrameInfo = animeOption.AnimeDetail.AnimeFrameInfos[i];
                 }
             }
@@ -387,7 +443,7 @@ namespace CrossgateToolkit
                         continue;
                     }
 
-                    int subPaletIndex = 0;
+                    int subPaletIndex = -1;
                     if (animeOption.AnimeDetail.IsHighVersion) subPaletIndex = (int)animeOption.AnimeDetail.Serial;
                     GraphicDetail graphicData =
                         GraphicData.GetGraphicDetail(graphicInfoData, _paletIndex, subPaletIndex, isLinearFilter);
@@ -402,7 +458,7 @@ namespace CrossgateToolkit
                     frames[i] = new AnimeFrame();
                     frames[i].Index = i;
                     frames[i].GraphicInfo = graphicInfoData;
-                    frames[i].Sprite = graphicData.Sprite;
+                    frames[i].Sprite = isPPU100 ? graphicData.SpritePPU100 : graphicData.Sprite;
                     frames[i].AnimeFrameInfo = animeFrameInfo;
                 }
             }
@@ -425,6 +481,15 @@ namespace CrossgateToolkit
             _delay = delayTime*1000;
         }
 
+        // 设置速度
+        public void SetSpeed(float speed)
+        {
+            if (_currentAnime == null) return;
+            _currentAnime.Speed = speed;
+            _currentAnime.FrameRate =
+                _currentAnime.AnimeDetail.CycleTime * 1f / speed / _currentAnime.AnimeDetail.FrameCount;
+        }
+
         //停止播放
         public void Stop()
         {
@@ -432,6 +497,11 @@ namespace CrossgateToolkit
             _currentAnime = null;
             _frames = null;
             gameObject.SetActive(false);
+            
+            //清理缓存
+            if(_imageRenderer!=null) _imageRenderer.sprite = null;
+            if(_spriteRenderer!=null) _spriteRenderer.sprite = null;
+            
         }
 
         //暂停播放
@@ -439,6 +509,13 @@ namespace CrossgateToolkit
         {
             isPlayable = false;
         }
+
+        // 恢复播放
+        public void Play()
+        {
+            if(_currentAnime!=null) isPlayable = true;
+        }
+        
         //恢复播放
         public void Resume()
         {
@@ -454,14 +531,41 @@ namespace CrossgateToolkit
 
         //创建动画配置
         private AnimeOption CreateAnimeOption(uint serial, Anime.DirectionType Direction, Anime.ActionType actionType,
-            Anime.PlayType playType=Anime.PlayType.Once, float Speed = 1f,AnimeEffectListener onEffectListener = null, AnimeCallback onFinishCallback = null)
+            Anime.PlayType playType=Anime.PlayType.Once, float Speed = 1f,AnimeEffectListener onEffectListener = null, AnimeCallback onFinishCallback = null,bool keepCallback = false)
         {
             AnimeDetail animeDetail = Anime.GetAnimeDetail(serial, Direction, actionType);
+            
             if (animeDetail == null)
             {
+                // 动画不存在,尝试查找图档
+                GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoData(serial);
+                if (graphicInfoData != null)
+                {
+                    // 图档存在情况下,不创建动画,直接更新图像显示并返回
+                    GraphicDetail graphicDetail = GraphicData.GetGraphicDetail(graphicInfoData, _paletIndex, 0, isLinearFilter);
+                    if (graphicDetail != null)
+                    {
+                        if (isRenderByImage)
+                        {
+                            _imageRenderer.sprite = isPPU100 ? graphicDetail.SpritePPU100 : graphicDetail.Sprite;
+                            _imageRenderer.SetNativeSize();
+                        }
+                        else
+                        {
+                            _spriteRenderer.sprite = isPPU100 ? graphicDetail.SpritePPU100 : graphicDetail.Sprite;
+                        }
+                        _rectTransform.sizeDelta = new Vector2(graphicDetail.Sprite.rect.width, graphicDetail.Sprite.rect.height);
+                        gameObject.SetActive(true);
+                        return null;
+                    }
+                }
+                else
+                {
+                    return null;    
+                }
                 // Debug.Log("AnimePlayer:AnimeDetail [" + serial + "] is null");
-                return null;
             }
+            
             AnimeOption animeOption = new AnimeOption()
             {
                 AnimeSerial = serial,
@@ -469,11 +573,13 @@ namespace CrossgateToolkit
                 actionType = actionType,
                 playType = playType,
                 Speed = Speed,
-                FrameRate = animeDetail.CycleTime / Speed / animeDetail.FrameCount,
+                FrameRate = animeDetail.CycleTime * 1f / (float)animeDetail.FrameCount /Speed,
                 AnimeDetail = animeDetail,
                 onEffectListener = onEffectListener,
                 onFinishCallback = onFinishCallback,
+                _keepCallback = keepCallback
             };
+            // Debug.Log("AnimePlayer:CreateAnimeOption " + animeOption.AnimeSerial + "  " + animeOption.actionType +" speed:"+Speed+" framerate:"+animeOption.FrameRate);
             return animeOption;
         }
 
@@ -487,13 +593,10 @@ namespace CrossgateToolkit
                 onFinishCallback?.Invoke(actionType);
                 return this;
             }
-            if (_animeQueue.Count == 0)
+            _animeQueue.Add(animeOption);    
+            if (_animeQueue[0] == animeOption)
             {
-                _play(animeOption);
-            }
-            else
-            {
-                _animeQueue.Add(animeOption);    
+                _play();
             }
             
             return this;
@@ -513,12 +616,15 @@ namespace CrossgateToolkit
             int currentFrame = _currentAnime.CurrentFrame;
             // _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, directionType, _currentAnime.actionType,
             //     _currentAnime.playType, _currentAnime.Speed, _currentAnime.onEffectListener,_currentAnime.onFinishCallback);
-            _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, directionType, _currentAnime.actionType,
+            AnimeOption animeOption = CreateAnimeOption(_currentAnime.AnimeSerial, directionType, _currentAnime.actionType,
                 _currentAnime.playType, _currentAnime.Speed);
-            _currentAnime.CurrentFrame = --currentFrame;
+            if (animeOption == null) return;
+            animeOption.CurrentFrame = --currentFrame;
                 
-            _currentAnime.KeepFrame = true;
-            _play(_currentAnime);
+            animeOption.KeepFrame = true;
+            if(_animeQueue.Count>0) _animeQueue[0] = animeOption;
+            else _animeQueue.Add(animeOption);
+            _play();
         }
         
         //更新计算
@@ -534,88 +640,98 @@ namespace CrossgateToolkit
         private void UpdateFrame()
         {
             _delay = 0;
+            AnimeOption playingAnime = _currentAnime;
             if (!isPlayable || _frames.Length == 0) return;
             
             //动画结束
-            if (_currentAnime.CurrentFrame >= _currentAnime.AnimeDetail.FrameCount)
+            if (playingAnime.CurrentFrame >= playingAnime.AnimeDetail.FrameCount)
             {
+                OnCycleCallback?.Invoke(playingAnime.actionType);
                 //循环播放
-                if (_currentAnime.playType == Anime.PlayType.Loop)
+                if (playingAnime.playType == Anime.PlayType.Loop)
                 {
-                    _currentAnime.onFinishCallback?.Invoke(_currentAnime.actionType);
-                    _currentAnime.CurrentFrame = 0;
-                }else if (_currentAnime.playType is Anime.PlayType.Once or Anime.PlayType.OnceAndDestroy)
+                    if(playingAnime==_currentAnime)  playingAnime.onFinishCallback?.Invoke(playingAnime.actionType);
+                    playingAnime._finishedCalled = true;
+                    playingAnime.CurrentFrame = 0;
+                }else if (playingAnime.playType is Anime.PlayType.Once or Anime.PlayType.OnceAndDestroy)
                 {
-                    _animeQueue.RemoveAt(0);
-                    if (_currentAnime.playType == Anime.PlayType.OnceAndDestroy)
+                    if (playingAnime.playType == Anime.PlayType.OnceAndDestroy)
                     {
+                        _animeQueue.Clear();
                         _spriteRenderer.sprite = null;
                         _imageRenderer.sprite = null;
                         _rectTransform.sizeDelta = Vector2.zero;
-                        _currentAnime.onFinishCallback?.Invoke(_currentAnime.actionType);
+                        if(playingAnime==_currentAnime) playingAnime.onFinishCallback?.Invoke(playingAnime.actionType);
                         gameObject.SetActive(false);
                         return;
                     }
-                    //播放下一个动画
-                    if(_animeQueue.Count>0)
+                    if (playingAnime.KeepFrame)
                     {
-                        _currentAnime.onFinishCallback?.Invoke(_currentAnime.actionType);
-                        AnimeOption animeOption = _animeQueue[0];
-                        _play(animeOption);
-                        return;
-                    }else
+                        if(playingAnime==_currentAnime) playingAnime.onFinishCallback?.Invoke(playingAnime.actionType);
+                        playingAnime.CurrentFrame--;
+                    }
+                    else
                     {
-                        if (_currentAnime.KeepFrame)
+                        _animeQueue.RemoveAt(0);
+                        //播放下一个动画
+                        if (_animeQueue.Count > 0)
                         {
-                            _currentAnime.onFinishCallback?.Invoke(_currentAnime.actionType);
-                            // _currentAnime.CurrentFrame--;
+                            AnimeCallback callback = playingAnime.onFinishCallback;
+                            Anime.ActionType actionType = playingAnime.actionType;
+                            // playingAnime.onFinishCallback?.Invoke(playingAnime.actionType);
+                            _play();
+                            callback?.Invoke(actionType);
+                            return;
                         }
                         else
                         {
-                            isPlayable = false;
-                            _currentAnime.onFinishCallback?.Invoke(_currentAnime.actionType);
+                            Pause();
+                            // 回调在Pause之后避免时序问题导致影响下一个动画
+                            playingAnime.onFinishCallback?.Invoke(playingAnime.actionType);
                             return;
                         }
-                        
                     }
-                    
                 }
                 
             }
             
             //问题帧自动跳过
-            if (_currentAnime.CurrentFrame < _frames.Length && _frames[_currentAnime.CurrentFrame] == null)
+            if (playingAnime.CurrentFrame < _frames.Length && _frames[playingAnime.CurrentFrame] == null)
             {
-                _currentAnime.CurrentFrame++;
+                playingAnime.CurrentFrame++;
                 return;
             }
             
             //根据当前帧Sprite动态调整对象大小
-            float width = _frames[_currentAnime.CurrentFrame].Sprite.rect.width * 1f;
-            float height = _frames[_currentAnime.CurrentFrame].Sprite.rect.height * 1f;
+            float width = _frames[playingAnime.CurrentFrame].Sprite.rect.width * 1f;
+            float height = _frames[playingAnime.CurrentFrame].Sprite.rect.height * 1f;
+            if (isPPU100)
+            {
+                width = width / 100f;
+                height = height / 100f;
+            }
 
             Vector3 pos = Vector3.zero;
-            pos.x = _frames[_currentAnime.CurrentFrame].GraphicInfo.OffsetX;
-            pos.y = -_frames[_currentAnime.CurrentFrame].GraphicInfo.OffsetY;
+            pos.x = _frames[playingAnime.CurrentFrame].GraphicInfo.OffsetX;
+            pos.y = -_frames[playingAnime.CurrentFrame].GraphicInfo.OffsetY;
             
             if (isRenderByImage)
             {
-                _imageRenderer.sprite = _frames[_currentAnime.CurrentFrame].Sprite;
+                _imageRenderer.sprite = _frames[playingAnime.CurrentFrame].Sprite;
                 _imageRenderer.SetNativeSize();
-                if (_currentAnime.AnimeDetail.FLAG!=null)
+                if (playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_X) || playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_Y))
                 {
-                    if (_currentAnime.AnimeDetail.FLAG.REVERSE_X)
+                    if (playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_X))
                     {
                         _imageRenderer.transform.localScale = new Vector3(-1, 1, 1);
                         pos.x = -pos.x;
                     }
 
-                    if (_currentAnime.AnimeDetail.FLAG.REVERSE_Y)
+                    if (playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_Y))
                     {
                         _imageRenderer.transform.localScale = new Vector3(1, -1, 1);
                         pos.y = -pos.y;
                     }
-                    
                 }
                 else
                 {
@@ -627,20 +743,28 @@ namespace CrossgateToolkit
             }
             else
             {
-                _spriteRenderer.sprite = _frames[_currentAnime.CurrentFrame].Sprite;
+                _spriteRenderer.sprite = _frames[playingAnime.CurrentFrame].Sprite;
                 _rectTransform.sizeDelta = new Vector2(width, height);
                 _spriteRenderer.size = new Vector2(width, height);
                 _rectTransform.pivot = new Vector2(0.5f,0f);
-                if (_currentAnime.AnimeDetail.FLAG!=null)
+                // Vector3 scale = isPPU100 ? new Vector3(100f, 100f, 100f) : Vector3.one;
+                // _rectTransform.localScale = scale;
+                if (playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_X) || playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_Y))
                 {
-                    if (_currentAnime.AnimeDetail.FLAG.REVERSE_X)
+                    if (playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_X))
                     {
                         _spriteRenderer.flipX = true;
+                    }else
+                    {
+                        _spriteRenderer.flipX = false;
                     }
                     
-                    if (_currentAnime.AnimeDetail.FLAG.REVERSE_Y)
+                    if (playingAnime.AnimeDetail.FLAG.HasFlag(AnimeFlag.REVERSE_Y))
                     {
                         _spriteRenderer.flipY = true;
+                    }else
+                    {
+                        _spriteRenderer.flipY = false;
                     }
                 }
                 else
@@ -650,18 +774,35 @@ namespace CrossgateToolkit
                 }
                 _rectTransform.localPosition = Vector3.zero;
             }
-            frameTexture = _frames[_currentAnime.CurrentFrame].Sprite.texture;
+            frameTexture = _frames[playingAnime.CurrentFrame].Sprite.texture;
             
             _timer = Time.time * 1000;
             
             //动画事件帧监听
-            if (_frames[_currentAnime.CurrentFrame].AnimeFrameInfo.Effect > 0)
-                _currentAnime.onEffectListener?.Invoke(_frames[_currentAnime.CurrentFrame].AnimeFrameInfo.Effect);
+            if (playingAnime==_currentAnime && _frames[playingAnime.CurrentFrame].AnimeFrameInfo.Effect > 0)
+                playingAnime.onEffectListener?.Invoke(_frames[playingAnime.CurrentFrame].AnimeFrameInfo.Effect);
+            if (playingAnime==_currentAnime && _frames[playingAnime.CurrentFrame].AnimeFrameInfo.Effect == Anime.EffectType.HitOver)
+                playingAnime._effectOverCalled = true;
             //音频事件帧监听
-            if (_frames[_currentAnime.CurrentFrame].AnimeFrameInfo.AudioIndex > 0)
-                onAudioListener?.Invoke(_frames[_currentAnime.CurrentFrame].AnimeFrameInfo.AudioIndex);
+            if (playingAnime==_currentAnime && _frames[playingAnime.CurrentFrame].AnimeFrameInfo.AudioIndex > 0)
+                onAudioListener?.Invoke(_frames[playingAnime.CurrentFrame].AnimeFrameInfo.AudioIndex);
             
-            _currentAnime.CurrentFrame++;
+            playingAnime.CurrentFrame++;
+        }
+
+        public void OnPointerEnter(PointerEventData eventData)
+        {
+            onMouseListener?.Invoke(MouseType.Enter);
+        }
+
+        public void OnPointerExit(PointerEventData eventData)
+        {
+            onMouseListener?.Invoke(MouseType.Exit);
+        }
+
+        public void OnPointerClick(PointerEventData eventData)
+        {
+            onMouseListener?.Invoke(MouseType.Click);
         }
     }
 }

+ 71 - 9
CrossgateToolkit/Audio.cs

@@ -11,6 +11,7 @@
  * 如有其他需要可调整加载方式
  */
 
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO;
@@ -20,6 +21,35 @@ using UnityEngine.Networking;
 
 namespace CrossgateToolkit
 {
+    public class EffectPlayer : MonoBehaviour
+    {
+        public AudioSource audioSource;
+        public delegate void OnAudioPlayEnd();
+        public OnAudioPlayEnd onAudioPlayEnd;
+        private void Awake()
+        {
+            audioSource = gameObject.AddComponent<AudioSource>();
+            audioSource.loop = false;
+            audioSource.playOnAwake = false;
+        }
+
+        public void Play(AudioClip clip)
+        {
+            audioSource.clip = clip;
+            audioSource.Play();
+            StartCoroutine(wait(clip.length));
+        }
+        
+        
+        
+        IEnumerator wait(float length)
+        {
+            yield return new WaitForSeconds(length + 0.3f);
+            onAudioPlayEnd?.Invoke();
+        }
+        
+        
+    }
     public static class Audio
     {
 
@@ -27,7 +57,11 @@ namespace CrossgateToolkit
         private static Dictionary<int, AudioClip> _bgmDic = new Dictionary<int, AudioClip>();
         // 声效音频缓存
         private static Dictionary<int, AudioClip> _effectDic = new Dictionary<int, AudioClip>();
-
+        private static Queue<EffectPlayer> _effectPlayerPool = new Queue<EffectPlayer>();
+        private static GameObject _effectPlayerContainer;
+        private static int lastEffectId = -1;
+        private static int lastEffectTime = -1;
+        public static float EffectVolume = 1f;
         public enum Type
         {
             BGM,
@@ -36,11 +70,13 @@ namespace CrossgateToolkit
         // 播放指定类型、编号的音频AudioClip
         public static void Play(AudioSource audioSource,Type type, int id)
         {
+            // 对于同时间大量同一音效的播放,只播放一次
+            if (type == Type.EFFECT && id == lastEffectId && Time.time - lastEffectTime < 0.1f) return;
             AudioClip audioClip;
             Dictionary<int,AudioClip> dic = type == Type.BGM ? _bgmDic : _effectDic;
             if (dic.TryGetValue(id, out audioClip))
             {
-                _playAudio(audioSource, audioClip);
+                _playAudio(type,audioSource, audioClip);
             }
             else
             {
@@ -55,11 +91,12 @@ namespace CrossgateToolkit
                         if (audioClip == null) return;
                         
                         dic[id] = audioClip;
-                        _playAudio(audioSource, audioClip);
+                        _playAudio(type,audioSource, audioClip);
                     }
                     else
                     {
                         DirectoryInfo directoryInfo = new DirectoryInfo(path);
+                        if(!directoryInfo.Exists) return;
                         FileInfo[] files = directoryInfo.GetFiles(audioName + ".wav", SearchOption.AllDirectories);
                         if (files.Length > 0)
                         {
@@ -70,7 +107,7 @@ namespace CrossgateToolkit
                                 if (loadedAudioClip != null)
                                 {
                                     dic[id] = loadedAudioClip;
-                                    _playAudio(audioSource, loadedAudioClip);
+                                    _playAudio(type,audioSource, loadedAudioClip);
                                 }
                             }));
                         }
@@ -80,11 +117,35 @@ namespace CrossgateToolkit
             }
         }
 
-        private static void _playAudio(AudioSource audioSource, AudioClip audioClip)
+        private static void _playAudio(Type type,AudioSource audioSource, AudioClip audioClip)
         {
-            audioSource.Stop();
-            audioSource.clip = audioClip;
-            audioSource.Play();
+            if (type == Type.EFFECT && audioSource == null)
+            {
+                if(_effectPlayerContainer==null) _effectPlayerContainer = new GameObject("EffectPlayerContainer");
+                EffectPlayer effectPlayer = null;
+                if (_effectPlayerPool.Count > 0)
+                {
+                    effectPlayer = _effectPlayerPool.Dequeue();
+                }
+                else
+                {
+                    effectPlayer = new GameObject("EffectPlayer").AddComponent<EffectPlayer>();
+                    effectPlayer.transform.SetParent(_effectPlayerContainer.transform);
+                    effectPlayer.onAudioPlayEnd = () =>
+                    {
+                        effectPlayer.audioSource.clip = null;
+                        _effectPlayerPool.Enqueue(effectPlayer);
+                    };
+                }
+                effectPlayer.audioSource.volume = EffectVolume;
+                effectPlayer.Play(audioClip);
+            }
+            else
+            {
+                audioSource.Stop();
+                audioSource.clip = audioClip;
+                audioSource.Play();
+            }
         }
         
         private delegate void AudioClipLoaded(AudioClip audioClip);
@@ -406,7 +467,8 @@ namespace CrossgateToolkit
             [436] = "37bird",
             [437] = "38make_gild",//升级
             [438] = "39levelup",
-
+            [500] = "cgply10a",
+            [501] = "cgply10b",
         };
     }
 }

+ 469 - 0
CrossgateToolkit/DynamicGraphic.cs

@@ -0,0 +1,469 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using UnityEngine;
+using Debug = UnityEngine.Debug;
+using Object = UnityEngine.Object;
+
+namespace CrossgateToolkit
+{
+    // 针对网游设置的可扩展动态图集
+    // 包含图集建立、自动维护和清理
+    public class DynamicGraphic
+    {
+        // 动态图集数据
+        public class DynamicData
+        {
+            // 图集高度
+            public int Width;
+            // 图集宽度
+            public int Height;
+            // 图集Texture
+            public Texture2D Texture2D;
+            // 图集Sprite引用及编号银映射
+            // public List<Sprite> RefSpriteList = new List<Sprite>();
+            // public Dictionary<uint, int> RefSpriteMap = new Dictionary<uint, int>();
+            // public Dictionary<uint,Color> PrimaryColor = new Dictionary<uint, Color>();
+            // 二维数组存储当前所有像素点可用位置 - 此数组在4096*4096情况下,占用约16MB内存
+            // public bool[,] PixelAvaliable;
+            // 在二维空间中,记录当前可用的矩形区域
+            public List<Rect> AvaliableRect = new List<Rect>();
+            public bool avaliable = true;
+            public bool CompressWhenFull = false;
+            
+            public void Init(bool Linear = false)
+            {
+                // 初始化时填补List的0位,避免序列号为0的图像无法获取
+                // RefSpriteList.Add(null);
+                AvaliableRect.Add(new Rect(0, 0, Width, Height));
+				float time = Time.realtimeSinceStartup;
+                Texture2D = new Texture2D(Width, Height, TextureFormat.RGBA4444, false);
+                Texture2D.name = "DynamicGraphicTexture";
+                Texture2D.filterMode = Linear ? FilterMode.Bilinear : FilterMode.Point;
+                Texture2D.wrapMode = TextureWrapMode.Clamp;
+                // Texture2D.Compress(true);
+                // Texture2d填充透明像素
+                // Color32[] transColor = new Color32[Width * Height];
+                // for (int i = 0; i < Width * Height; i++)
+                // {
+                //     transColor[i] = Color.clear;
+                // }
+                // Texture2D.SetPixels32(transColor);
+                // Texture2D.Apply();
+                // transColor = null;
+            }
+
+            private int bestLongSideFit;
+            private int bestShortSideFit;
+            
+            // 查找最佳位置
+            public Rect FindBestFitRect(int width, int height)
+            {
+                Rect bestRect = default;
+                bestShortSideFit = int.MaxValue;
+                bool hasAvaliable = false;
+                for (int i = 0; i < AvaliableRect.Count; ++i)
+                {
+	                if(AvaliableRect[i].width>64 && AvaliableRect[i].height>48) hasAvaliable = true;
+                    if (AvaliableRect[i].width >= width && AvaliableRect[i].height >= height)
+                    {
+                        int leftoverHoriz = Mathf.Abs((int)AvaliableRect[i].width - width);
+                        int leftoverVert = Mathf.Abs((int)AvaliableRect[i].height - height);
+                        int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
+                        int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
+
+                        if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit))
+                        {
+                            bestRect.x = AvaliableRect[i].x;
+                            bestRect.y = AvaliableRect[i].y;
+                            bestRect.width = width;
+                            bestRect.height = height;
+                            bestShortSideFit = shortSideFit;
+                            bestLongSideFit = longSideFit;
+                        }
+                    }
+                    
+                }
+                // 检测过程中判断是否还有有效空间
+                avaliable = hasAvaliable;
+                if (!avaliable)
+                {
+	                // 当有效空间为0时,对texture压缩
+	                if(CompressWhenFull) Texture2D.Compress(true);
+                }
+                return bestRect;
+            }
+            
+            // 插入位置
+            public void InsertRect (Rect node)
+            {
+	            // float time = Time.time;
+	            // Stopwatch stopwatch = new Stopwatch();
+	            // stopwatch.Start();
+                int numRectanglesToProcess = AvaliableRect.Count;
+                for (int i = 0; i < numRectanglesToProcess; ++i)
+                {
+                    if (SplitFreeRect(AvaliableRect[i], ref node))
+                    {
+                        AvaliableRect.RemoveAt(i);
+                        --i;
+                        --numRectanglesToProcess;
+                    }
+                }
+
+                // 重新整理可用区域,去除重叠区域
+                OptFreeList();
+                // stopwatch.Stop();
+                // Debug.Log("[CGTool] 插入位置耗时: " + stopwatch.ElapsedMilliseconds + "ms");
+            }
+            
+            // 分割区域
+            bool SplitFreeRect (Rect freeRect, ref Rect useRect)
+            {
+                Rect rect = default;
+		        if (useRect.x >= freeRect.x + freeRect.width || useRect.x + useRect.width <= freeRect.x ||
+			        useRect.y >= freeRect.y + freeRect.height || useRect.y + useRect.height <= freeRect.y)
+			        return false;
+
+		        if (useRect.x < freeRect.x + freeRect.width && useRect.x + useRect.width > freeRect.x)
+		        {
+			        if (useRect.y > freeRect.y && useRect.y < freeRect.y + freeRect.height)
+			        {
+				        rect = freeRect;
+				        rect.height = useRect.y - rect.y;
+                        AvaliableRect.Add(rect);
+			        }
+                    
+			        if (useRect.y + useRect.height < freeRect.y + freeRect.height)
+			        {
+				        rect = freeRect;
+				        rect.y = useRect.y + useRect.height;
+				        rect.height = freeRect.y + freeRect.height - (useRect.y + useRect.height);
+                        AvaliableRect.Add(rect);
+			        }
+		        }
+
+		        if (useRect.y < freeRect.y + freeRect.height && useRect.y + useRect.height > freeRect.y)
+		        {
+			        if (useRect.x > freeRect.x && useRect.x < freeRect.x + freeRect.width)
+			        {
+                        rect = freeRect;
+				        rect.width = useRect.x - rect.x;
+                        AvaliableRect.Add(rect);
+			        }
+                    
+			        if (useRect.x + useRect.width < freeRect.x + freeRect.width)
+			        {
+				        rect = freeRect;
+				        rect.x = useRect.x + useRect.width;
+				        rect.width = freeRect.x + freeRect.width - (useRect.x + useRect.width);
+                        AvaliableRect.Add(rect);
+			        }
+		        }
+
+		        return true;
+	        }
+
+            // 重新整理可用区域,去除重叠区域
+	        void OptFreeList ()
+	        {
+		        for (int i = 0; i < AvaliableRect.Count; ++i)
+			        for (int j = i + 1; j < AvaliableRect.Count; ++j)
+			        {
+				        if (IsIntersect(AvaliableRect[i], AvaliableRect[j]))
+				        {
+					        AvaliableRect.RemoveAt(i);
+					        --i;
+					        break;
+				        }
+				        if (IsIntersect(AvaliableRect[j], AvaliableRect[i]))
+				        {
+					        AvaliableRect.RemoveAt(j);
+					        --j;
+				        }
+			        }
+	        }
+
+            // 比对两个矩形是否重叠
+	        bool IsIntersect (Rect a, Rect b)
+	        {
+		        return a.x >= b.x && a.y >= b.y
+			        && a.x + a.width <= b.x + b.width
+			        && a.y + a.height <= b.y + b.height;
+	        }
+        }
+        
+        private class ClearMono:MonoBehaviour
+        {
+	        public void Clear()
+	        {
+		        Destroy(gameObject);
+	        }
+        }
+
+        // 动态图集最大宽度
+        public int MaxGraphicWidth;
+        // 动态图集最大高度
+        public int MaxGraphicHeight;
+        // 动态图集内部间隔
+        public int Padding;
+        // 动态图集是否使用线性采样
+        public bool Linear;
+        // 动态图集是否使用100ppu
+        public bool PPU100;
+        // 当动态图档无可用空间时进行压缩
+        public bool CompressWhenFull;
+        // 使用调色板编号
+        public int PaletIndex;
+        // 动态图集固定图像尺寸
+        public Vector2Int FixedSize = default;
+        // 当前动态图集包含的图集数据
+        public List<DynamicData> DynamicDatas = new List<DynamicData>();
+        // Sprite缓存
+        public Dictionary<uint, Sprite> SpriteCache = new Dictionary<uint, Sprite>();
+        // 主色缓存
+        public Dictionary<uint, Color> PrimaryColorCache = new Dictionary<uint, Color>();
+
+        private static ClearMono clearMono;
+        
+        /// <summary>
+        /// 创建动态图集
+        /// </summary>
+        /// <param name="GraphicWidth">图集最大宽度</param>
+        /// <param name="GraphicHeight">图集最大高度</param>
+        /// <param name="GraphicPadding">图集各图档间隔</param>
+        /// <param name="palet">调色板编号</param>
+        /// <param name="linear">线性过滤</param>
+        /// <param name="ppu100">以100的PPU方式生成Sprite对象</param>
+        /// <param name="compressWhenFull">当图集对象无可用空间时,对Texture进行压缩</param>
+        public DynamicGraphic(int GraphicWidth, int GraphicHeight, int GraphicPadding = 0,int palet = 0,bool linear = false,bool ppu100 = false,bool compressWhenFull = false)
+        {
+            MaxGraphicWidth = GraphicWidth;
+            MaxGraphicHeight = GraphicHeight;
+            Padding = GraphicPadding;
+            PaletIndex = palet;
+            Linear = linear;
+            PPU100 = ppu100;
+            CompressWhenFull = compressWhenFull;
+            if (clearMono == null) clearMono = new GameObject("DynamicGraphicClear").AddComponent<ClearMono>();
+        }
+        
+        
+        // 清理图集图像
+        public void Clear(bool autoGC = false)
+        {
+	        // clearMono.StartCoroutine(ClearCoroutine(DynamicDatas, autoGC));
+	        ClearCoroutine(DynamicDatas, autoGC);
+	        DynamicDatas = new List<DynamicData>();
+        }
+        
+        // 获取图像
+        public Sprite GetSprite(uint Serial)
+        {
+            Sprite sprite = null;
+            // 检查缓存
+            SpriteCache.TryGetValue(Serial, out sprite);
+            if (sprite != null) return sprite;
+
+            // 获取图档数据
+            GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoData(Serial);
+            if (graphicInfoData == null)
+            {
+	            Debug.LogError("[CGTool] 无法获取图档数据: " + Serial);
+	            return null;
+            }
+            // Debug.Log("[CGTool] 获取图档数据: " + Serial + " " + graphicInfoData.Width + "x" + graphicInfoData.Height);
+
+            // 更新尺寸,增加Padding
+            int width = (int)graphicInfoData.Width + Padding;
+            int height = (int)graphicInfoData.Height + Padding;
+            
+            // 首先检查最新的图集数据是否有空间
+            DynamicData lastDynamicData = null;
+            bool avaliable = true;
+            Rect avaliableRect = default;
+
+            // 检测是否有图集数据
+            if (DynamicDatas.Count > 0)
+            {
+	            for (var i = 0; i < DynamicDatas.Count; i++)
+	            {
+		            lastDynamicData = DynamicDatas[i];
+		            if (!lastDynamicData.avaliable)
+		            {
+			            lastDynamicData = null;
+			            continue;
+		            }
+		            avaliableRect = lastDynamicData.FindBestFitRect(width, height);
+		            if (avaliableRect != default) break;
+	            }
+                // lastDynamicData = DynamicDatas[^1];
+                // avaliableRect = lastDynamicData.FindBestFitRect(width, height);
+            }
+            else avaliable = false;
+
+            
+            
+            // 如果没有可用数据集或没有空间则创建新的图集
+            if (!avaliable || avaliableRect==default)
+            {
+                DynamicData newDynamicData = new DynamicData();
+                newDynamicData.Width = MaxGraphicWidth;
+                newDynamicData.Height = MaxGraphicHeight;
+                
+                // 初始化
+                newDynamicData.Init(Linear);
+                newDynamicData.CompressWhenFull = CompressWhenFull;
+                DynamicDatas.Add(newDynamicData);
+                lastDynamicData = newDynamicData;
+                avaliableRect = lastDynamicData.FindBestFitRect(width, height);
+                Debug.Log("[CGTool] 创建新图集: " + newDynamicData.Width + "x" + newDynamicData.Height + " 当前图集数量: " +
+                          DynamicDatas.Count);
+            }
+            
+            
+            // 获取图档像素
+            List<Color32> color32s;
+            Color PrimaryColor = Color.clear;
+            Texture texture = null;
+            bool useCache = true;
+
+            // 填充图集,填充方式为可用区域的左下角开始
+            if (useCache)
+            {
+	            // 二级缓存模式
+	            GraphicDetail graphicDetail = GraphicData.GetGraphicDetail(graphicInfoData, PaletIndex, 0, Linear, true);
+	            if (graphicDetail == null) return null;
+	            texture = graphicDetail.Sprite.texture;
+	            PrimaryColor = graphicDetail.PrimaryColor;
+	            Graphics.CopyTexture(texture, 0, 0, 0,0,
+		            (int)graphicInfoData.Width, (int)graphicInfoData.Height, lastDynamicData.Texture2D, 0, 0, (int)avaliableRect.x, (int)avaliableRect.y);
+            }
+            else
+            {
+	            // 非缓存模式
+	            color32s = GraphicData.UnpackGraphic(graphicInfoData, PaletIndex);
+	            if (color32s == null || color32s.Count == 0) return null;
+            
+	            // 去除最后一位主色
+	            // lastDynamicData.PrimaryColor[Serial] = color32s[^1];
+	            PrimaryColor = color32s[^1];
+	            color32s.RemoveAt(color32s.Count - 1);
+	            lastDynamicData.Texture2D.SetPixels32((int)avaliableRect.x, (int)avaliableRect.y,
+	             (int)graphicInfoData.Width, (int)graphicInfoData.Height, color32s.ToArray(), 0);
+	            lastDynamicData.Texture2D.Apply(false, false);
+	            color32s = null;
+            }
+            
+            //直接通过Texture2D做偏移,并转为Sprite的偏移量
+            Vector2 offset = new Vector2(0f, 1f);
+            offset.x += -(graphicInfoData.OffsetX * 1f) / graphicInfoData.Width;
+            offset.y -= (-graphicInfoData.OffsetY * 1f) / graphicInfoData.Height;
+            
+            // 创建Sprite
+            sprite = Sprite.Create(lastDynamicData.Texture2D,
+	            new Rect(avaliableRect.x, avaliableRect.y, graphicInfoData.Width, graphicInfoData.Height),
+	            offset, PPU100 ? 100 : 1, 0, SpriteMeshType.FullRect);
+            sprite.name = "DG-" + Serial;
+            
+            SpriteCache[Serial] = sprite;
+            PrimaryColorCache[Serial] = PrimaryColor;
+
+            // 更新当前可用区域
+            lastDynamicData.InsertRect(avaliableRect);
+            return sprite;
+        }
+
+        public IEnumerator GetSpriteSync(uint Serial, Action<Sprite> callback)
+        {
+	        Sprite sprite = null;
+			SpriteCache.TryGetValue(Serial, out sprite);
+			if (sprite!=null)
+			{
+				callback?.Invoke(sprite);
+				yield break;
+			}
+			yield return null;
+	        sprite = GetSprite(Serial);
+	        callback?.Invoke(sprite);
+		}
+        
+        // 获取主色
+        public Color GetPrimaryColor(uint Serial)
+        {
+	        SpriteCache.TryGetValue(Serial, out var sprite);
+	        if(sprite==null) clearMono.StartCoroutine(GetSpriteSync(Serial, s => sprite = s));
+	        PrimaryColorCache.TryGetValue(Serial, out var color);
+	        // if(color==null) color = Color.clear;
+	        return color;
+		}
+        
+        private void ClearCoroutine(List<DynamicData> dynamicDatas, bool autoGC = false)
+        {
+	        PrimaryColorCache.Clear();
+	        Dictionary<uint, Sprite> spriteCache = SpriteCache;
+	        SpriteCache = new Dictionary<uint, Sprite>();
+			     
+	        List<Texture> textures = new List<Texture>();
+			     
+	        foreach (var keyValuePair in spriteCache)
+	        {
+		        Sprite sprite = keyValuePair.Value;
+		        if (sprite == null) continue;
+		        if(!textures.Contains(sprite.texture)) textures.Add(sprite.texture);
+		        Object.Destroy(sprite);
+		        sprite = null;
+		        GraphicData.ClearCache(keyValuePair.Key);
+	        }
+	        foreach (var texture in textures)
+	        {
+		        Object.Destroy(texture);
+		        // Resources.UnloadAsset(texture);
+	        }
+			     
+	        DynamicDatas.Clear();
+	        if (autoGC)
+	        {
+		        Resources.UnloadUnusedAssets();
+		        // System.GC.Collect();
+	        }
+        }
+        
+        // 协程清理有可能导致无法清除干净,暂时不用
+  //       IEnumerator ClearCoroutine(List<DynamicData> dynamicDatas, bool autoGC = false)
+  //       {
+	 //        PrimaryColorCache.Clear();
+	 //        Dictionary<uint, Sprite> spriteCache = SpriteCache;
+	 //        SpriteCache = new Dictionary<uint, Sprite>();
+		// 	
+		// 	List<Texture> textures = new List<Texture>();
+		// 	
+		// 	foreach (var keyValuePair in spriteCache)
+		// 	{
+		// 		Sprite sprite = keyValuePair.Value;
+		// 		if (sprite == null) continue;
+		// 		if(!textures.Contains(sprite.texture)) textures.Add(sprite.texture);
+		// 		spriteCache.Remove(keyValuePair.Key);
+		// 		sprite = null;
+		// 		// Resources.UnloadAsset(sprite);
+		// 	}
+		// 	yield return null;
+		// 	foreach (var texture in textures)
+		// 	{
+		// 		Object.Destroy(texture);
+		// 		yield return null;
+		// 	}
+		// 	
+		// 	DynamicDatas.Clear();
+		// 	yield return null;
+		// 	if (autoGC)
+		// 	{
+		// 		Resources.UnloadUnusedAssets();
+		// 		// System.GC.Collect();
+		// 	}
+		// }
+    }
+}

+ 3 - 0
CrossgateToolkit/DynamicGraphic.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b2d30f38066d4e8e9829ca2e498bf3ac
+timeCreated: 1705439693

+ 5 - 8
CrossgateToolkit/Graphic.cs

@@ -38,8 +38,7 @@ namespace CrossgateToolkit
         {
             // 解析Bin文件目录结构
             if(!Directory.Exists(CGTool.PATH.BIN)) throw new Exception("图档目录不存在,请检查CGTool中是否配置相应PATH路径");
-
-            float time = Time.realtimeSinceStartup;
+            //float time = Time.realtimeSinceStartup;
             // 整理目录结构,生成对应待处理文件列表
             List<DirectoryInfo> _directorys = new List<DirectoryInfo>();
             _directorys.Add(new DirectoryInfo(CGTool.PATH.BIN));
@@ -48,7 +47,6 @@ namespace CrossgateToolkit
             {
                 AnalysisDirectory(directory);
             }
-
             Debug.Log("[CGTool] 图档资源查找完毕,共找到: (" + _graphicFilePairs.Count + ") 个图档文件, (" + _animeFilePairs.Count +
                       ") 个动画文件");
             
@@ -57,14 +55,13 @@ namespace CrossgateToolkit
             {
                 GraphicInfo.Init(graphicFilePair.Version,graphicFilePair.InfoFile, graphicFilePair.DataFile);
             }
-            
+
             // 预加载 Anime
             foreach (FilePair animeFilePair in _animeFilePairs)
             {
                 Anime.Init(animeFilePair.Version, animeFilePair.InfoFile, animeFilePair.DataFile);
             }
-            
-            Debug.Log("[CGTool] 图档资源加载完毕,耗时: " + (Time.realtimeSinceStartup - time) + "s");
+            //Debug.Log("[CGTool] 图档资源加载完毕,耗时: " + (Time.realtimeSinceStartup - time) + "s");
         }
 
         // 版本号分析
@@ -186,7 +183,7 @@ namespace CrossgateToolkit
         {
             GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoData(serial);
             if (graphicInfoData == null) return null;
-            return GraphicData.GetGraphicDetail(graphicInfoData, palet, 0, linerFilter);
+            return GraphicData.GetGraphicDetail(graphicInfoData, palet, -1, linerFilter);
         }
         
         // 获取图档数据
@@ -194,7 +191,7 @@ namespace CrossgateToolkit
         {
             GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoDataByIndex(Version, index);
             if (graphicInfoData == null) return null;
-            return GraphicData.GetGraphicDetail(graphicInfoData, palet, 0, linerFilter);
+            return GraphicData.GetGraphicDetail(graphicInfoData, palet, -1, linerFilter);
         }
     }
 }

+ 181 - 99
CrossgateToolkit/GraphicData.cs

@@ -39,12 +39,12 @@ namespace CrossgateToolkit
         public int Palet;
         //图档Sprite
         public Sprite Sprite;
+        //图档Sprite(100%PPU)
+        public Sprite SpritePPU100;
         //图档主色调,用于小地图绘制
         public Color32 PrimaryColor;
     }
     
-    
-    
     // 图档数据
     public static class GraphicData
     {
@@ -55,36 +55,68 @@ namespace CrossgateToolkit
         public static Dictionary<GraphicInfoData,Dictionary<int,GraphicDetail>> _linearCache = new Dictionary<GraphicInfoData, Dictionary<int, GraphicDetail>>();
         
         // 获取图档
-        public static GraphicDetail GetGraphicDetail(GraphicInfoData graphicInfoData, int palet = 0,int subPalet = 0,bool asLinear = false)
+        public static GraphicDetail GetGraphicDetail(GraphicInfoData graphicInfoData, int palet = 0,int subPalet = -1,bool asLinear = false,bool cache = true)
         {
             GraphicDetail graphicDetail = null;
 
-            var checkCache = asLinear ? _linearCache : _cache;
-            
-            if (checkCache.ContainsKey(graphicInfoData))
+            if (cache)
             {
-                if (checkCache[graphicInfoData].ContainsKey(palet))
+                var checkCache = asLinear ? _linearCache : _cache;
+            
+                if (checkCache.ContainsKey(graphicInfoData))
                 {
-                    graphicDetail = checkCache[graphicInfoData][palet];
+                    if (checkCache[graphicInfoData].ContainsKey(palet))
+                    {
+                        graphicDetail = checkCache[graphicInfoData][palet];
+                    }
+                    else
+                    {
+                        graphicDetail = _loadGraphicDetail(graphicInfoData, palet, subPalet, asLinear);
+                        checkCache[graphicInfoData].Add(palet, graphicDetail);
+                    }
                 }
                 else
                 {
                     graphicDetail = _loadGraphicDetail(graphicInfoData, palet, subPalet, asLinear);
+                    checkCache.Add(graphicInfoData, new Dictionary<int, GraphicDetail>());
                     checkCache[graphicInfoData].Add(palet, graphicDetail);
                 }
             }
             else
             {
                 graphicDetail = _loadGraphicDetail(graphicInfoData, palet, subPalet, asLinear);
-                checkCache.Add(graphicInfoData, new Dictionary<int, GraphicDetail>());
-                checkCache[graphicInfoData].Add(palet, graphicDetail);
             }
             
             return graphicDetail;
         }
+
+        public static void ClearCache(uint serial,int palet =0,bool asLinear = false)
+        {
+            var checkCache = asLinear ? _linearCache : _cache;
+            GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoData(serial);
+            if (graphicInfoData == null) return;
+            if (checkCache.ContainsKey(graphicInfoData))
+            {
+                if (checkCache[graphicInfoData].ContainsKey(palet))
+                {
+                    GraphicDetail graphicDetail = checkCache[graphicInfoData][palet];
+                    if (graphicDetail != null)
+                    {
+                        UnityEngine.Object.Destroy(graphicDetail.Sprite.texture);
+                        if (graphicDetail.Sprite != null)
+                        {
+                            UnityEngine.Object.Destroy(graphicDetail.Sprite);
+                            UnityEngine.Object.Destroy(graphicDetail.SpritePPU100);
+                        }
+                        graphicDetail = null;
+                    }
+                    checkCache[graphicInfoData].Remove(palet);
+                }
+            }
+        }
         
         // 解析图档
-        private static GraphicDetail _loadGraphicDetail(GraphicInfoData graphicInfoData,int palet = 0,int subPalet = 0,bool asLinear = false)
+        private static GraphicDetail _loadGraphicDetail(GraphicInfoData graphicInfoData,int palet = 0,int subPalet = -1,bool asLinear = false)
         {
             GraphicDetail graphicDetail = new GraphicDetail();
             
@@ -104,18 +136,21 @@ namespace CrossgateToolkit
             Texture2D texture2D;
             Sprite sprite;
 
-            // RGBA4444 减少内存占用
+            // RGBA4444 减少内存占用-对边缘增加1px空白,避免黑线
             texture2D = new Texture2D((int) graphicInfoData.Width, (int) graphicInfoData.Height,
                 TextureFormat.RGBA4444, false, asLinear);
             // 固定点过滤
             if (asLinear) texture2D.filterMode = FilterMode.Bilinear;
             else texture2D.filterMode = FilterMode.Point;
+            texture2D.wrapMode = TextureWrapMode.Clamp;
             texture2D.SetPixels32(pixels.ToArray());
-            // texture2D.LoadRawTextureData(rawTextureData);
             texture2D.Apply();
             
-            sprite = Sprite.Create(texture2D, new Rect(0, 0, texture2D.width, texture2D.height), offset, 1,1,SpriteMeshType.FullRect);
+            sprite = Sprite.Create(texture2D, new Rect(0, 0, texture2D.width, texture2D.height), offset, 1,0,SpriteMeshType.FullRect);
 
+            // 创建PPU为100的sprite
+            Sprite spritePPU100 = Sprite.Create(texture2D, new Rect(0, 0, texture2D.width, texture2D.height), offset, 100,0,SpriteMeshType.FullRect);
+            
             //写入数据
             graphicDetail.Index = graphicInfoData.Index;
             graphicDetail.Serial = graphicInfoData.Serial;
@@ -125,6 +160,7 @@ namespace CrossgateToolkit
             graphicDetail.OffsetY = graphicInfoData.OffsetY;
             graphicDetail.Palet = palet;
             graphicDetail.Sprite = sprite;
+            graphicDetail.SpritePPU100 = spritePPU100;
             return graphicDetail;
         }
 
@@ -134,6 +170,9 @@ namespace CrossgateToolkit
         {
             public int BatchOffsetX;
             public int BatchOffsetY;
+            public GraphicInfoData GraphicInfoData;
+            // public List<Color32> Pixels;
+            public Color32 PrimaryColor;
             public GraphicDetail GraphicDetail;
         }
         // 图档合批
@@ -150,21 +189,26 @@ namespace CrossgateToolkit
         /// 通过指定图档序列,对图档进行合批处理,并返回合批后的图档数据
         /// </summary>
         /// <param name="graphicInfoDatas">图档索引数据序列</param>
+        /// <param name="AsSerialBatch">以图档编号而非索引号返回合批数据</param>
         /// <param name="palet">调色板序号</param>
+        /// <param name="subPalet">副调色板编号(针对动画)</param>
+        /// <param name="linear">线性过滤</param>
         /// <param name="maxTextureSize">单个Texture最大尺寸,地面数据建议2048,物件数据建议4096</param>
         /// <param name="padding">图档间隔,可以有效避免图档渲染时出现多余的黑边或像素黏连</param>
-        /// <returns>合批后的图档数据,Key(unit)为图档数据编号,Value为图档数据</returns>
-        public static Dictionary<uint, GraphicDetail> BakeGraphics(List<GraphicInfoData> graphicInfoDatas,int palet = 0, int maxTextureSize = 2048,int padding = 0)
+        /// <param name="compress">启用压缩</param>
+        /// <returns>合批后的图档数据,Key(unit)为图档数据编号(或AsSerialBatch为false时为图档序号),Value为图档数据</returns>
+        public static Dictionary<uint, GraphicDetail> BakeGraphics(List<GraphicInfoData> graphicInfoDatas,bool AsSerialBatch = true,int palet = 0,int subPalet = -1,bool linear = false,int maxTextureSize = 2048,int padding = 0,bool compress = false)
         {
             // 单个Texture最大尺寸
             int maxWidth = maxTextureSize;
             int maxHeight = maxTextureSize;
             
             List<TextureData> textureDatas = new List<TextureData>();
-            Dictionary<uint, GraphicDetail> graphicDataDic = new Dictionary<uint, GraphicDetail>();
 
             // 根据objectInfos的内,GraphicInfoData的Width,Height进行排序,优先排序Width,使图档从小到大排列
             graphicInfoDatas = graphicInfoDatas.OrderBy(obj => obj.Width).ThenBy(obj => obj.Height).ToList();
+            // 去重
+            graphicInfoDatas = graphicInfoDatas.Distinct().ToList();
 
             int offsetX = 0;    // X轴偏移量
             int offsetY = 0;    // Y轴偏移量
@@ -195,9 +239,9 @@ namespace CrossgateToolkit
                 BatchData batchData = new BatchData();
                 batchData.BatchOffsetX = offsetX;
                 batchData.BatchOffsetY = offsetY;
-                batchData.GraphicDetail = GetGraphicDetail(graphicInfoData, palet);
-
-                // graphicDatas.Add(graphicData);
+                batchData.GraphicDetail = GetGraphicDetail(graphicInfoData, palet, subPalet, linear, true);
+                batchData.GraphicInfoData = graphicInfoData;
+                batchData.PrimaryColor = batchData.GraphicDetail.PrimaryColor;
                 
                 textureData.BatchDatas.Add(batchData);
                 textureData.GraphicInfoDatas.Add(graphicInfoData);
@@ -212,31 +256,34 @@ namespace CrossgateToolkit
             //最后一次合并
             if (textureData.BatchDatas.Count > 0) textureDatas.Add(textureData);
             
+            Dictionary<uint, GraphicDetail> graphicDataDic = new Dictionary<uint, GraphicDetail>();
             //合并Texture2D
             for (var i = 0; i < textureDatas.Count; i++)
             {
                 TextureData textureDataPiece = textureDatas[i];
+                // 将最大高宽调整为4的倍数,提高渲染效率及适配压缩
+                textureDataPiece.MaxWidth = (int) Math.Ceiling(textureDataPiece.MaxWidth / 4f) * 4;
+                textureDataPiece.MaxHeight = (int) Math.Ceiling(textureDataPiece.MaxHeight / 4f) * 4;
+                
                 // Debug.Log($"合并第{i}个Texture2D,最大高度:{textureDataPiece.MaxHeight},图像数量:{textureDataPiece.GraphicDatas.Count}");
                 Color32[] colors = Enumerable.Repeat(new Color32(0,0,0,0), textureDataPiece.MaxWidth * textureDataPiece.MaxHeight).ToArray();
-                Texture2D texture2DPiece = new Texture2D(textureDataPiece.MaxWidth, textureDataPiece.MaxHeight, TextureFormat.RGBA4444, false, false);
-                texture2DPiece.filterMode = FilterMode.Point;
+                Texture2D texture2DPiece = new Texture2D(textureDataPiece.MaxWidth, textureDataPiece.MaxHeight, TextureFormat.RGBA4444, false, linear);
+                texture2DPiece.filterMode = linear ? FilterMode.Bilinear : FilterMode.Point;
+                texture2DPiece.wrapMode = TextureWrapMode.Clamp;
                 texture2DPiece.SetPixels32(colors);
+                texture2DPiece.Apply();
                 for (var n = 0; n < textureDataPiece.BatchDatas.Count; n++)
                 {
                     BatchData batchData = textureDataPiece.BatchDatas[n];
                     GraphicInfoData graphicInfoData = textureDataPiece.GraphicInfoDatas[n];
-
-                    if (batchData.GraphicDetail!=null)
-                    {
-                        Color32[] pixels = batchData.GraphicDetail.Sprite.texture.GetPixels32();
-                        texture2DPiece.SetPixels32(batchData.BatchOffsetX, batchData.BatchOffsetY, (int) graphicInfoData.Width, (int) graphicInfoData.Height,
-                            pixels.ToArray());
-                    }
+                    Graphics.CopyTexture(batchData.GraphicDetail.Sprite.texture, 0, 0, 0, 0, (int) graphicInfoData.Width,
+                        (int) graphicInfoData.Height, texture2DPiece, 0, 0, batchData.BatchOffsetX,
+                        batchData.BatchOffsetY);
                 }
-                texture2DPiece.Apply();
+                
                 Combine(texture2DPiece, textureDataPiece.BatchDatas);
             }
-
+            
             void Combine(Texture2D texture2D,List<BatchData> batchDatas)
             {
                 for (var i = 0; i < batchDatas.Count; i++)
@@ -244,58 +291,75 @@ namespace CrossgateToolkit
                     BatchData batchData = batchDatas[i];
                     //直接通过Texture2D做偏移,并转为Sprite的偏移量
                     Vector2 offset = new Vector2(0f, 1f);
-                    offset.x += -(batchData.GraphicDetail.OffsetX * 1f) / batchData.GraphicDetail.Width;
-                    offset.y -= (-batchData.GraphicDetail.OffsetY * 1f) / batchData.GraphicDetail.Height;
-
-                    Sprite sprite = Sprite.Create(texture2D, new Rect(batchData.BatchOffsetX, batchData.BatchOffsetY, (int)batchData.GraphicDetail.Width, (int)batchData.GraphicDetail.Height),offset, 1, 1, SpriteMeshType.FullRect);
+                    offset.x += -(batchData.GraphicInfoData.OffsetX * 1f) / batchData.GraphicInfoData.Width;
+                    offset.y -= (-batchData.GraphicInfoData.OffsetY * 1f) / batchData.GraphicInfoData.Height;
+                    
+                    Sprite sprite = Sprite.Create(texture2D,
+                        new Rect(batchData.BatchOffsetX, batchData.BatchOffsetY, (int)batchData.GraphicInfoData.Width,
+                            (int)batchData.GraphicInfoData.Height), offset, 1, 0, SpriteMeshType.FullRect);
+                    Sprite spritePPU100 = Sprite.Create(texture2D,
+                        new Rect(batchData.BatchOffsetX, batchData.BatchOffsetY, (int)batchData.GraphicInfoData.Width,
+                            (int)batchData.GraphicInfoData.Height), offset, 100, 0, SpriteMeshType.FullRect);
                     GraphicDetail graphicDetail = new GraphicDetail()
                     {
-                        Index = batchData.GraphicDetail.Index,
-                        Serial = batchData.GraphicDetail.Serial,
-                        Width = batchData.GraphicDetail.Width,
-                        Height = batchData.GraphicDetail.Height,
-                        OffsetX = batchData.GraphicDetail.OffsetX,
-                        OffsetY = batchData.GraphicDetail.OffsetY,
-                        Palet = batchData.GraphicDetail.Palet,
+                        Index = batchData.GraphicInfoData.Index,
+                        Serial = batchData.GraphicInfoData.Serial,
+                        Width = batchData.GraphicInfoData.Width,
+                        Height = batchData.GraphicInfoData.Height,
+                        OffsetX = batchData.GraphicInfoData.OffsetX,
+                        OffsetY = batchData.GraphicInfoData.OffsetY,
+                        Palet = palet,
                         Sprite = sprite,
-                        PrimaryColor = batchData.GraphicDetail.PrimaryColor
+                        SpritePPU100 = spritePPU100,
+                        PrimaryColor = batchData.PrimaryColor
                     };
-                    
                     // graphicDataPiece.Sprite = sprite;
-                    graphicDataDic.Add(graphicDetail.Serial, graphicDetail);
+                    if (AsSerialBatch) graphicDataDic[graphicDetail.Serial] = graphicDetail; 
+                    else graphicDataDic[graphicDetail.Index] = graphicDetail;
+                    ClearCache(graphicDetail.Serial);
+                    if(compress) texture2D.Compress(true);
                 }
-            }
 
+                
+            }
             return graphicDataDic;
         }
         #endregion
         
         //解压图像数据
-        private static List<Color32> UnpackGraphic(GraphicInfoData graphicInfoData,int PaletIndex=0,int SubPaletIndex=0){
+        public static List<Color32> UnpackGraphic(GraphicInfoData graphicInfoData, int PaletIndex = 0,
+            int SubPaletIndex = -1)
+        {
             List<Color32> pixels = new List<Color32>();
             //获取调色板
-            List<Color32> palet;
+            List<Color32> palet = null;
 
             //调整流指针
             BinaryReader fileReader = graphicInfoData.GraphicReader;
             fileReader.BaseStream.Position = graphicInfoData.Addr;
 
             //读入目标字节集
-            byte[] Content = fileReader.ReadBytes((int) graphicInfoData.Length);
+            byte[] Content = fileReader.ReadBytes((int)graphicInfoData.Length);
 
             //读取缓存字节集
             BinaryReader contentReader = new BinaryReader(new MemoryStream(Content));
 
             //16字节头信息
             byte[] RD = contentReader.ReadBytes(2);
+            // 研究了几个图档数据,这个字节分别有 0~3 不同类型
+            // 猜想一下的话
+            // 0:图档无压缩无内置图档
+            // 1:图档压缩无内置图档
+            // 2:图档无压缩有内置图档
+            // 3:图档压缩有内置图档
             int Version = contentReader.ReadByte();
             int Unknow = contentReader.ReadByte();
             uint Width = contentReader.ReadUInt32();
             uint Height = contentReader.ReadUInt32();
             uint DataLen = contentReader.ReadUInt32();
             uint innerPaletLen = 0;
-            
-            
+
+
             // 低版本头部长度为16,高版本为20
             int headLen = 16;
             if (Version > 1)
@@ -303,66 +367,84 @@ namespace CrossgateToolkit
                 headLen = 20;
                 innerPaletLen = contentReader.ReadUInt32();
             }
-            
+
             //数据长度
             int contentLen = (int)(DataLen - headLen);
-            int pixelLen = (int) (graphicInfoData.Width * graphicInfoData.Height);
-            
-            int[] paletIndex;
-            if (graphicInfoData.UnpackedPaletIndex == null)
+            int pixelLen = (int)(graphicInfoData.Width * graphicInfoData.Height);
+
+            byte[] paletIndexs = graphicInfoData.UnpackedPaletIndex;
+            if (paletIndexs == null)
             {
                 //解压数据
-                byte[] contentBytes = contentReader.ReadBytes((int) contentLen);
-                NativeArray<byte> bytes = new NativeArray<byte>((int) contentBytes.Length, Allocator.TempJob);
+                byte[] contentBytes =
+                    contentReader.ReadBytes((int)Version % 2 == 0 ? (int)(pixelLen + innerPaletLen) : contentLen);
+                NativeArray<byte> bytes = new NativeArray<byte>((int)contentBytes.Length, Allocator.TempJob);
                 bytes.CopyFrom(contentBytes);
                 long decompressLen = pixelLen + innerPaletLen;
-                
-                NativeArray<int> colorIndexs =
-                    new NativeArray<int>((int)decompressLen, Allocator.TempJob);
+
+                NativeArray<byte> colorIndexs =
+                    new NativeArray<byte>((int)decompressLen, Allocator.TempJob);
 
                 DecompressJob decompressJob = new DecompressJob()
                 {
                     bytes = bytes,
-                    compressd = Version != 0,
+                    compressd = Version % 2 != 0,
                     colorIndexs = colorIndexs
                 };
                 // decompressJob.Execute();
                 decompressJob.Schedule().Complete();
+                paletIndexs = colorIndexs.ToArray();
+                graphicInfoData.UnpackedPaletIndex = paletIndexs;
+                
+                // 如果存在内置调色板,则读取内置调色板
+                if (innerPaletLen > 0 && graphicInfoData.InnerPalet == null)
+                {
+                    byte[] innerPaletIndex = paletIndexs.Skip(pixelLen).Take((int)innerPaletLen).ToArray();
+                    graphicInfoData.InnerPalet = AnalysisInnerPalet(innerPaletIndex).ToList();
+                    
+                }
+                
                 bytes.Dispose();
-                paletIndex = colorIndexs.ToArray();
-                graphicInfoData.UnpackedPaletIndex = paletIndex;
                 colorIndexs.Dispose();
             }
-            else
-            {
-                paletIndex = graphicInfoData.UnpackedPaletIndex;
-            }
+            
+            paletIndexs = paletIndexs.Take(pixelLen).ToArray();
 
-            if (SubPaletIndex > 0)
+            // palet = Palet.GetPalet(PaletIndex);
+            // Debug.Log($"PaletIndex:{PaletIndex},SubPaletIndex:{SubPaletIndex},palet:{palet!=null},InnerPalet:{graphicInfoData.InnerPalet!=null}");
+            // 如果指定了外置调色板
+            if (SubPaletIndex >= 0)
             {
                 palet = Palet.GetPalet(SubPaletIndex);
                 if (palet == null)
                 {
                     GraphicInfoData subPaletInfoData = GraphicInfo.GetGraphicInfoData((uint)SubPaletIndex);
-                    Graphic.GetGraphicDetail((uint)SubPaletIndex);
-                    palet = subPaletInfoData.InnerPalet;
-                    Palet.AddPalet(SubPaletIndex, palet);
+                    if (subPaletInfoData != null)
+                    {
+                        Graphic.GetGraphicDetail((uint)SubPaletIndex);
+                        if (subPaletInfoData.InnerPalet != null)
+                        {
+                            palet = subPaletInfoData.InnerPalet;
+                            Palet.AddPalet(SubPaletIndex, palet);
+                        }
+                    }
                 }
             }
-            else
+            
+            if (palet == null)
             {
-                if (innerPaletLen > 0)
+                if (graphicInfoData.InnerPalet != null)
                 {
-                    int[] innerPaletIndex = paletIndex.Skip(pixelLen).Take((int) innerPaletLen).ToArray();
-                    palet = AnalysisInnerPalet(innerPaletIndex).ToList();
-                    paletIndex = paletIndex.Take(pixelLen).ToArray();
-                    graphicInfoData.InnerPalet = palet;
+                    // 没有指定外置调色板,存在内置调色板,则读取内置调色板
+                    palet = graphicInfoData.InnerPalet;
                 }
                 else
                 {
                     palet = Palet.GetPalet(PaletIndex);
+                    if (palet == null) palet = Palet.GetPalet(0);
                 }
             }
+            
             //释放连接
             contentReader.Dispose();
             contentReader.Close();
@@ -371,16 +453,16 @@ namespace CrossgateToolkit
             int r = 0;
             int g = 0;
             int b = 0;
-            foreach (int index in paletIndex)
+            foreach (int index in paletIndexs)
             {
                 Color32 color32;
-                if (index == 999 || (index > palet.Count - 1))
+                if (index == 0 || (index > palet.Count - 1))
                 {
                     color32 = Color.clear;
                 }
                 else
                 {
-                    color32 = palet[index];   
+                    color32 = palet[index];
                 }
                 pixels.Add(color32);
                 r += color32.r;
@@ -418,18 +500,18 @@ namespace CrossgateToolkit
         }
 
         //分析高版本内部调色板
-        private static Color32[] AnalysisInnerPalet(int[] bytes)
+        private static Color32[] AnalysisInnerPalet(byte[] bytes)
         {
             int colorLen = bytes.Length / 3;
             Color32[] palet = new Color32[colorLen + 1];
             for (var i = 0; i < colorLen; i++)
             {
-                int[] paletBytes = bytes.Skip(i * 3).Take(3).ToArray();
+                byte[] paletBytes = bytes.Skip(i * 3).Take(3).ToArray();
                 Color32 color32 = new Color32();
                 color32.r = (byte)paletBytes[2];
                 color32.g = (byte)paletBytes[1];
                 color32.b = (byte)paletBytes[0];
-                color32.a = 0xFF;
+                color32.a = (byte)(i == 0 ? 0x00 : 0xFF);
                 palet[i] = color32;
             }
             palet[colorLen] = Color.clear;
@@ -517,7 +599,7 @@ namespace CrossgateToolkit
                     repeat = head % 0xc0;
                     for (var i = 0; i < repeat; i++)
                     {
-                        colorIndexs.Add(999);
+                        colorIndexs.Add(0);
                     }
 
                 }
@@ -526,7 +608,7 @@ namespace CrossgateToolkit
                     repeat = head % 0xd0 * 0x100 + next();
                     for (var i = 0; i < repeat; i++)
                     {
-                        colorIndexs.Add(999);
+                        colorIndexs.Add(0);
                     }
 
                 }
@@ -535,7 +617,7 @@ namespace CrossgateToolkit
                     repeat = head % 0xe0 * 0x10000 + next() * 0x100 + next();
                     for (var i = 0; i < repeat; i++)
                     {
-                        colorIndexs.Add(999);
+                        colorIndexs.Add(0);
                     }
                 }
             }
@@ -556,7 +638,7 @@ namespace CrossgateToolkit
         [ReadOnly]
         public NativeArray<byte> bytes;
         public bool compressd;
-        public NativeArray<int> colorIndexs;
+        public NativeArray<byte> colorIndexs;
     
         private int _maxIndex;
         private int _index;
@@ -568,7 +650,7 @@ namespace CrossgateToolkit
             if (_index > _maxIndex) return -1;
             return bytes[_index];
         }
-        private void AddColorIndex(int index)
+        private void AddColorIndex(byte index)
         {
             if (_colorIndex > colorIndexs.Length - 1) return;
             colorIndexs[_colorIndex] = index;
@@ -587,7 +669,7 @@ namespace CrossgateToolkit
                 {
                     int pindex = NextByte();
                     if(pindex==-1) break;
-                    AddColorIndex(pindex);
+                    AddColorIndex((byte)pindex);
                 }
             }
             else
@@ -606,7 +688,7 @@ namespace CrossgateToolkit
                         repeat = head;
                         for (var i = 0; i < repeat; i++)
                         {
-                            AddColorIndex(NextByte());
+                            AddColorIndex((byte)NextByte());
                         }
     
                     }
@@ -615,7 +697,7 @@ namespace CrossgateToolkit
                         repeat = head % 0x10 * 0x100 + NextByte();
                         for (var i = 0; i < repeat; i++)
                         {
-                            AddColorIndex(NextByte());
+                            AddColorIndex((byte)NextByte());
                         }
     
                     }
@@ -624,14 +706,14 @@ namespace CrossgateToolkit
                         repeat = head % 0x20 * 0x10000 + NextByte() * 0x100 + NextByte();
                         for (var i = 0; i < repeat; i++)
                         {
-                            AddColorIndex(NextByte());
+                            AddColorIndex((byte)NextByte());
                         }
     
                     }
                     else if (head < 0x90)
                     {
                         repeat = head % 0x80;
-                        int index = NextByte();
+                        byte index = (byte)NextByte();
                         for (var i = 0; i < repeat; i++)
                         {
                             AddColorIndex(index);
@@ -640,7 +722,7 @@ namespace CrossgateToolkit
                     }
                     else if (head < 0xa0)
                     {
-                        int index = NextByte();
+                        byte index = (byte)NextByte();
                         repeat = head % 0x90 * 0x100 + NextByte();
                         for (var i = 0; i < repeat; i++)
                         {
@@ -650,7 +732,7 @@ namespace CrossgateToolkit
                     }
                     else if (head < 0xc0)
                     {
-                        int index = NextByte();
+                        byte index = (byte)NextByte();
                         repeat = head % 0xa0 * 0x10000 + NextByte() * 0x100 + NextByte();
                         for (var i = 0; i < repeat; i++)
                         {
@@ -663,7 +745,7 @@ namespace CrossgateToolkit
                         repeat = head % 0xc0;
                         for (var i = 0; i < repeat; i++)
                         {
-                            AddColorIndex(999);
+                            AddColorIndex(0);
                         }
     
                     }
@@ -672,7 +754,7 @@ namespace CrossgateToolkit
                         repeat = head % 0xd0 * 0x100 + NextByte();
                         for (var i = 0; i < repeat; i++)
                         {
-                            AddColorIndex(999);
+                            AddColorIndex(0);
                         }
     
                     }
@@ -681,7 +763,7 @@ namespace CrossgateToolkit
                         repeat = head % 0xe0 * 0x10000 + NextByte() * 0x100 + NextByte();
                         for (var i = 0; i < repeat; i++)
                         {
-                            AddColorIndex(999);
+                            AddColorIndex(0);
                         }
                     }
                 }

+ 14 - 2
CrossgateToolkit/GraphicInfo.cs

@@ -52,7 +52,7 @@ namespace CrossgateToolkit
         public BinaryReader GraphicReader;
         public byte VERSION_FLAG;
         //已解压的调色板索引
-        public int[] UnpackedPaletIndex;
+        public byte[] UnpackedPaletIndex;
         public List<Color32> InnerPalet;
     }
 
@@ -88,11 +88,23 @@ namespace CrossgateToolkit
                 graphicInfoData.Height = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
                 graphicInfoData.East = fileReader.ReadByte();
                 graphicInfoData.South = fileReader.ReadByte();
-                graphicInfoData.Blocked =  fileReader.ReadByte() == 0;
+                byte Blocked = fileReader.ReadByte();
+                graphicInfoData.Blocked =  Blocked % 2 == 0;
                 graphicInfoData.AsGround = fileReader.ReadByte() == 1;
                 graphicInfoData.Unknow = fileReader.ReadBytes(4);
                 graphicInfoData.Serial = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
                 graphicInfoData.GraphicReader = graphicFileReader;
+                // if (graphicInfoData.Serial == 220759)
+                // {
+                //     Debug.LogError(graphicInfoData.Serial + "穿越" + Blocked);
+                //     Debug.LogError(graphicInfoData.Serial + "穿越" + graphicInfoData.Blocked);
+                //     Debug.LogError(graphicInfoData.Serial + "穿越" + graphicInfoData.Width);
+                //     Debug.LogError(graphicInfoData.Serial + "穿越" + graphicInfoData.Height);
+                // } 
+                // if (graphicInfoData.Serial >= 220000 && graphicInfoData.Width == 64 && graphicInfoData.Height == 47 && graphicInfoData.Blocked)
+                // {
+                //     Debug.LogError(graphicInfoData.Serial + "穿越" + Blocked);
+                // }
                 
                 //建立Index映射表
                 _indexCache[Version][graphicInfoData.Index] = graphicInfoData;

+ 56 - 6
CrossgateToolkit/Map.cs

@@ -14,6 +14,7 @@ using System.IO;
 using System.Linq;
 using System.Text.RegularExpressions;
 using UnityEngine;
+using Object = System.Object;
 
 namespace CrossgateToolkit
 {
@@ -67,6 +68,9 @@ namespace CrossgateToolkit
         //缓存数据
         private static Dictionary<uint, MapInfo> _cache = new Dictionary<uint, MapInfo>();
         private static Dictionary<uint, MapFileInfo> _mapIndexFiles = new Dictionary<uint, MapFileInfo>(); 
+        
+        private static Dictionary<uint,Dictionary<uint,GraphicDetail>> _mapGroundGraphicBatch = new Dictionary<uint, Dictionary<uint, GraphicDetail>>();
+        private static Dictionary<uint,Dictionary<uint,GraphicDetail>> _mapObjectGraphicBatch = new Dictionary<uint, Dictionary<uint, GraphicDetail>>();
 
         //初始化地图文件列表
         public static void Init()
@@ -112,20 +116,66 @@ namespace CrossgateToolkit
         }
         
         // 地面数据合批
-        public static Dictionary<uint,GraphicDetail> BakeGrounds(List<GraphicInfoData> graphicInfoDatas,int palet = 0)
+        public static Dictionary<uint,GraphicDetail> BakeGrounds(uint mapID,List<GraphicInfoData> graphicInfoDatas,int palet = 0,int subPalet = 0,bool linear = false)
         {
-            Dictionary<uint, GraphicDetail>
-                graphicDataDict = GraphicData.BakeGraphics(graphicInfoDatas, palet, 2048, 0);
+            _mapGroundGraphicBatch.TryGetValue(mapID, out var graphicDataDict);
+            if (graphicDataDict == null)
+            {
+                graphicDataDict = GraphicData.BakeGraphics(graphicInfoDatas, true, palet, subPalet, linear, 2048, 0);
+                _mapGroundGraphicBatch[mapID] = graphicDataDict;
+            }
             return graphicDataDict;
         }
         
         // 物件数据合批
-        public static Dictionary<uint,GraphicDetail> BakeObjects(List<GraphicInfoData> graphicInfoDatas,int palet = 0)
+        public static Dictionary<uint,GraphicDetail> BakeObjects(uint mapID,List<GraphicInfoData> graphicInfoDatas,int palet = 0,int subPalet = 0,bool linear = false)
         {
-            Dictionary<uint, GraphicDetail>
-                graphicDataDict = GraphicData.BakeGraphics(graphicInfoDatas, palet, 4096);
+            _mapObjectGraphicBatch.TryGetValue(mapID, out var graphicDataDict);
+            if (graphicDataDict == null)
+            {
+                graphicDataDict = GraphicData.BakeGraphics(graphicInfoDatas, true, palet, subPalet, linear, 4096, 0);
+                _mapObjectGraphicBatch[mapID] = graphicDataDict;
+            }
             return graphicDataDict;
         }
+        
+        public static void ClearMapBatch(uint mapID)
+        {
+            Dictionary<uint,GraphicDetail> graphicDataDict;
+            List<GraphicDetail> graphicDetails;
+            List<Texture> textures = new List<Texture>();
+            _mapGroundGraphicBatch.TryGetValue(mapID, out graphicDataDict);
+            if (graphicDataDict != null)
+            {
+                graphicDetails = graphicDataDict.Values.ToList();
+                foreach (var graphicDetail in graphicDetails)
+                {
+                    Texture texture = graphicDetail.Sprite.texture;
+                    if (!textures.Contains(texture)) textures.Add(graphicDetail.Sprite.texture);
+                    graphicDetail.SpritePPU100 = null;
+                    graphicDetail.Sprite = null;
+                }
+            }
+            _mapObjectGraphicBatch.TryGetValue(mapID, out graphicDataDict);
+            if (graphicDataDict != null)
+            {
+                graphicDetails = graphicDataDict.Values.ToList();
+                foreach (var graphicDetail in graphicDetails)
+                {
+                    Texture texture = graphicDetail.Sprite.texture;
+                    if (!textures.Contains(texture)) textures.Add(graphicDetail.Sprite.texture);
+                    graphicDetail.SpritePPU100 = null;
+                    graphicDetail.Sprite = null;
+                }
+            }
+            foreach (var texture in textures)
+            {
+                Resources.UnloadAsset(texture);
+            }
+            
+            _mapGroundGraphicBatch.Remove(mapID);
+            _mapObjectGraphicBatch.Remove(mapID);
+        }
 
         //加载地图数据
         private static MapInfo _loadMap(uint serial)

+ 3 - 1
CrossgateToolkit/Palet.cs

@@ -88,9 +88,11 @@ namespace CrossgateToolkit
                 color32.r = bytes[0];
                 color32.g = bytes[1];
                 color32.b = bytes[2];
-                color32.a = 0xFF;
+                color32.a = (byte)(i == 0 ? 0x00 : 0xFF);
                 PaletColors.Add(color32);
             }
+
+            
             
             
             for (var i = 0; i < 224; i++)

+ 68 - 4
README.md

@@ -37,6 +37,7 @@
 > * `AnimePlayer` [动画播放器挂载组件](#动画播放) 
 >   * `AnimePlayer` 动画关键帧(攻击/攻击完成)事件回调
 >   * `AnimePlayer` 音频帧事件回调
+> * `DynamicGraphic` [动态图集](#动态图集)
 
 <div style="display: flex;flex-direction: column;align-items: center;">
 <div><img style="" src="Preview/AnimeSupport.gif"><img style="" src="Preview/AnimeHSupport.gif"></div>
@@ -45,8 +46,6 @@
 
 </div>
 
-
-
 ## 3、使用说明
 
 ### 基本环境
@@ -153,10 +152,14 @@ Map.MapInfo mapInfo = Map.GetMap(uint Serial);
 * 合批图档
 * 通过指定图档序列,对图档进行合批处理,并返回合批后的图档数据
 * @param graphicInfoDatas 图档索引数据序列
+* @param AsSerialBatch 以图档编号而非索引号返回合批数据,默认为true,反之则以图档索引号返回数据
 * @param palet 调色板序号
-* @param maxTextureSize 单个Texture最大尺寸,地面数据建议2048,物件数据建议4096
+* @param subPalet 副调色板序号,此项针对动画数据
+* @param linear 以线性过滤方式生成图集
+* @param maxTextureSize 单个Texture最大尺寸,地面数据建议2048,物件数据建议低于4096
 * @param padding 图档间隔,可以有效避免图档渲染时出现多余的黑边或像素黏连
-* @return Dictionary 合批后的图档数据,Key(unit)为图档数据编号,Value为图档数据
+* @param compress 启用压缩
+* @return Dictionary 合批后的图档数据,Key(unit)为图档数据编号(或AsSerialBatch为false时为图档序号),Value为图档数据
 */
 GraphicData.BakeGraphics(
     List<GraphicInfoData> graphicInfoDatas,
@@ -310,12 +313,73 @@ player.Stop();
 
 ```
 
+### 动态图集
+比```图档合批```更加灵活的动态图集功能,在运行时进行图档图集的写入处理操作
+
+主要针对网游地图的动态获取增加的可扩展动态图集,包含图集建立、自维护和清理
+
+每个动态图集对象内部会根据图档情况进行动态扩充,并采用相对高效的可用空间搜索方式进行图集填充,尽量减少空间浪费
+
+动态图集固定采用 ```图集缓存 + 二级缓存模式```,并采用Unity的Graphic异步复制纹理方式写入图集,最大限度降低图集使用过程中CPU的计算量
+
+```csharp
+/// <summary>
+/// 创建动态图集
+/// </summary>
+/// <param name="GraphicWidth">图集最大宽度</param>
+/// <param name="GraphicHeight">图集最大高度</param>
+/// <param name="GraphicPadding">图集各图档间隔</param>
+/// <param name="palet">调色板编号</param>
+/// <param name="linear">线性过滤</param>
+/// <param name="ppu100">以100的PPU方式生成Sprite对象</param>
+/// <param name="compressWhenFull">当图集对象无可用空间时,对Texture进行压缩</param>
+
+DynamicGraphic(
+    int GraphicWidth,
+    int GraphicHeight,
+    int GraphicPadding = 0,
+    int palet = 0,
+    bool linear = false,
+    bool ppu100 = false,
+    bool compressWhenFull = false)
+
+```
+
+```csharp
+// 创建一个1024*1024尺寸,间距 0 ,调色板编号 0,非线性过滤,输出PPU为100的的动态图集,且进行压缩
+DynamicGraphic dynamicGraphic = new DynamicGraphic(1024, 1024, 0, 0, false, true, true);
+
+// 通过动态图集获取图像
+Sprite sprite = dynamicGraphic.GetSprite(uint Serial);
+
+// 通过协程方式获取图像
+StartCoroutine(dynamicGraphic.GetSpriteSync(uint Serial,(sprite)=>{
+    // 使用回调Sprite
+}));
+
+// 图集清理
+dynamicGraphic.Clear(bool GC = false);
+```
+
 ### 其他
 请根据情况自行探索修改代码适应应用场景
 
 
 
 ## 4、更新日志
+### v 3.0
+> `ADD` 本次大更新主要是性能优化及功能性更新,部分方法调用参数发生变化
+>
+> `ADD` 图档Sprite获取以及动画使用增加线性过滤参数
+>
+> `ADD` 动画使用增加PPU100、线性过滤、图集压缩等参数选项
+>
+> `ADD` 图档获取Sprite时可通过GraphicDetail.SpritePPU100获取PPU为100的Sprite对象
+>
+> `ADD` 增加动态图集 `DynamicGraphic` 类,具体使用方法请参考README示例或查阅代码
+>
+> `UPD` 调整部分代码和图档获取,性能增强,降低CPU压力,减少内存占用等
+
 ### v 2.6
 > `UPD` 修改了Anime初始化过程,将预载全部动画帧修改为读取时加载,减少初始化时间
 >