HonorLee 11 months ago
commit
2f1484be3c
10 changed files with 1635 additions and 0 deletions
  1. 247 0
      CGTool/Anime.cs
  2. 313 0
      CGTool/AnimePlayer.cs
  3. 53 0
      CGTool/CGTool.cs
  4. 358 0
      CGTool/Graphic.cs
  5. 167 0
      CGTool/GraphicInfo.cs
  6. 14 0
      CGTool/LICENSE
  7. 245 0
      CGTool/Map.cs
  8. 111 0
      CGTool/Palet.cs
  9. 14 0
      LICENSE
  10. 113 0
      README.md

+ 247 - 0
CGTool/Anime.cs

@@ -0,0 +1,247 @@
+/**
+ * 魔力宝贝图档解析脚本 - CGTool
+ * 
+ * @Author  HonorLee (dev@honorlee.me)
+ * @Version 1.0 (2023-04-15)
+ * @License GPL-3.0
+ *
+ * Anime.cs 动画基础类
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+
+namespace CGTool
+{
+    //动画信息
+    public class AnimeInfo
+    {
+        public int Version;
+        //4 bytes   动画索引
+        public uint Index;
+        //4 bytes   动画文件地址
+        public uint Addr;
+        //2 bytes   动作数量
+        public int ActionCount;
+        //2 bytes   未知字节
+        public byte[] Unknow;
+        //动画数据  Direction -> ActionType -> AnimeData
+        public Dictionary<int, Dictionary<int, AnimeDetail>> AnimeDatas = new Dictionary<int, Dictionary<int, AnimeDetail>>();
+    }
+
+    //动画帧数据
+    public class AnimeFrameInfo
+    {
+        //图档编号
+        public uint GraphicIndex;
+        //偏移X
+        public int OffsetX;
+        //偏移Y
+        public int OffsetY;
+        //音效编号
+        public int AudioIndex;
+        //动效编号
+        public Anime.EffectType Effect;
+    }
+
+    //动画数据
+    public class AnimeDetail
+    {
+        public uint Index;
+        public int Version;
+        public int Direction;
+        public int ActionType;
+        public uint CycleTime;
+        public uint FrameCount;
+        public AnimeFrameInfo[] AnimeFrameInfos;
+        
+        // public byte[] unknown;
+    }
+    //动画相关Enum类型
+    public class Anime : MonoBehaviour
+    {
+        //方向
+        public enum DirectionType
+        {
+            NULL=-1,
+            North=0,
+            NorthEast=1,
+            East=2,
+            SouthEast=3,
+            South=4,
+            SouthWest=5,
+            West=6,
+            NorthWest=7
+        }
+        //动作(未补全)
+        public enum ActionType
+        {
+            Stand=0,
+            Walk=1,
+            BeforeRun=2,
+            Run=3,
+            AfterRun=4,
+            Attack=5,
+        }
+        //动效
+        public enum EffectType
+        {
+            Hit=1,
+            HitOver=2
+        }
+        //动画列表缓存    Index -> AnimeInfo
+        private static Dictionary<uint, AnimeInfo> _animeInfoCache = new Dictionary<uint, AnimeInfo>();
+
+        //动画序列缓存    Direction -> Action -> AnimeData
+        // private static Dictionary<uint, Dictionary<int, AnimeData>> _animeCache =
+        //     new Dictionary<uint, Dictionary<int, AnimeData>>();
+        
+        private static List<string> _animeInfoFiles = new List<string>()
+        {
+            //龙之沙漏 之前版本前Info数据
+            "AnimeInfo_4.bin",
+            //龙之沙漏 版本Info数据
+            "AnimeInfoEx_1.Bin"
+        };
+
+        private static List<string> _animeDataFiles = new List<string>()
+        {
+            "Anime_4.bin", "AnimeEx_1.Bin"
+        };
+
+        //获取动画数据信息
+        public static AnimeInfo GetAnimeInfo(uint Index)
+        {
+            //返回缓存
+            if (_animeInfoCache.ContainsKey(Index)) return _animeInfoCache[Index];
+            //动画编号大于105000的属于 龙之沙漏 版本
+            int Version = 0;
+            if (Index >= 105000) Version = 1;
+            Dictionary<uint, AnimeInfo> animeInfos = _loadAnimeInfo(Version);
+            if (animeInfos.ContainsKey(Index)) return animeInfos[Index];
+            return null;
+        }
+
+        //获取动画数据
+        public static AnimeDetail GetAnimeDetail(uint serial,DirectionType Direction,ActionType Action)
+        {
+            AnimeInfo animeInfo = GetAnimeInfo(serial);
+            if (animeInfo == null) return null;
+            if (animeInfo.AnimeDatas.ContainsKey((int)Direction))
+            {
+                if (animeInfo.AnimeDatas[(int)Direction].ContainsKey((int) Action))
+                    return animeInfo.AnimeDatas[(int)Direction][(int) Action];
+            }
+
+            return null;
+        }
+        
+        //加载动画数据
+        private static Dictionary<uint, AnimeInfo> _loadAnimeInfo(int Version)
+        {
+            //查找Info文件
+            string infoFileName = _animeInfoFiles[Version];
+            string dataFileName = _animeDataFiles[Version];
+            FileInfo infoFile = new FileInfo(CGTool.BaseFolder + "/" + infoFileName);
+            FileInfo dataFile = new FileInfo(CGTool.BaseFolder + "/" + dataFileName);
+            if (!infoFile.Exists || !dataFile.Exists) return null;
+
+            //创建流读取器
+            FileStream infoFileStream = infoFile.OpenRead();
+            FileStream dataFileStream = dataFile.OpenRead();
+            BinaryReader infoFileReader = new BinaryReader(infoFileStream);
+            BinaryReader dataFileReader = new BinaryReader(dataFileStream);
+
+            // Dictionary<uint, AnimeInfo> animeInfos = new Dictionary<uint, AnimeInfo>();
+            long DataLength = infoFileStream.Length / 12;
+            for (int i = 0; i < DataLength; i++)
+            {
+                //初始化对象
+                AnimeInfo animeInfo = new AnimeInfo();
+                animeInfo.Version = Version;
+                animeInfo.Index = BitConverter.ToUInt32(infoFileReader.ReadBytes(4),0);
+                animeInfo.Addr = BitConverter.ToUInt32(infoFileReader.ReadBytes(4),0);
+                animeInfo.ActionCount = infoFileReader.ReadUInt16();
+                animeInfo.Unknow = infoFileReader.ReadBytes(2);
+                // print(JsonUtility.ToJson(animeInfo));
+                dataFileStream.Position = animeInfo.Addr;
+                for (int j = 0; j < animeInfo.ActionCount; j++)
+                {
+                    AnimeDetail animeData = new AnimeDetail();
+                    animeData.Index = animeInfo.Index;
+                    animeData.Version = Version;
+                    animeData.Direction = dataFileReader.ReadUInt16();
+                    animeData.ActionType = dataFileReader.ReadUInt16();
+                    animeData.CycleTime = BitConverter.ToUInt32(dataFileReader.ReadBytes(4),0);
+                    animeData.FrameCount = BitConverter.ToUInt32(dataFileReader.ReadBytes(4),0);
+                    animeData.AnimeFrameInfos = new AnimeFrameInfo[animeData.FrameCount];
+                    // if (animeInfo.Index == 101201) Debug.Log("----------------------------------");
+                    for (int k = 0; k < animeData.FrameCount; k++)
+                    {
+                        animeData.AnimeFrameInfos[k] = new AnimeFrameInfo();
+                        //GraphicIndex序号
+                        animeData.AnimeFrameInfos[k].GraphicIndex = BitConverter.ToUInt32(dataFileReader.ReadBytes(4),0);
+                        //未知字节
+                        // animeData.unknown = dataFileReader.ReadBytes(6);
+                        // if (animeInfo.Index == 101201)
+                        // {
+                        //     byte[] tt = dataFileReader.ReadBytes(6);
+                        //     
+                        //     Debug.Log(tt[0]+" "+tt[1]+" "+tt[2]+" "+tt[3]+" "+tt[4]+" "+tt[5]);
+                        // }
+                        // else
+                        // {
+                        //     dataFileReader.ReadBytes(6);
+                        // }
+                        animeData.AnimeFrameInfos[k].OffsetX = BitConverter.ToInt16(dataFileReader.ReadBytes(2),0);
+                        animeData.AnimeFrameInfos[k].OffsetY = BitConverter.ToInt16(dataFileReader.ReadBytes(2),0);
+                        animeData.AnimeFrameInfos[k].AudioIndex = dataFileReader.ReadByte();
+                        int effect = dataFileReader.ReadByte();
+                        if (effect == 0x27 || effect == 0x28)
+                        {
+                            animeData.AnimeFrameInfos[k].Effect = EffectType.Hit;
+                        }
+                        else if(effect == 0x4E || effect == 0x4F)
+                        {
+                            animeData.AnimeFrameInfos[k].Effect = EffectType.HitOver;
+                        }
+                        // animeData.AnimeFrameInfos[k].Effect = dataFileReader.ReadByte();
+                    }
+                    
+                    if (!animeInfo.AnimeDatas.ContainsKey(animeData.Direction))
+                        animeInfo.AnimeDatas.Add(animeData.Direction, new Dictionary<int, AnimeDetail>());
+
+                    if (animeInfo.AnimeDatas[animeData.Direction].ContainsKey(animeData.ActionType))
+                    {
+                        animeInfo.AnimeDatas[animeData.Direction][animeData.ActionType] = animeData;
+                    }
+                    else
+                    {
+                        animeInfo.AnimeDatas[animeData.Direction].Add(animeData.ActionType, animeData);
+                    }
+
+                    if (_animeInfoCache.ContainsKey(animeInfo.Index))
+                    {
+                        _animeInfoCache[animeInfo.Index] = animeInfo;
+                    }
+                    else
+                    {
+                        _animeInfoCache.Add(animeInfo.Index, animeInfo);
+                    }
+                }
+
+            }
+
+            infoFileReader.Dispose();
+            infoFileReader.Close();
+            dataFileReader.Dispose();
+            dataFileReader.Close();
+            infoFileStream.Close();
+            dataFileStream.Close();
+
+            return _animeInfoCache;
+        }
+    }
+}

+ 313 - 0
CGTool/AnimePlayer.cs

@@ -0,0 +1,313 @@
+/**
+ * 魔力宝贝图档解析脚本 - CGTool
+ * 
+ * @Author  HonorLee (dev@honorlee.me)
+ * @Version 1.0 (2023-04-15)
+ * @License GPL-3.0
+ *
+ * AnimePlayer.cs 动画播放器-挂载类
+ */
+
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace CGTool
+{
+    //动画周期回调
+    public delegate void AnimeCallback();
+    
+    //动画动作帧监听
+    public delegate void AnimeEffectListener(Anime.EffectType effect);
+    
+    //动画音频帧监听
+    public delegate void AnimeAudioListener(int audioIndex);
+    
+    //鼠标移入事件监听
+    public delegate void MouseListener(AnimePlayer animePlayer);
+    
+    /**
+     * 动画播放器,用于播放CG动画,支持多动画队列播放
+     * 脚本需绑定至挂载了SpriteRenderer和RectTransform的对象上
+     * 除此之外,还需绑定BoxCollider2D(可选),用于监听鼠标的移入移出事件
+     *
+     * 当动画播放完成后会自动调用onFinishCallback回调函数
+     * 另外可指定onActionListener和onAudioListener监听动画动作帧和音频帧
+     * 目前已知的动作帧有:
+     * 击中 0x27 | 0x28
+     * 伤害结算 0x4E | 0x4F
+     */
+    public class AnimePlayer : MonoBehaviour
+    {
+        //动画帧数据
+        private class AnimeFrame
+        {
+            public int Index;
+            public GraphicInfoData GraphicInfo;
+            public Sprite Sprite;
+            public AnimeFrameInfo AnimeFrameInfo;
+        }
+
+        //播放配置数据
+        private class AnimeOption
+        {
+            public uint AnimeSerial;
+            public Anime.DirectionType Direction;
+            public Anime.ActionType ActionType;
+            public bool Infinity;
+            public float Speed;
+            public float FrameRate;
+            public AnimeDetail AnimeDetail;
+            public AnimeCallback onFinishCallback;
+        }
+        
+        //当前播放
+        private AnimeOption _currentAnime;
+        private AnimeFrame[] _frames;
+        private int _currentFrame;
+        
+        //是否播放
+        private bool isPlayable;
+        
+        //待播放队列
+        private Queue<AnimeOption> _animeQueue = new Queue<AnimeOption>();
+        
+        //计时器
+        private float _timer;
+        
+        //绑定SpriteRenderer
+        private SpriteRenderer _spriteRenderer;
+        //绑定RectTransform
+        private RectTransform _rectTransform;
+        //绑定BoxCollider2D(可选)
+        private BoxCollider2D _boxCollider2D;
+        
+        //动画动作帧监听
+        public AnimeEffectListener onEffectListener;
+        public AnimeAudioListener onAudioListener;
+        //鼠标移入事件监听
+        public MouseListener onMouseEnterListener;
+        //鼠标移出事件监听
+        public MouseListener onMouseExitListener;
+
+        //获取偏移量(无用)
+        public Vector2 offset
+        {
+            get
+            {
+                float offsetX = -_frames[_currentFrame].GraphicInfo.OffsetX;
+                float offsetY = _frames[_currentFrame].GraphicInfo.OffsetY;
+                return new Vector2(offsetX, offsetY);
+            }
+        }
+
+        //实例初始化时获取相关绑定
+        private void Awake()
+        {
+            _spriteRenderer = GetComponentInParent<SpriteRenderer>();
+            _rectTransform = GetComponentInParent<RectTransform>();
+            //碰撞盒,仅当需要添加鼠标事件时使用
+            _boxCollider2D = GetComponent<BoxCollider2D>();
+        }
+
+        //鼠标移入监听
+        private void OnMouseEnter()
+        {
+            if(onMouseEnterListener!=null) onMouseEnterListener(this);
+        }
+
+        //鼠标移出监听
+        private void OnMouseExit()
+        {
+            if(onMouseExitListener!=null) onMouseExitListener(this);
+        }
+
+        /**
+         * 播放动画,调用此方法将会清空当前播放队列,调用完成可通过链式调用nextPlay方法添加动画到播放队列
+         * @param Serial 动画序列号
+         * @param Direction 动画方向
+         * @param ActionType 动画动作
+         * @param Infinity 是否循环
+         * @param Speed 播放速度,以 1s 为基准,根据动画帧率计算实际播放周期时长
+         * @param onFinishCallback 动画结束回调
+         * @return AnimePlayer
+         */
+        public AnimePlayer play(uint Serial,Anime.DirectionType Direction,Anime.ActionType ActionType,bool Infinity = false,float Speed=1f,AnimeCallback onFinishCallback=null)
+        {
+            if (_spriteRenderer == null)
+            {
+                Debug.Log("AnimePlayer:SpriteRenderer is null");
+                return this;
+            }
+            AnimeOption animeOption = CreateAnimeOption(Serial, Direction, ActionType, Infinity, Speed, onFinishCallback);
+            if (animeOption == null)
+            {
+                Debug.Log("AnimePlayer:AnimeOption create failed");
+                return this;
+            }
+            //清空播放队列
+            _animeQueue.Clear();
+            //播放
+            _play(animeOption);
+            
+            //链式调用,后续可通过nextPlay方法添加动画到播放队列
+            return this;
+        }
+
+        //调整动画方向
+        public void changeDirection(Anime.DirectionType directionType)
+        {
+            _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, directionType, _currentAnime.ActionType,
+                _currentAnime.Infinity, _currentAnime.Speed, _currentAnime.onFinishCallback);
+            _play(_currentAnime);
+        }
+        
+        //调整动画动作类型
+        public void changeActionType(Anime.ActionType actionType)
+        {
+            _currentAnime = CreateAnimeOption(_currentAnime.AnimeSerial, _currentAnime.Direction,actionType,
+                _currentAnime.Infinity, _currentAnime.Speed, _currentAnime.onFinishCallback);
+            _play(_currentAnime);
+        }
+
+        //播放
+        private void _play(AnimeOption animeOption)
+        {
+            isPlayable = false;
+            _currentAnime = animeOption;
+            _frames = new AnimeFrame[animeOption.AnimeDetail.FrameCount];
+            
+            //获取动画帧数据
+            for (int i = 0; i < animeOption.AnimeDetail.AnimeFrameInfos.Length; i++)
+            {
+                GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoDataByIndex(animeOption.AnimeDetail.Version, animeOption.AnimeDetail.AnimeFrameInfos[i].GraphicIndex);
+                if (graphicInfoData == null)
+                {
+                    Debug.Log("GraphicInfo Version:" + animeOption.AnimeDetail.Version + " Index:" +
+                              animeOption.AnimeDetail.AnimeFrameInfos[i] + " is null");
+                    continue;
+                }
+                GraphicData graphicData = Graphic.GetGraphicData(graphicInfoData);
+                if (graphicData == null)
+                {
+                    Debug.Log("GraphicData Version:" + animeOption.AnimeDetail.Version + " Index:" +
+                              animeOption.AnimeDetail.AnimeFrameInfos[i] + " is null");
+                    continue;
+                }
+                
+                //创建帧数据
+                _frames[i] = new AnimeFrame();
+                _frames[i].Index = i;
+                _frames[i].GraphicInfo = graphicInfoData;
+                _frames[i].Sprite = graphicData.Sprite;
+                _frames[i].AnimeFrameInfo = animeOption.AnimeDetail.AnimeFrameInfos[i];
+            }
+
+            _currentFrame = -1;
+            isPlayable = true;
+            UpdateFrame();
+        }
+
+        //创建动画配置
+        private AnimeOption CreateAnimeOption(uint Serial, Anime.DirectionType Direction, Anime.ActionType ActionType,
+            bool Infinity = false, float Speed = 1f, AnimeCallback onFinishCallback = null)
+        {
+            AnimeDetail animeDetail = Anime.GetAnimeDetail(Serial, Direction, ActionType);
+            if (animeDetail == null)
+            {
+                Debug.Log("AnimePlayer:AnimeDetail is null");
+                return null;
+            }
+            AnimeOption animeOption = new AnimeOption()
+            {
+                AnimeSerial = Serial,
+                Direction = Direction,
+                ActionType = ActionType,
+                Infinity = Infinity,
+                Speed = Speed,
+                FrameRate = animeDetail.CycleTime / Speed / animeDetail.FrameCount,
+                AnimeDetail = animeDetail,
+                onFinishCallback = onFinishCallback,
+            };
+            return animeOption;
+        }
+
+        //加入链式动画播放队列
+        public AnimePlayer nextPlay(uint Serial, Anime.DirectionType Direction, Anime.ActionType ActionType,
+            bool Infinity = false, float Speed = 1f, AnimeCallback onFinishCallback = null)
+        {
+            AnimeOption animeOption = CreateAnimeOption(Serial, Direction, ActionType, Infinity, Speed, onFinishCallback);
+            if (animeOption == null) return this;
+            _animeQueue.Enqueue(animeOption);
+            return this;
+        }
+        
+        //更新计算
+        private void Update()
+        {
+            float now = Time.time * 1000;
+            if (_currentAnime != null && (now - _timer) >= _currentAnime.FrameRate) UpdateFrame();
+        }
+
+        //更新帧
+        private void UpdateFrame()
+        {
+            if (!isPlayable || _frames.Length == 0) return;
+            
+            _currentFrame++;
+            
+            //动画结束
+            if (_currentFrame >= _currentAnime.AnimeDetail.FrameCount)
+            {
+                if(_currentAnime.onFinishCallback!=null) _currentAnime.onFinishCallback();
+                //循环播放
+                if (_currentAnime.Infinity)
+                {
+                    _currentFrame = 0;
+                }
+                //播放下一个动画
+                else if(_animeQueue.Count>0)
+                {
+                    AnimeOption animeOption = _animeQueue.Dequeue();
+                    _play(animeOption);
+                    return;
+                }
+            }
+            
+            //问题帧自动跳过
+            if (_frames[_currentFrame] == null) return;
+            //自动偏移
+            // float graphicWidth = _frames[_currentFrame].Sprite.rect.width;
+            // float graphicHeight = _frames[_currentFrame].Sprite.rect.height;
+            // float offsetX = -_frames[_currentFrame].GraphicInfo.OffsetX;
+            // float offsetY = _frames[_currentFrame].GraphicInfo.OffsetY;
+            
+            //根据当前帧Sprite动态调整对象大小
+            float width = _frames[_currentFrame].Sprite.rect.width * 1f;
+            float height = _frames[_currentFrame].Sprite.rect.height * 1f;
+            
+            _spriteRenderer.sprite = 
+                _frames[_currentFrame].Sprite;
+            _rectTransform.sizeDelta = new Vector2(width, height);
+            _spriteRenderer.size = new Vector2(width, height);
+            _rectTransform.pivot = new Vector2(0.5f,0f);
+            
+            // 2D碰撞器自动调整,但是动态碰撞器反而会导致重叠大物体选中效果不稳定,效果不如固定大小碰撞器好
+            // if (_boxCollider2D != null)
+            // {
+            //     Vector2 newSize =_boxCollider2D.size 
+            //     _boxCollider2D.size = new Vector2(width, height);
+            // }
+            // _rectTransform.pivot = new Vector2(offsetX,offsetY);
+            // _rectTransform.localPosition = new Vector3(0f,  0f);
+            
+            
+            _timer = Time.time * 1000;
+            
+            //动画事件帧监听
+            if(_frames[_currentFrame].AnimeFrameInfo.Effect >0 && onEffectListener!=null) onEffectListener(_frames[_currentFrame].AnimeFrameInfo.Effect);
+            //音频事件帧监听
+            if(_frames[_currentFrame].AnimeFrameInfo.AudioIndex >0 && onAudioListener!=null) onAudioListener(_frames[_currentFrame].AnimeFrameInfo.AudioIndex);
+        }
+    }
+}

+ 53 - 0
CGTool/CGTool.cs

@@ -0,0 +1,53 @@
+/**
+ * 魔力宝贝图档解析脚本 - CGTool
+ * 
+ * @Author  HonorLee (dev@honorlee.me)
+ * @Version 1.0 (2023-04-15)
+ * @License GPL-3.0
+ *
+ * CGTool.cs 入口文件
+ */
+
+using UnityEngine;
+
+namespace CGTool
+{
+    public class CGTool : MonoBehaviour
+    {
+        public readonly static bool DEBUG = true;
+        //Bin基础目录
+        public readonly static string BaseFolder = System.Environment.CurrentDirectory + "/bin";
+        //Palet调色板目录
+        public readonly static string PaletFolder = BaseFolder + "/pal";
+        //Datas目录
+        public readonly static string DataFolder = System.Environment.CurrentDirectory + "/data";
+        //Map地图文件目录
+        public readonly static string MapFolder = DataFolder + "/map";
+        
+        //日志工具
+        // public readonly static Util.Logger Logger = new Util.Logger("CGTool", DEBUG);
+
+        public static bool ShowMapUnitName = true;
+        
+        
+        //初始化CGTool
+        public static void Init()
+        {
+            //初始化加载并缓存 0-15 调色板文件
+            for (int i = 0; i < 16; i++) Palet.GetPalet(i);
+            
+            //初始化加载并缓存GraphicInfo配置表
+            for (int i = 0; i < 2; i++) GraphicInfo.GetGraphicInfo(i);
+
+            //初始化加载动画序列信息
+            Anime.GetAnimeInfo(0);
+            Anime.GetAnimeInfo(105000);
+            
+            //地图索引初始化
+            Map.Init();
+
+            Debug.Log("CGTool初始化完成");
+        }
+
+    }
+}

+ 358 - 0
CGTool/Graphic.cs

@@ -0,0 +1,358 @@
+/**
+ * 魔力宝贝图档解析脚本 - CGTool
+ * 
+ * @Author  HonorLee (dev@honorlee.me)
+ * @Version 1.0 (2023-04-15)
+ * @License GPL-3.0
+ *
+ * Graphic.cs 图档解析类
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+
+namespace CGTool
+{
+    public class GraphicData
+    {
+        //版本号
+        public int Version;
+        //索引
+        public uint Index;
+        //地图编号
+        public uint MapSerial;
+        //图档宽度
+        public uint Width;
+        //图档高度
+        public uint Height;
+        //图档偏移X
+        public int OffsetX;
+        //图档偏移Y
+        public int OffsetY;
+        //Palet调色板Index
+        public int PaletIndex;
+        //图档Sprite
+        public Sprite Sprite;
+        //图档主色调,用于小地图绘制
+        public Color32 PrimaryColor;
+    }
+    public class Graphic
+    {
+        //缓存Addr  Version -> Addr -> PaletIndex -> GraphicData
+        private static Dictionary<int, Dictionary<uint, Dictionary<int, GraphicData>>> _cache =
+            new Dictionary<int, Dictionary<uint, Dictionary<int, GraphicData>>>();
+        //
+        // //缓存Index映射 Version -> Index -> PaletIndex -> GraphicData
+        // private static Dictionary<int, Dictionary<uint, Dictionary<int, GraphicData>>> _indexCache =
+        //     new Dictionary<int, Dictionary<uint, Dictionary<int, GraphicData>>>();
+        //
+        // //缓存MapSerial映射 Version -> MapSerial -> PaletIndex -> GraphicData
+        // private static Dictionary<int, Dictionary<uint, Dictionary<int, GraphicData>>> _serialCache =
+        //     new Dictionary<int, Dictionary<uint, Dictionary<int, GraphicData>>>();
+        
+        private static List<string> _graphicPaths = new List<string>()
+        {
+            //龙之沙漏 之前版本前图档数据
+            "Graphic_66.bin",
+            //龙之沙漏 版本图档数据
+            "GraphicEx_5.bin"
+        };
+        
+        //根据地址获取GraphicData
+        public static GraphicData GetGraphicData(GraphicInfoData graphicInfoData,int PaletIndex=0)
+        {
+            GraphicData graphicData = null;
+            //缓存数据
+            if (_cache.ContainsKey(graphicInfoData.Version))
+            {
+                if (_cache[graphicInfoData.Version].ContainsKey(graphicInfoData.Addr))
+                {
+                    if (_cache[graphicInfoData.Version][graphicInfoData.Addr].ContainsKey(PaletIndex))
+                    {
+                        graphicData = _cache[graphicInfoData.Version][graphicInfoData.Addr][PaletIndex];
+                    }
+                }
+            }
+            //无缓存则加载数据
+            if (graphicData == null) graphicData = _loadGraphicData(graphicInfoData, PaletIndex);
+            
+            return graphicData;
+        }
+
+        //初始化加载GraphicInfo
+        private static GraphicData _loadGraphicData(GraphicInfoData graphicInfoData, int PaletIndex = 0)
+        {
+            //查找图档文件
+            string fileName = _graphicPaths[graphicInfoData.Version];
+            FileInfo file = new FileInfo(CGTool.BaseFolder + "/" + fileName);
+            if (!file.Exists) return null;
+
+            //创建流读取器
+            FileStream fileStream = file.OpenRead();
+            BinaryReader fileReader = new BinaryReader(fileStream);
+
+            //获取调色板
+            List<Color32> palet = Palet.GetPalet(PaletIndex);
+
+            GraphicData graphicData = new GraphicData();
+            List<Color32> pixels = new List<Color32>();
+
+            //调整流指针
+            fileStream.Position = graphicInfoData.Addr;
+
+            //读入目标字节集
+            byte[] Content = fileReader.ReadBytes((int) graphicInfoData.Length);
+
+            //关闭文件链接
+            fileReader.Dispose();
+            fileReader.Close();
+            fileStream.Close();
+
+            //读取缓存字节集
+            BinaryReader contentReader = new BinaryReader(new MemoryStream(Content));
+
+            //16字节头信息
+            byte[] HEAD = contentReader.ReadBytes(2);
+            int Version = contentReader.ReadByte();
+            int Unknow = contentReader.ReadByte();
+            uint Width = contentReader.ReadUInt32();
+            uint Height = contentReader.ReadUInt32();
+            uint Length = contentReader.ReadUInt32();
+
+            //主色调色值
+            int r = 0;
+            int g = 0;
+            int b = 0;
+            //数据长度
+            uint contentLen = Length - 16;
+            //非压缩型数据
+            if (Version == 0)
+            {
+                while (true)
+                {
+                    Color32 color32;
+                    try
+                    {
+                        color32 = palet[contentReader.ReadByte()];
+                    }
+                    catch (Exception e)
+                    {
+                        break;
+                    }
+                    pixels.Add(color32);
+                    r += color32.r;
+                    g += color32.g;
+                    b += color32.b;
+                }
+            }
+            else
+                //压缩型数据解压
+            {
+                int count = 0;
+                while (true)
+                {
+                    count++;
+                    int head;
+                    try
+                    {
+                        head = contentReader.ReadByte();
+                    }
+                    catch (Exception e)
+                    {
+                        break;
+                    }
+                    
+                    int repeat = 0;
+                    Color32 color32;
+                    if (head < 0x10)
+                    {
+                        repeat = head;
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            color32 = palet[contentReader.ReadByte()];
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else if (head < 0x20)
+                    {
+                        repeat = head % 0x10 * 0x100 + contentReader.ReadByte();
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            color32 = palet[contentReader.ReadByte()];
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else if (head < 0x80)
+                    {
+                        repeat = head % 0x20 * 0x10000 + contentReader.ReadByte() * 0x100 + contentReader.ReadByte();
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            color32 = palet[contentReader.ReadByte()];
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else if (head < 0x90)
+                    {
+                        repeat = head % 0x80;
+                        color32 = palet[contentReader.ReadByte()];
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else if (head < 0xa0)
+                    {
+                        color32 = palet[contentReader.ReadByte()];
+                        repeat = head % 0x90 * 0x100 + contentReader.ReadByte();
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else if (head < 0xc0)
+                    {
+                        color32 = palet[contentReader.ReadByte()];
+                        repeat = head % 0xa0 * 0x10000 + contentReader.ReadByte() * 0x100 + contentReader.ReadByte();
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else if (head < 0xd0)
+                    {
+                        color32 = Color.clear;
+                        repeat = head % 0xc0;
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else if (head < 0xe0)
+                    {
+                        color32 = Color.clear;
+                        repeat = head % 0xd0 * 0x100 + contentReader.ReadByte();
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+
+                    }
+                    else
+                    {
+                        color32 = Color.clear;
+                        repeat = head % 0xe0 * 0x10000 + contentReader.ReadByte() * 0x100 + contentReader.ReadByte();
+                        for (var i = 0; i < repeat; i++)
+                        {
+                            r += color32.r;
+                            g += color32.g;
+                            b += color32.b;
+                            pixels.Add(color32);
+                        }
+                    }
+                }
+            }
+
+            //主色调计算及提亮
+            r = r / pixels.Count * 2;
+            g = g / pixels.Count * 2;
+            b = b / pixels.Count * 2;
+            if (r > 255) r = 255;
+            if (g > 255) g = 255;
+            if (b > 255) b = 255;
+            //主色调
+            Color32 primaryColor32 = new Color32((byte) r, (byte) g, (byte) b, 255);
+
+            //释放连接
+            contentReader.Dispose();
+            contentReader.Close();
+
+            //创建Sprite对象
+            Texture2D texture2D = new Texture2D((int) graphicInfoData.Width, (int) graphicInfoData.Height,
+                TextureFormat.RGBA32, false);
+            
+            int len = (int) (graphicInfoData.Width * graphicInfoData.Height);
+            if (pixels.Count != len)
+            {
+                if (pixels.Count > len)
+                {
+                    pixels = pixels.GetRange(0, len);
+                }
+                else
+                {
+                    Color32[] temc = new Color32[len - pixels.Count];
+                    ArrayList.Repeat(Color.clear, len - pixels.Count).CopyTo(temc);
+                    pixels.AddRange(temc);
+                }
+
+            }
+
+            texture2D.SetPixels32(pixels.ToArray());
+            texture2D.filterMode = FilterMode.Point;
+            // texture2D.Compress(true);
+            texture2D.Apply();
+            
+            //直接通过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(texture2D, new Rect(0, 0, texture2D.width, texture2D.height), offset, 1,1,SpriteMeshType.FullRect);
+
+            //写入数据
+            graphicData.Version = graphicInfoData.Version;
+            graphicData.Index = graphicInfoData.Index;
+            graphicData.MapSerial = graphicInfoData.MapSerial;
+            graphicData.Width = graphicInfoData.Width;
+            graphicData.Height = graphicInfoData.Height;
+            graphicData.OffsetX = graphicInfoData.OffsetX;
+            graphicData.OffsetY = graphicInfoData.OffsetY;
+            graphicData.PaletIndex = PaletIndex;
+            graphicData.Sprite = sprite;
+            graphicData.PrimaryColor = primaryColor32;
+            
+            //缓存
+            if (!_cache.ContainsKey(graphicInfoData.Version))
+                _cache.Add(graphicInfoData.Version, new Dictionary<uint, Dictionary<int, GraphicData>>());
+            if(!_cache[graphicInfoData.Version].ContainsKey(graphicInfoData.Addr)) _cache[graphicInfoData.Version].Add(graphicInfoData.Addr,new Dictionary<int, GraphicData>());
+            if (!_cache[graphicInfoData.Version][graphicInfoData.Addr].ContainsKey(PaletIndex))
+                _cache[graphicInfoData.Version][graphicInfoData.Addr].Add(PaletIndex, graphicData);
+            
+            return graphicData;
+        }
+        
+    }
+}

+ 167 - 0
CGTool/GraphicInfo.cs

@@ -0,0 +1,167 @@
+/**
+ * 魔力宝贝图档解析脚本 - CGTool
+ * 
+ * @Author  HonorLee (dev@honorlee.me)
+ * @Version 1.0 (2023-04-15)
+ * @License GPL-3.0
+ *
+ * GraphicInfo.cs 图档索引解析类
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+// using Logger = Util.Logger;
+
+namespace CGTool
+{
+    //GraphicInfo数据块
+    public class GraphicInfoData
+    {
+        //版本号
+        public int Version;
+        //4 bytes   索引
+        public uint Index;
+        //4 bytes   Graphic 地址
+        public uint Addr;
+        //4 bytes   Graphic 数据长度
+        public uint Length;
+        //4 bytes   Graphic 偏移 - X
+        public int OffsetX;
+        //4 bytes   Graphic 偏移 - Y
+        public int OffsetY;
+        //4 bytes   Graphic 宽
+        public uint Width;
+        //4 bytes   Graphic 高
+        public uint Height;
+        //4 bytes   Graphic East占地
+        public int East;
+        //4 bytes   Graphic South 占地
+        public int South;
+        //bool      穿越标识
+        public bool Blocked;
+        //1 byte    作为地面无层级遮挡[Test]
+        public byte AsGround;
+        //4 bytes   未知标识
+        public byte[] Unknow;
+        //4 bytes   地图编号
+        public uint MapSerial;
+    }
+
+    public class GraphicInfo:MonoBehaviour
+    {
+        // private static Logger _logger = new Logger("GraphicInfo", false);
+        //版本索引字典    版本编号
+        private static Dictionary<int, List<GraphicInfoData>> _cache = new Dictionary<int, List<GraphicInfoData>>();
+
+        //版本-Addr映射字典   版本编号 -> Index -> GraphicInfoData
+        private static Dictionary<int, Dictionary<uint, GraphicInfoData>>
+            _indexDict = new Dictionary<int, Dictionary<uint, GraphicInfoData>>();
+        
+        //版本-Map编号映射字典  版本编号 -> MapSerial -> GraphicInfoData
+        private static Dictionary<int, Dictionary<uint, GraphicInfoData>>
+            _mapSerialDict = new Dictionary<int, Dictionary<uint, GraphicInfoData>>();
+
+        private static List<string> _graphicInfoPaths = new List<string>()
+        {
+            //龙之沙漏 之前版本前Info数据
+            "GraphicInfo_66.bin",
+            //龙之沙漏 版本Info数据
+            "GraphicInfoEx_5.bin"
+        };
+
+        //获取GraphicInfo数据,Info数据加载后会缓存
+        public static List<GraphicInfoData> GetGraphicInfo(int Version)
+        {
+            //返回缓存数据
+            if (_cache.ContainsKey(Version)) return _cache[Version];
+            
+            //初始化映射库
+            _indexDict.Add(Version,new Dictionary<uint, GraphicInfoData>());
+            _mapSerialDict.Add(Version,new Dictionary<uint, GraphicInfoData>());
+            //加载并初始化数据
+            List<GraphicInfoData> infoDatas = _loadGraphicInfo(Version);
+            _cache.Add(Version, infoDatas);
+            
+            return infoDatas;
+        }
+        //通过地面编号获取GraphicInfo数据
+        public static GraphicInfoData GetGraphicInfoDataByMapSerial(int Version, uint MapSerial)
+        {
+            GraphicInfoData graphicInfoData = null;
+            if (_mapSerialDict.ContainsKey(Version))
+            {
+                _mapSerialDict[Version].TryGetValue(MapSerial, out graphicInfoData);
+                // graphicInfoData = _mapSerialDict[Version][MapSerial];
+            }
+
+            return graphicInfoData;
+        }
+        //通过索引获取GraphicInfo数据
+        public static GraphicInfoData GetGraphicInfoDataByIndex(int Version, uint Index)
+        {
+            GraphicInfoData graphicInfoData = null;
+            if (_indexDict.ContainsKey(Version) && _indexDict[Version].ContainsKey(Index))
+            {
+                graphicInfoData = _indexDict[Version][Index];
+            }
+
+            return graphicInfoData;
+        }
+        
+        //初始化加载GraphicInfo
+        private static List<GraphicInfoData> _loadGraphicInfo(int Version)
+        {
+            //查找Info文件
+            string fileName = _graphicInfoPaths[Version];
+            FileInfo file = new FileInfo(CGTool.BaseFolder + "/" + fileName);
+            if (!file.Exists) return null;
+
+            //创建流读取器
+            FileStream fileStream = file.OpenRead();
+            BinaryReader fileReader = new BinaryReader(fileStream); 
+            
+            //解析Info数据表
+            List<GraphicInfoData> infoDatas = new List<GraphicInfoData>();
+            long DataLength = fileStream.Length/40;
+            for (int i = 0; i < DataLength; i++)
+            {
+                GraphicInfoData graphicInfoData = new GraphicInfoData();
+                graphicInfoData.Version = Version;
+                graphicInfoData.Index = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
+                graphicInfoData.Addr = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
+                graphicInfoData.Length = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
+                graphicInfoData.OffsetX = BitConverter.ToInt32(fileReader.ReadBytes(4),0);
+                graphicInfoData.OffsetY = BitConverter.ToInt32(fileReader.ReadBytes(4),0);
+                graphicInfoData.Width = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
+                graphicInfoData.Height = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
+                graphicInfoData.East = fileReader.ReadByte();
+                graphicInfoData.South = fileReader.ReadByte();
+                graphicInfoData.Blocked =  fileReader.ReadByte() == 0;
+                graphicInfoData.AsGround = fileReader.ReadByte();
+                graphicInfoData.Unknow = fileReader.ReadBytes(4);
+                graphicInfoData.MapSerial = BitConverter.ToUInt32(fileReader.ReadBytes(4),0);
+
+                //建立映射表
+                if(!_indexDict[Version].ContainsKey(graphicInfoData.Index)) _indexDict[Version].Add(graphicInfoData.Index, graphicInfoData);
+                if(graphicInfoData.MapSerial > 0 && !_mapSerialDict[Version].ContainsKey(graphicInfoData.MapSerial)) _mapSerialDict[Version].Add(graphicInfoData.MapSerial, graphicInfoData);
+                
+                infoDatas.Add(graphicInfoData);
+
+                // _logger.Write("Index: " + graphicInfoData.Index + " Addr: " + graphicInfoData.Addr + 
+                //               " Width: " + graphicInfoData.Width + 
+                //               " Height: " + graphicInfoData.Height +
+                //               " OffsetX: " + graphicInfoData.OffsetX +
+                //               " OffsetY: " + graphicInfoData.OffsetY +
+                //               " East: " + graphicInfoData.East +
+                //               " South: " + graphicInfoData.South +
+                //               " Blocked: " + graphicInfoData.Blocked +
+                //               " Unknow: " + BitConverter.ToString(graphicInfoData.Unknow).Replace("-", ",") +
+                //               " MapSerial: " + graphicInfoData.MapSerial);
+            }
+            // CGTool.Logger.Write("加载GraphicInfo - 版本: " + Version + " 文件: " + fileName + " 贴图总量: "+ infoDatas.Count);
+            return infoDatas;
+        }
+    }
+}

+ 14 - 0
CGTool/LICENSE

@@ -0,0 +1,14 @@
+    Copyright (C) <2022>  <HonorLee @ honorlee.me>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.

+ 245 - 0
CGTool/Map.cs

@@ -0,0 +1,245 @@
+/**
+ * 魔力宝贝图档解析脚本 - CGTool
+ * 
+ * @Author  HonorLee (dev@honorlee.me)
+ * @Version 1.0 (2023-04-15)
+ * @License GPL-3.0
+ *
+ * Map.cs 服务端地图解析类
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace CGTool
+{
+    //地图文件信息
+    public class MapFileInfo
+    {
+        public uint Serial;
+        public string Name;
+        public string FileName;
+    }
+    //地图块数据
+    public class MapBlockData
+    {
+        public GraphicInfoData GraphicInfo;
+        // public uint GraphicIndex;
+        public uint MapSerial;
+    }
+    //地图信息
+    public class MapInfo
+    {
+        //地图编号
+        public uint Serial;
+        //地图宽度
+        public uint Width;
+        //地图高度
+        public uint Height;
+        // 地图名称
+        public string Name;
+        //未知数据
+        public byte[] Unknow;
+        //地面数据
+        public List<MapBlockData> GroundDatas = new List<MapBlockData>();
+        //地表数据
+        public List<MapBlockData> ObjectDatas = new List<MapBlockData>();
+        public bool[] BlockedIndexs;
+        public bool[,] MapPoints;
+    }
+    
+    public class Map
+    {
+        
+        //缓存数据
+        private static Dictionary<uint, MapInfo> _cache = new Dictionary<uint, MapInfo>();
+        private static Dictionary<uint, MapFileInfo> _mapIndexFiles = new Dictionary<uint, MapFileInfo>(); 
+
+        //初始化地图文件列表
+        public static void Init()
+        {
+            DirectoryInfo mapDirectory = new DirectoryInfo(CGTool.MapFolder);
+            FileInfo[] mapFiles = mapDirectory.GetFiles();
+            foreach (var fileInfo in mapFiles)
+            {
+                string filename = fileInfo.Name;
+                if(filename.Equals(".DS_Store")) continue;
+                MapFileInfo _file = new MapFileInfo();
+                string[] indexName = filename.Split(("_").ToCharArray());
+                _file.Serial = uint.Parse(indexName[0]);
+                _file.Name = indexName[1];
+                _file.FileName = filename;
+                _mapIndexFiles.Add(_file.Serial, _file);
+            }
+        }
+
+        //获取全部地图列表
+        public static List<MapFileInfo> GetMapList()
+        {
+            List<MapFileInfo> _list = new List<MapFileInfo>();
+            foreach (var mapIndexFile in _mapIndexFiles)
+            {
+                _list.Add(mapIndexFile.Value);
+            }
+
+            return _list;
+        }
+        //获取地图数据
+        public static MapInfo GetMap(uint serial)
+        {
+            
+            //返回缓存数据
+            if (_cache.ContainsKey(serial)) return _cache[serial];
+            //加载数据
+            MapInfo mapInfo = _loadMap(serial);
+            return mapInfo;
+        }
+
+        //加载地图数据
+        private static MapInfo _loadMap(uint serial)
+        {
+            // CGTool.Logger.Write("开始加载时间:" + DateTime.Now);
+            if (!_mapIndexFiles.ContainsKey(serial)) return null;
+            
+            // print("找到地图文件: " + mapFileInfo.Name);
+            FileStream mapFileStream = new FileStream(CGTool.MapFolder + "/" + _mapIndexFiles[serial].FileName, FileMode.Open);
+            BinaryReader mapFileReader = new BinaryReader(mapFileStream);
+            
+            MapInfo mapInfo = new MapInfo();
+            mapInfo.Serial = serial;
+
+            //地图文件头
+            byte[] mapHeader = mapFileReader.ReadBytes( 8);
+            //地图名称
+            byte[] mapNameBytes = mapFileReader.ReadBytes(32);
+            mapInfo.Name = System.Text.Encoding.GetEncoding("GBK").GetString(mapNameBytes).Split('|')[0];
+
+            //读取地图宽度
+            byte[] bytes = mapFileReader.ReadBytes(2);
+            Array.Reverse(bytes);
+            mapInfo.Width = (uint)BitConverter.ToUInt16(bytes,0);
+            //读取地图高度
+            bytes = mapFileReader.ReadBytes(2);
+            Array.Reverse(bytes);
+            mapInfo.Height = (uint)BitConverter.ToUInt16(bytes,0);
+
+            byte[] mapBytes = mapFileReader.ReadBytes((int) (mapInfo.Width * mapInfo.Height * 2));
+            byte[] mapCoverBytes = mapFileReader.ReadBytes((int) (mapInfo.Width * mapInfo.Height * 2));
+
+            mapFileReader.Dispose();
+            mapFileReader.Close();
+            mapFileStream.Close();
+
+            // print(JsonUtility.ToJson(mapInfo));
+            
+            BinaryReader mapReader = new BinaryReader(new MemoryStream(mapBytes));
+            BinaryReader mapCoverReader = new BinaryReader(new MemoryStream(mapCoverBytes));
+            // BinaryReader mapInfoReader = new BinaryReader(new MemoryStream(mapInfoBytes));
+
+            List<MapBlockData> tempGroundTiles = new List<MapBlockData>();
+            List<MapBlockData> tempObjectTiles = new List<MapBlockData>();
+            
+            // CGTool.Logger.Write("开始解析时间:" + DateTime.Now);
+            uint len = mapInfo.Width * mapInfo.Height;
+            for (uint i = 0; i < len; i++)
+            {
+                //地面数据
+                MapBlockData mapTile = null;
+                bytes = mapReader.ReadBytes(2);
+                Array.Reverse(bytes);
+                uint mapGraphicSerial = BitConverter.ToUInt16(bytes,0);
+                int Version = 0;
+                if (mapGraphicSerial > 20000)
+                {
+                    mapGraphicSerial += 200000;
+                    Version = 1;
+                }
+                GraphicInfoData graphicInfoData = GraphicInfo.GetGraphicInfoDataByMapSerial(Version, mapGraphicSerial);
+                if (graphicInfoData != null)
+                {
+                    mapTile = new MapBlockData();
+                    mapTile.GraphicInfo = graphicInfoData;
+                    mapTile.MapSerial = mapGraphicSerial;
+                }
+                tempGroundTiles.Add(mapTile);
+                
+                MapBlockData mapCoverTile = null;
+                bytes = mapCoverReader.ReadBytes(2);
+                Array.Reverse(bytes);
+                uint mapCoverGraphicSerial = BitConverter.ToUInt16(bytes,0);
+                Version = 0;
+                if (mapCoverGraphicSerial > 30000 || mapCoverGraphicSerial==25290)
+                {
+                    mapCoverGraphicSerial += 200000;
+                    Version = 1;
+                }
+                graphicInfoData = GraphicInfo.GetGraphicInfoDataByMapSerial(Version, mapCoverGraphicSerial);
+                if (graphicInfoData != null)
+                {
+                    mapCoverTile = new MapBlockData();
+                    mapCoverTile.GraphicInfo = graphicInfoData;
+                    mapCoverTile.MapSerial = mapCoverGraphicSerial;
+                }
+                tempObjectTiles.Add(mapCoverTile);
+            }
+
+            List<MapBlockData> GroundTiles = new List<MapBlockData>();
+            List<MapBlockData> ObjectTiles = new List<MapBlockData>();
+            bool[] blockedIndexs = new bool[mapInfo.Width * mapInfo.Height];
+            // CGTool.Logger.Write("开始排序时间:" + DateTime.Now);
+            for (int y = 0; y < mapInfo.Height; y++)
+            {
+                for (int x = 0; x < mapInfo.Width; x++)
+                {
+                    // int index = i * (int) mapInfo.Width + ((int) mapInfo.Width - j - 1);
+                    int _tmpindex = x + (int)((mapInfo.Height - y - 1) * mapInfo.Width);
+                    int index = x + y * (int)mapInfo.Width;
+                    
+                    MapBlockData mapTile = tempGroundTiles[_tmpindex]; 
+                    MapBlockData ObjectTile = tempObjectTiles[_tmpindex];
+                    
+                    GroundTiles.Add(mapTile);
+                    ObjectTiles.Add(ObjectTile);
+
+                    if (mapTile==null || mapTile.GraphicInfo.Blocked) blockedIndexs[index] = true;
+                    if (ObjectTile!=null && ObjectTile.GraphicInfo.Blocked)
+                    {
+                        blockedIndexs[index] = true;
+                        if (ObjectTile.GraphicInfo.East > 0 || ObjectTile.GraphicInfo.South > 0)
+                        {
+                            for (int i = x; i < (x + ObjectTile.GraphicInfo.East); i++)
+                            {
+                                for (int j = y; j < (y+ ObjectTile.GraphicInfo.South); j++)
+                                {
+
+                                    if(i>=mapInfo.Width || j>=mapInfo.Height) continue;
+                                    int _index = (int) (j * mapInfo.Width + i);
+                                    blockedIndexs[_index] = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            bool[,] points = new bool[mapInfo.Width, mapInfo.Height];
+            for (int y = 0; y < mapInfo.Height; y++)
+            {
+                for (int x = 0; x < mapInfo.Width; x++)
+                {
+                    int index = x + y * (int)mapInfo.Width;
+                    points[x, y] = !blockedIndexs[index];
+                }
+            }
+
+            mapInfo.GroundDatas = GroundTiles;
+            mapInfo.ObjectDatas = ObjectTiles;
+            mapInfo.BlockedIndexs = blockedIndexs;
+            mapInfo.MapPoints = points;
+            _cache[serial] = mapInfo;
+            // CGTool.Logger.Write("地图解析完成时间:" + DateTime.Now);
+            return mapInfo;
+        }
+    }
+}

+ 111 - 0
CGTool/Palet.cs

@@ -0,0 +1,111 @@
+/**
+ * 魔力宝贝图档解析脚本 - CGTool
+ * 
+ * @Author  HonorLee (dev@honorlee.me)
+ * @Version 1.0 (2023-04-15)
+ * @License GPL-3.0
+ *
+ * Palet.cs 调色板解析类
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+// using Logger = Util.Logger;
+
+namespace CGTool
+{
+    public class Palet
+    {
+
+        private static Dictionary<int, List<Color32>> _cache = new Dictionary<int, List<Color32>>();
+
+        //获取调色板
+        public static List<Color32> GetPalet(int index)
+        {
+            //返回缓存数据
+            if (_cache.ContainsKey(index)) return _cache[index];
+            //获取新调色板
+            List<Color32> paletData = _loadPalet(index);
+            //加入缓存
+            if (paletData != null) _cache.Add(index, paletData);
+            if (paletData == null) paletData = GetPalet(0);
+            return paletData;
+        }
+
+        //加载缓存数据
+        private static List<Color32> _loadPalet(int index)
+        {
+            //查找调色板文件
+            DirectoryInfo paletFolderInfo = new DirectoryInfo(CGTool.PaletFolder);
+            string filledIndex = Util.StringTool.numToString((uint)index, 2, true);
+            FileInfo[] files = paletFolderInfo.GetFiles("palet_" + filledIndex + ".cgp");
+            if (files.Length == 0) return null;
+            // CGTool.Logger.Write("加载调色板 - 编号: " + filledIndex);
+            //创建流读取器
+            FileInfo paletFileInfo = files[0];
+            FileStream paletFileStream = paletFileInfo.OpenRead();
+            BinaryReader paletReader = new BinaryReader(paletFileStream);
+            
+            //调色板解析表
+            List<Color32> PaletColors = new List<Color32>();
+
+            //头部调色板
+            int[] headPlate = new int[]
+            {
+                0x000000, 0x000080, 0x008000, 0x008080, 0x800080, 0x800000, 0x808000, 0xc0c0c0, 0xc0dcc0, 0xf0caa6,
+                0x0000de, 0x005fff, 0xa0ffff, 0xd25f00, 0xffd250, 0x28e128
+            };
+            //尾部调色板
+            int[] footPlate = new int[]
+            {
+                0x96c3f5, 0x5fa01e, 0x467dc3, 0x1e559b, 0x374146, 0x1e2328, 0xf0fbff, 0xa56e3a, 0x808080, 0x0000ff,
+                0x00ff00, 0x00ffff, 0xff0000, 0xff80ff, 0xffff00, 0xffffff
+            };
+
+            //解压缩
+            foreach (var i in headPlate)
+            {
+                byte[] bytes = BitConverter.GetBytes(i);
+                Color32 color32 = new Color32();
+                color32.r = bytes[0];
+                color32.g = bytes[1];
+                color32.b = bytes[2];
+                color32.a = 0xFF;
+                PaletColors.Add(color32);
+            }
+            
+            
+            for (var i = 0; i < 224; i++)
+            {
+                byte[] paletBytes = paletReader.ReadBytes(3);
+                // if(i<16 || i>224) continue;
+                Color32 color32 = new Color32();
+                color32.r = paletBytes[2];
+                color32.g = paletBytes[1];
+                color32.b = paletBytes[0];
+                color32.a = 0xFF;
+                PaletColors.Add(color32);
+            }
+            
+            foreach (var i in footPlate)
+            {
+                byte[] bytes = BitConverter.GetBytes(i);
+                Color32 color32 = new Color32();
+                color32.r = bytes[0];
+                color32.g = bytes[1];
+                color32.b = bytes[2];
+                color32.a = 0xFF;
+                PaletColors.Add(color32);
+            }
+            //清理缓存
+            paletReader.Dispose();
+            paletReader.Close();
+            paletFileStream.Dispose();
+            paletFileStream.Close();
+            
+            return PaletColors;
+        }
+    }
+}

+ 14 - 0
LICENSE

@@ -0,0 +1,14 @@
+    Copyright (C) <2022>  <HonorLee @ honorlee.me>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.

+ 113 - 0
README.md

@@ -0,0 +1,113 @@
+# 魔力宝贝Unity C#图档解析脚本
+
+## 1、开源目的
+本脚本旨在学习研究```Crossgate``` ```魔力宝贝``` 图档bin文件的解压与使用,相关资料来源于互联网文章与相关技术大佬指导
+
+再次感谢```阿伍```等在互联网上分享相关解压、算法解析等教学文章的技术大佬所提供的帮助
+
+本脚本遵照GPL共享协议,可随意修改、调整代码学习使用
+
+请务删除或修改文档或源代码中相关版权声明信息,且严禁用于商业项目
+
+因利用本脚本盈利或其他行为导致的任何商业版权纠纷,脚本作者不承担任何责任或损失
+
+## 2、使用说明
+
+克隆当前仓库或下载zip包解压,将CGTool文件夹放置于Unity项目文件夹内引用
+
+### 框架初始化
+在入口或初始化脚本头部引入CGTool初始化文件
+```csharp
+using CGTool;
+```
+并在关键位置对CGTool进行初始化
+```csharp
+CGTool.CGTool.Init();
+```
+CGTool初始化时,会自动对相关索引Info文件进行解析,请根据实际所采用版本情况,对脚本代码中解析相关的文件名称进行修改调整
+
+### 获取图档索引数据
+```csharp
+//通过地面编号获取GraphicInfo数据
+GraphicInfo.GetGraphicInfoDataByMapSerial(int Version, uint MapSerial);
+//通过索引获取GraphicInfo数据
+GraphicInfo.GetGraphicInfoDataByIndex(int Version, uint Index);
+```
+
+### 获取指定索引图档数据
+```csharp
+//通过图档索引编号获取GraphicData数据
+Graphic.GetGraphicData(GraphicInfoData graphicInfoData,int PaletIndex=0);
+```
+
+### 获取并播放动画数据
+```csharp
+/**
+* 动画播放器,用于播放CG动画,支持多动画队列播放
+* 脚本需绑定至挂载了SpriteRenderer和RectTransform的对象上
+* 除此之外,还需绑定BoxCollider2D(可选),用于监听鼠标的移入移出事件
+*
+* 当动画播放完成后会自动调用onFinishCallback回调函数
+* 另外可指定onActionListener和onAudioListener监听动画动作帧和音频帧
+* 目前已知的动作帧有:
+* 击中 0x27 | 0x28
+* 伤害结算 0x4E | 0x4F
+*/
+
+/**
+* 播放动画,调用此方法将会清空当前播放队列,调用完成可通过链式调用nextPlay方法添加动画到播放队列
+* @param Serial 动画序列号
+* @param Direction 动画方向
+* @param ActionType 动画动作
+* @param Infinity 是否循环
+* @param Speed 播放速度,以 1s 为基准,根据动画帧率计算实际播放周期时长
+* @param onFinishCallback 动画结束回调
+* @return AnimePlayer
+*/
+AnimePlayer player.play(uint AnimeSerial, Anime.DirectionType.North, Anime.ActionType.Stand, true,0.1f,AnimeCallback onFinishCallback=null);
+
+//设置帧动效反馈监听
+AnimePlayer player.onEffectListener = effect => { };
+
+//设置帧音效反馈监听
+AnimePlayer player.onAudioListener = audioIndex => { };
+
+//链式调用添加动作队列
+AnimePlayer player.play(...params).nextPlay(...params);
+```
+
+### 其他
+请根据情况自行探索修改代码适应应用场景
+
+## 3、版本及功能概述
+### 1.0
+
+当前版本目前仅支持 魔力宝贝3.7-龙之沙漏 及以下版本的图档解析
+
+>`ADD` 脚本初始化
+> 
+>`ADD` 图档索引GraphicInfo文件解析
+> 
+>`ADD` 图档Graphic文件数据解析
+> 
+>`ADD` 调色板Palet文件解析
+> 
+>`ADD` 动画索引AnimeInfo文件解析
+> 
+>`ADD` 动画Anime文件数据解析
+> 
+>`ADD` <font color="red">服务端</font>地图文件解析
+
+
+
+## 4、待处理
+
+- 支援 4.0 以上版本图档解析
+- 音频解析
+- 其他未知
+- 优化
+
+## LICENSE
+This project is licensed under the GPL license. Copyrights are respective of each contributor listed at the beginning of each definition file.
+
+