qiuqiu 2 месяцев назад
Родитель
Сommit
b66fb837c2

+ 5 - 2
.idea/workspace.xml

@@ -7,9 +7,11 @@
   </component>
   <component name="ChangeListManager">
     <list default="true" id="fec10672-acda-4616-894b-a4b6f93aea6f" name="Default Changelist" comment="提交">
+      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/笔记文件/2.笔记/FMOD中台库.md" beforeDir="false" afterPath="$PROJECT_DIR$/笔记文件/2.笔记/FMOD中台库.md" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/笔记文件/日记/2025_10_14_星期二.md" beforeDir="false" afterPath="$PROJECT_DIR$/笔记文件/日记/2025_10_14_星期二.md" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/笔记文件/日记/2025_10_16_星期四.md" beforeDir="false" afterPath="$PROJECT_DIR$/笔记文件/日记/2025_10_16_星期四.md" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/笔记文件/2.笔记/FairyGUI_第一章.md" beforeDir="false" afterPath="$PROJECT_DIR$/笔记文件/2.笔记/FairyGUI_第一章.md" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/笔记文件/2.笔记/引导系统.md" beforeDir="false" afterPath="$PROJECT_DIR$/笔记文件/2.笔记/引导系统.md" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/笔记文件/2.笔记/瀑布主题替换 临时记录.md" beforeDir="false" afterPath="$PROJECT_DIR$/笔记文件/2.笔记/瀑布主题替换 临时记录.md" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -156,6 +158,7 @@
       <workItem from="1759108112708" duration="607000" />
       <workItem from="1759971786581" duration="1810000" />
       <workItem from="1760342699754" duration="303000" />
+      <workItem from="1760663783767" duration="1880000" />
     </task>
     <task id="LOCAL-00023" summary="提交">
       <created>1744710005735</created>

+ 2 - 1
笔记文件/2.笔记/FMOD中台库.md

@@ -35,4 +35,5 @@ FMod软件,运行时界面:
 
 切换页签,Events主要是用于编辑,右键可以创建
 
-![[Pasted image 20251016143353.png]]
+![[Pasted image 20251016143353.png]]
+

+ 3 - 0
笔记文件/2.笔记/FairyGUI_第一章.md

@@ -0,0 +1,3 @@
+完全免费开源的
+
+![[Pasted image 20251017171737.png]]

+ 3 - 1
笔记文件/2.笔记/引导系统.md

@@ -14,4 +14,6 @@
 
 目前修改逻辑代码位置,这仨:
 
-![[Pasted image 20250910113640.png]]
+![[Pasted image 20250910113640.png]]
+
+需求文档:https://inspire.sg.larksuite.com/wiki/D5cWwdxRDis74AkRDYmlzwq4gPc

+ 28 - 0
笔记文件/2.笔记/瀑布主题替换 临时记录.md

@@ -1,5 +1,8 @@
 #灵感 
 
+主题同步,相关文档:
+
+
 对应的主题路径:
 
 ![[Pasted image 20251015135627.png]]
@@ -14,3 +17,28 @@
 
 ![[Pasted image 20251015140511.png]]
 
+一开始的时候,会没法加载出主题
+
+![[Pasted image 20251017164511.png]]
+
+主题相关的加载配置,是这个
+
+![[Pasted image 20251017164542.png]]
+
+加上这个相关的配置,就可以了
+
+![[Pasted image 20251017164704.png]]
+
+lua的入口,是这里,如果要加lua断点的话,是可以在这里加的
+
+![[Pasted image 20251017164919.png]]
+
+主题配置,相关路径:
+
+![[Pasted image 20251017165421.png]]
+
+服务端推送的,主题列表,是在大厅这里的逻辑
+
+![[Pasted image 20251017180740.png]]
+
+![[Pasted image 20251017180853.png]]

+ 28 - 0
笔记文件/日记/2025_10_17_星期五.md

@@ -0,0 +1,28 @@
+
+10:02
+
+###### [[unity日常积累]]
+
+###### [[节奏天国]]
+
+###### [[帧同步王者荣耀]]
+
+###### [[从零开发跨平台通用日志插件]]
+
+###### [[通用异步网络通信库]]
+
+###### [[高性能定时系统实现]]
+
+###### [[学习资料]]
+
+###### [[其他]]
+
+#### [[看板]]
+
+# 今日任务
+
+- [x] 解决瀑布客户端加载主题的问题
+- [ ] 升级一下cursor,看看gpt5是否可以正常包含和使用
+---
+
+# Journal

+ 1352 - 0
笔记文件/附件/GridMapEditor.cs

@@ -0,0 +1,1352 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace MapEditor
+{
+    /// <summary>
+    /// 网格地图编辑器 - 全新实现
+    /// </summary>
+    public class GridMapEditor : EditorWindow
+    {
+        // Canvas相关
+        private Canvas targetCanvas;
+        private Vector2 canvasSize;
+        
+        // Sprite相关
+        private Sprite currentSprite;
+        private Vector2 spriteSize;
+        
+        // 图层系统
+        [System.Serializable]
+        public class SpriteLayer
+        {
+            public Sprite sprite;
+            public string name;
+            public bool isVisible = true;
+            public bool isPreviewVisible = true; // 在预览窗体中是否可见
+            
+            public SpriteLayer(Sprite sprite)
+            {
+                this.sprite = sprite;
+                this.name = sprite != null ? sprite.name : "未命名图层";
+            }
+        }
+        
+        private System.Collections.Generic.List<SpriteLayer> spriteLayersList = new System.Collections.Generic.List<SpriteLayer>();
+        private int selectedLayerIndex = -1;
+        private Vector2 layerListScrollPosition;
+        private int draggedLayerIndex = -1;
+        
+        // 网格相关
+        private int gridColumns = 0;
+        private int gridRows = 0;
+        
+        // UI相关
+        private Vector2 scrollPosition;
+        private Rect previewRect;
+        private float previewScale = 1f;
+        private bool isPaintingEnabled = true; // 左键绘制,右键擦除
+        private bool showOtherLayers = true; // 是否显示其他图层(半透明)
+        
+        // 颜色
+        private Color gridColor = new Color(0.3f, 0.3f, 0.3f, 0.8f); // 更明显的网格线
+        private Color backgroundColor = new Color(0.2f, 0.2f, 0.2f, 1f);
+
+        // 配置数据模型(仅用于JSON序列化)
+        [System.Serializable]
+        private class MapConfig
+        {
+            public string canvasName;
+            public float canvasWidth;
+            public float canvasHeight;
+            public System.Collections.Generic.List<LayerConfig> layers = new System.Collections.Generic.List<LayerConfig>();
+        }
+
+        [System.Serializable]
+        private class LayerConfig
+        {
+            public string name;
+            public string spriteAssetPath;   // 仅编辑器可用
+            public string resourcesPath;      // 运行时可用(Assets/Resources 下)
+            public bool isVisible;
+            public bool isPreviewVisible;
+            public System.Collections.Generic.List<ItemConfig> items = new System.Collections.Generic.List<ItemConfig>();
+        }
+
+        [System.Serializable]
+        private class ItemConfig
+        {
+            public int cellX;
+            public int cellY;
+            public float posX;   // Canvas坐标(以中心为原点)
+            public float posY;
+        }
+
+        // 放置的数据:按Sprite区分的"图层"
+        private System.Collections.Generic.Dictionary<Sprite, System.Collections.Generic.Dictionary<Vector2Int, GameObject>> spriteLayers = new System.Collections.Generic.Dictionary<Sprite, System.Collections.Generic.Dictionary<Vector2Int, GameObject>>();
+        
+        // 每个图层的Canvas绝对位置记录
+        private System.Collections.Generic.Dictionary<Sprite, System.Collections.Generic.Dictionary<Vector2Int, Vector2>> layerCanvasPositions = new System.Collections.Generic.Dictionary<Sprite, System.Collections.Generic.Dictionary<Vector2Int, Vector2>>();
+
+        // 单元格间距(Canvas单位,像素),默认0
+        private Vector2 cellGap = Vector2.zero;
+
+        [MenuItem("工具/网格地图编辑器")]
+        public static void ShowWindow()
+        {
+            GridMapEditor window = GetWindow<GridMapEditor>("网格地图编辑器");
+            window.minSize = new Vector2(600, 600);
+        }
+
+        private void OnGUI()
+        {
+            DrawToolbar();
+            EditorGUILayout.Space(10);
+            
+            if (targetCanvas != null && spriteLayersList.Count > 0)
+            {
+                DrawPreviewArea();
+            }
+            else
+            {
+                DrawHelpBox();
+            }
+        }
+
+        /// <summary>
+        /// 绘制工具栏
+        /// </summary>
+        private void DrawToolbar()
+        {
+            EditorGUILayout.BeginVertical("box");
+            
+            GUILayout.Label("网格地图编辑器", EditorStyles.boldLabel);
+            EditorGUILayout.Space(5);
+            
+            // Canvas选择
+            EditorGUI.BeginChangeCheck();
+            Canvas newCanvas = (Canvas)EditorGUILayout.ObjectField(
+                "目标Canvas", 
+                targetCanvas, 
+                typeof(Canvas), 
+                true
+            );
+            if (EditorGUI.EndChangeCheck())
+            {
+                targetCanvas = newCanvas;
+                UpdateCanvasSize();
+                CalculateGrid();
+            }
+            
+            if (targetCanvas != null)
+            {
+                EditorGUILayout.LabelField("Canvas大小", $"{canvasSize.x} x {canvasSize.y}");
+            }
+            
+            EditorGUILayout.Space(5);
+            
+            // 图层管理
+            DrawLayerManagement();
+            
+            EditorGUILayout.Space(5);
+            
+            // 网格信息
+            if (gridColumns > 0 && gridRows > 0)
+            {
+                EditorGUILayout.LabelField("网格信息", $"{gridColumns} 列 x {gridRows} 行 = {gridColumns * gridRows} 个单元格");
+            }
+            
+            EditorGUILayout.Space(5);
+
+            // 单元间距设置
+            EditorGUI.BeginChangeCheck();
+            Vector2 newGap = EditorGUILayout.Vector2Field("单元间距", cellGap);
+            if (EditorGUI.EndChangeCheck())
+            {
+                newGap.x = Mathf.Max(0, newGap.x);
+                newGap.y = Mathf.Max(0, newGap.y);
+                cellGap = newGap;
+                CalculateGrid();
+            }
+            
+            // 预览缩放
+            previewScale = EditorGUILayout.Slider("预览缩放", previewScale, 0.1f, 2f);
+            
+            // 其他图层显示开关
+            showOtherLayers = EditorGUILayout.Toggle("显示其他图层", showOtherLayers);
+            
+            EditorGUILayout.Space(10);
+            
+            // 配置保存/加载
+            EditorGUILayout.BeginHorizontal();
+            if (GUILayout.Button("保存配置", GUILayout.Height(24)))
+            {
+                SaveConfigToFile();
+            }
+            if (GUILayout.Button("加载配置", GUILayout.Height(24)))
+            {
+                LoadConfigFromFile();
+            }
+            EditorGUILayout.EndHorizontal();
+
+            EditorGUILayout.Space(5);
+
+            // 保存按钮
+            GUI.enabled = targetCanvas != null && HasPlacedSprites();
+            if (GUILayout.Button("保存为纹理贴图", GUILayout.Height(30)))
+            {
+                SaveAsTexture();
+            }
+            GUI.enabled = true;
+            
+            EditorGUILayout.EndVertical();
+        }
+
+        /// <summary>
+        /// 绘制图层管理UI
+        /// </summary>
+        private void DrawLayerManagement()
+        {
+            EditorGUILayout.BeginVertical("box");
+            GUILayout.Label("图层管理", EditorStyles.boldLabel);
+            
+            // 添加新图层按钮
+            EditorGUILayout.BeginHorizontal();
+            if (GUILayout.Button("添加图层", GUILayout.Height(25)))
+            {
+                // 打开Sprite选择对话框
+                string path = EditorUtility.OpenFilePanel("选择Sprite", "Assets", "png");
+                if (!string.IsNullOrEmpty(path))
+                {
+                    // 将绝对路径转换为相对路径
+                    if (path.StartsWith(Application.dataPath))
+                    {
+                        path = "Assets" + path.Substring(Application.dataPath.Length);
+                    }
+                    
+                    // 加载Sprite
+                    Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(path);
+                    if (sprite != null)
+                    {
+                        AddLayer(sprite);
+                    }
+                    else
+                    {
+                        EditorUtility.DisplayDialog("错误", "无法加载Sprite,请确保选择的是有效的图片文件", "确定");
+                    }
+                }
+            }
+            if (GUILayout.Button("清空所有图层", GUILayout.Height(25)))
+            {
+                if (EditorUtility.DisplayDialog("确认清空", "确定要清空所有图层吗?", "确定", "取消"))
+                {
+                    ClearAllLayers();
+                }
+            }
+            EditorGUILayout.EndHorizontal();
+            
+            EditorGUILayout.Space(5);
+            
+            // 图层列表
+            if (spriteLayersList.Count > 0)
+            {
+                layerListScrollPosition = EditorGUILayout.BeginScrollView(layerListScrollPosition, GUILayout.Height(150));
+                
+                for (int i = 0; i < spriteLayersList.Count; i++)
+                {
+                    DrawLayerItem(i);
+                }
+                
+                EditorGUILayout.EndScrollView();
+            }
+            else
+            {
+                EditorGUILayout.HelpBox("暂无图层,请添加图层开始编辑", MessageType.Info);
+            }
+            
+            EditorGUILayout.EndVertical();
+        }
+        
+        /// <summary>
+        /// 绘制单个图层项
+        /// </summary>
+        private void DrawLayerItem(int index)
+        {
+            if (index >= spriteLayersList.Count) return;
+            
+            var layer = spriteLayersList[index];
+            bool isSelected = selectedLayerIndex == index;
+            
+            EditorGUILayout.BeginHorizontal();
+            
+            // 选择按钮
+            if (GUILayout.Button(isSelected ? "●" : "○", GUILayout.Width(20), GUILayout.Height(20)))
+            {
+                selectedLayerIndex = index;
+                currentSprite = layer.sprite;
+                UpdateSpriteSize();
+                CalculateGrid();
+                Repaint();
+            }
+            
+            // 场景可见性切换
+            EditorGUILayout.LabelField("场景", GUILayout.Width(30));
+            layer.isVisible = EditorGUILayout.Toggle(layer.isVisible, GUILayout.Width(20));
+            
+            // 预览可见性切换
+            EditorGUILayout.LabelField("预览", GUILayout.Width(30));
+            layer.isPreviewVisible = EditorGUILayout.Toggle(layer.isPreviewVisible, GUILayout.Width(20));
+            
+            // 图层名称
+            EditorGUILayout.LabelField(layer.name, isSelected ? EditorStyles.boldLabel : EditorStyles.label);
+            
+            // 上下移动按钮
+            EditorGUILayout.BeginVertical();
+            if (GUILayout.Button("↑", GUILayout.Width(20), GUILayout.Height(15)))
+            {
+                MoveLayerUp(index);
+            }
+            if (GUILayout.Button("↓", GUILayout.Width(20), GUILayout.Height(15)))
+            {
+                MoveLayerDown(index);
+            }
+            EditorGUILayout.EndVertical();
+            
+            // 删除按钮
+            if (GUILayout.Button("×", GUILayout.Width(20), GUILayout.Height(20)))
+            {
+                if (EditorUtility.DisplayDialog("删除图层", $"确定要删除图层 '{layer.name}' 吗?", "确定", "取消"))
+                {
+                    RemoveLayer(index);
+                }
+            }
+            
+            EditorGUILayout.EndHorizontal();
+            
+            // 拖拽处理
+            HandleLayerDrag(index);
+            
+            EditorGUILayout.Space(2);
+        }
+        
+        /// <summary>
+        /// 处理图层拖拽(简化版本)
+        /// </summary>
+        private void HandleLayerDrag(int index)
+        {
+            // 暂时禁用拖拽功能,避免卡顿
+            // 可以通过上下箭头按钮来调整顺序
+        }
+
+        /// <summary>
+        /// 绘制预览区域
+        /// </summary>
+        private void DrawPreviewArea()
+        {
+            EditorGUILayout.BeginVertical("box");
+            GUILayout.Label("预览区域", EditorStyles.boldLabel);
+            
+            // 计算预览区域(取Canvas与网格尺寸中较大者)
+            Vector2 cellSizeCanvas = new Vector2(spriteSize.x + cellGap.x, spriteSize.y + cellGap.y);
+            float gridCanvasWidth = Mathf.Max(1, gridColumns) * cellSizeCanvas.x;
+            float gridCanvasHeight = Mathf.Max(1, gridRows) * cellSizeCanvas.y;
+            float drawCanvasWidth = Mathf.Max(canvasSize.x, gridCanvasWidth);
+            float drawCanvasHeight = Mathf.Max(canvasSize.y, gridCanvasHeight);
+
+            float scaledWidth = drawCanvasWidth * previewScale;
+            float scaledHeight = drawCanvasHeight * previewScale;
+            
+            // 创建滚动视图
+            scrollPosition = EditorGUILayout.BeginScrollView(
+                scrollPosition, 
+                GUILayout.ExpandWidth(true), 
+                GUILayout.ExpandHeight(true)
+            );
+            
+            // 获取预览区域的矩形
+            previewRect = GUILayoutUtility.GetRect(
+                scaledWidth, 
+                scaledHeight, 
+                GUILayout.ExpandWidth(false), 
+                GUILayout.ExpandHeight(false)
+            );
+            
+            // 绘制背景(保持透明,不覆盖网格线)
+            // EditorGUI.DrawRect(previewRect, backgroundColor);
+            
+            // 绘制网格
+            DrawGrid(previewRect);
+            
+            // 处理鼠标
+            HandleMouseInPreview(previewRect);
+            
+            EditorGUILayout.EndScrollView();
+            EditorGUILayout.EndVertical();
+        }
+
+        /// <summary>
+        /// 绘制网格
+        /// </summary>
+        private void DrawGrid(Rect rect)
+        {
+            if (gridColumns <= 0 || gridRows <= 0) return;
+            
+            Handles.BeginGUI();
+            
+            // 将Canvas单位映射到预览区域,保持等比(使用绘制区域的Canvas尺寸)
+            Vector2 cellSizeCanvas = new Vector2(spriteSize.x + cellGap.x, spriteSize.y + cellGap.y);
+            float gridCanvasWidth = Mathf.Max(1, gridColumns) * cellSizeCanvas.x;
+            float gridCanvasHeight = Mathf.Max(1, gridRows) * cellSizeCanvas.y;
+            float drawCanvasWidth = Mathf.Max(canvasSize.x, gridCanvasWidth);
+            float drawCanvasHeight = Mathf.Max(canvasSize.y, gridCanvasHeight);
+            float scaleX = rect.width / drawCanvasWidth;
+            float scaleY = rect.height / drawCanvasHeight;
+            
+            // 计算Canvas在预览区域中的位置(居中显示)
+            float canvasOffsetX = (rect.width - drawCanvasWidth * scaleX) / 2f;
+            float canvasOffsetY = (rect.height - drawCanvasHeight * scaleY) / 2f;
+            
+            float cellWidthCanvas = spriteSize.x + cellGap.x;
+            float cellHeightCanvas = spriteSize.y + cellGap.y;
+            float cellWidth = cellWidthCanvas * scaleX;
+            float cellHeight = cellHeightCanvas * scaleY;
+            float gridWidth = gridColumns * cellWidth;
+            float gridHeight = gridRows * cellHeight;
+            
+            // 网格起始位置(考虑Canvas偏移)
+            float gridStartX = rect.x + canvasOffsetX;
+            float gridStartY = rect.y + canvasOffsetY;
+            
+            // 绘制垂直线
+            for (int x = 0; x <= gridColumns; x++)
+            {
+                float xPos = gridStartX + x * cellWidth;
+                Handles.color = gridColor;
+                Handles.DrawLine(
+                    new Vector3(xPos, gridStartY, 0),
+                    new Vector3(xPos, gridStartY + gridHeight, 0)
+                );
+            }
+            
+            // 绘制水平线
+            for (int y = 0; y <= gridRows; y++)
+            {
+                float yPos = gridStartY + y * cellHeight;
+                Handles.color = gridColor;
+                Handles.DrawLine(
+                    new Vector3(gridStartX, yPos, 0),
+                    new Vector3(gridStartX + gridWidth, yPos, 0)
+                );
+            }
+            
+            // 绘制已放置的Sprite预览
+            if (currentSprite != null)
+            {
+                DrawSpriteGrid(rect, cellWidth, cellHeight);
+            }
+            
+            Handles.EndGUI();
+        }
+
+        /// <summary>
+        /// 在网格中绘制Sprite
+        /// </summary>
+        private void DrawSpriteGrid(Rect rect, float cellWidth, float cellHeight)
+        {
+            if (spriteLayersList.Count == 0) return;
+            
+            // 计算Canvas在预览区域中的缩放和偏移
+            Vector2 cellSizeCanvas = new Vector2(spriteSize.x + cellGap.x, spriteSize.y + cellGap.y);
+            float gridCanvasWidth = Mathf.Max(1, gridColumns) * cellSizeCanvas.x;
+            float gridCanvasHeight = Mathf.Max(1, gridRows) * cellSizeCanvas.y;
+            float drawCanvasWidth = Mathf.Max(canvasSize.x, gridCanvasWidth);
+            float drawCanvasHeight = Mathf.Max(canvasSize.y, gridCanvasHeight);
+            float scaleX = rect.width / drawCanvasWidth;
+            float scaleY = rect.height / drawCanvasHeight;
+            
+            // 计算Canvas在预览区域中的位置(居中显示)
+            float canvasOffsetX = (rect.width - drawCanvasWidth * scaleX) / 2f;
+            float canvasOffsetY = (rect.height - drawCanvasHeight * scaleY) / 2f;
+            
+            // 渲染所有图层(按顺序,从下到上)
+            for (int layerIndex = 0; layerIndex < spriteLayersList.Count; layerIndex++)
+            {
+                var layer = spriteLayersList[layerIndex];
+                if (!layer.isVisible || !layer.isPreviewVisible || layer.sprite == null) continue;
+                
+                bool isSelectedLayer = layerIndex == selectedLayerIndex;
+                
+                // 根据开关决定是否显示其他图层
+                if (!isSelectedLayer && !showOtherLayers) continue;
+                
+                float layerOpacity = isSelectedLayer ? 1f : 0.5f; // 非选中图层半透明
+                
+                // 设置透明度
+                Color originalColor = GUI.color;
+                GUI.color = new Color(1f, 1f, 1f, layerOpacity);
+                
+                // 绘制该图层的所有Sprite(使用原始尺寸)
+                DrawLayerSpritesWithOriginalSize(rect, layer, canvasOffsetX, canvasOffsetY, scaleX, scaleY);
+                
+                // 恢复颜色
+                GUI.color = originalColor;
+            }
+        }
+        
+        /// <summary>
+        /// 绘制单个图层的所有Sprite(使用记录的Canvas绝对位置)
+        /// </summary>
+        private void DrawLayerSpritesWithOriginalSize(Rect rect, SpriteLayer layer, float canvasOffsetX, float canvasOffsetY, float scaleX, float scaleY)
+        {
+            if (layer.sprite == null) return;
+            
+            Texture2D texture = layer.sprite.texture;
+            Rect spriteRect = layer.sprite.rect;
+            
+            // 计算UV坐标
+            Rect uv = new Rect(
+                spriteRect.x / texture.width,
+                spriteRect.y / texture.height,
+                spriteRect.width / texture.width,
+                spriteRect.height / texture.height
+            );
+            
+            // 获取该图层的Sprite数据
+            if (!spriteLayers.ContainsKey(layer.sprite)) return;
+            var layerData = spriteLayers[layer.sprite];
+            
+            // 绘制该图层中已放置的Sprite
+            foreach (var kvp in layerData)
+            {
+                Vector2Int cell = kvp.Key;
+                
+                // 使用记录的Canvas绝对位置
+                if (!layerCanvasPositions.ContainsKey(layer.sprite) || !layerCanvasPositions[layer.sprite].ContainsKey(cell))
+                {
+                    continue; // 没有位置记录,跳过
+                }
+                
+                Vector2 canvasPosition = layerCanvasPositions[layer.sprite][cell];
+                
+                // 将Canvas坐标转换为预览区域坐标
+                // Canvas坐标系:中心为原点,向右为+X,向上为+Y
+                // 预览区域坐标系:左上角为原点,向右为+X,向下为+Y
+                float previewX = rect.x + canvasOffsetX + (canvasPosition.x + canvasSize.x / 2f) * scaleX;
+                float previewY = rect.y + canvasOffsetY + (canvasSize.y / 2f - canvasPosition.y) * scaleY;
+                
+                // 使用该图层Sprite的原始尺寸
+                Vector2 originalSpriteSize = new Vector2(spriteRect.width, spriteRect.height);
+                
+                // 计算Sprite在预览区域中的实际尺寸(保持原始比例)
+                float spriteScaleX = originalSpriteSize.x * scaleX;
+                float spriteScaleY = originalSpriteSize.y * scaleY;
+                
+                // 计算居中位置
+                float centerX = previewX - spriteScaleX / 2f;
+                float centerY = previewY - spriteScaleY / 2f;
+                
+                Rect cellRect = new Rect(centerX, centerY, spriteScaleX, spriteScaleY);
+                
+                GUI.DrawTextureWithTexCoords(cellRect, texture, uv);
+            }
+        }
+        
+        /// <summary>
+        /// 绘制单个图层的所有Sprite(旧方法,保持兼容)
+        /// </summary>
+        private void DrawLayerSprites(Rect rect, SpriteLayer layer, float canvasOffsetX, float canvasOffsetY, float scaleX, float scaleY)
+        {
+            if (layer.sprite == null) return;
+            
+            Texture2D texture = layer.sprite.texture;
+            Rect spriteRect = layer.sprite.rect;
+            
+            // 计算UV坐标
+            Rect uv = new Rect(
+                spriteRect.x / texture.width,
+                spriteRect.y / texture.height,
+                spriteRect.width / texture.width,
+                spriteRect.height / texture.height
+            );
+            
+            // 获取该图层的Sprite数据
+            if (!spriteLayers.ContainsKey(layer.sprite)) return;
+            var layerData = spriteLayers[layer.sprite];
+            
+            float cellWidthCanvas = spriteSize.x + cellGap.x;
+            float cellHeightCanvas = spriteSize.y + cellGap.y;
+            
+            // 绘制该图层中已放置的Sprite
+            foreach (var kvp in layerData)
+            {
+                Vector2Int cell = kvp.Key;
+                Rect cellRect = new Rect(
+                    rect.x + canvasOffsetX + cell.x * cellWidthCanvas * scaleX,
+                    rect.y + canvasOffsetY + cell.y * cellHeightCanvas * scaleY,
+                    spriteSize.x * scaleX,
+                    spriteSize.y * scaleY
+                );
+                
+                GUI.DrawTextureWithTexCoords(cellRect, texture, uv);
+            }
+        }
+
+        /// <summary>
+        /// 处理预览区域内的鼠标事件(左键放置,右键删除)
+        /// </summary>
+        private void HandleMouseInPreview(Rect rect)
+        {
+            if (!isPaintingEnabled || selectedLayerIndex < 0 || selectedLayerIndex >= spriteLayersList.Count || targetCanvas == null) return;
+            
+            var selectedLayer = spriteLayersList[selectedLayerIndex];
+            if (selectedLayer.sprite == null) return;
+            
+            Event e = Event.current;
+            if (e == null) return;
+            if (e.isMouse && (e.type == EventType.MouseDown || e.type == EventType.MouseDrag))
+            {
+                if (!rect.Contains(e.mousePosition)) return;
+                // 只在MouseDown或按住拖动时响应
+                int button = e.button; // 0左 1右
+                Vector2Int? cell = ScreenToCell(rect, e.mousePosition);
+                if (!cell.HasValue) return;
+                if (button == 0)
+                {
+                    PlaceSpriteAtCell(cell.Value, selectedLayer.sprite);
+                    e.Use();
+                }
+                else if (button == 1)
+                {
+                    RemoveSpriteAtCell(cell.Value, selectedLayer.sprite);
+                    e.Use();
+                }
+            }
+        }
+
+        /// <summary>
+        /// 将预览坐标转换为网格坐标
+        /// </summary>
+        private Vector2Int? ScreenToCell(Rect rect, Vector2 mousePos)
+        {
+            if (gridColumns <= 0 || gridRows <= 0) return null;
+            
+            Vector2 cellSizeCanvas = new Vector2(spriteSize.x + cellGap.x, spriteSize.y + cellGap.y);
+            float gridCanvasWidth = Mathf.Max(1, gridColumns) * cellSizeCanvas.x;
+            float gridCanvasHeight = Mathf.Max(1, gridRows) * cellSizeCanvas.y;
+            float drawCanvasWidth = Mathf.Max(canvasSize.x, gridCanvasWidth);
+            float drawCanvasHeight = Mathf.Max(canvasSize.y, gridCanvasHeight);
+            float scaleX = rect.width / drawCanvasWidth;
+            float scaleY = rect.height / drawCanvasHeight;
+            
+            // 计算Canvas在预览区域中的位置(居中显示)
+            float canvasOffsetX = (rect.width - drawCanvasWidth * scaleX) / 2f;
+            float canvasOffsetY = (rect.height - drawCanvasHeight * scaleY) / 2f;
+            
+            float cellWidthCanvas = cellSizeCanvas.x;
+            float cellHeightCanvas = cellSizeCanvas.y;
+            
+            // 转换为Canvas坐标系
+            float localXCanvas = (mousePos.x - rect.x - canvasOffsetX) / scaleX;
+            float localYCanvas = (mousePos.y - rect.y - canvasOffsetY) / scaleY;
+            
+            int col = Mathf.FloorToInt(localXCanvas / cellWidthCanvas);
+            int row = Mathf.FloorToInt(localYCanvas / cellHeightCanvas);
+            col = Mathf.Clamp(col, 0, gridColumns - 1);
+            row = Mathf.Clamp(row, 0, gridRows - 1);
+            return new Vector2Int(col, row);
+        }
+
+        /// <summary>
+        /// 在Canvas上指定单元放置一个Image
+        /// </summary>
+        private void PlaceSpriteAtCell(Vector2Int cell, Sprite sprite)
+        {
+            if (sprite == null) return;
+            
+            if (!spriteLayers.ContainsKey(sprite))
+            {
+                spriteLayers[sprite] = new System.Collections.Generic.Dictionary<Vector2Int, GameObject>();
+                layerCanvasPositions[sprite] = new System.Collections.Generic.Dictionary<Vector2Int, Vector2>();
+            }
+            if (spriteLayers[sprite].ContainsKey(cell)) return; // 已存在
+
+            GameObject parent = GetOrCreateLayerParent(sprite);
+            GameObject go = new GameObject($"Cell_{cell.x}_{cell.y}");
+            go.transform.SetParent(parent.transform, false);
+            var img = go.AddComponent<UnityEngine.UI.Image>();
+            img.sprite = sprite;
+
+            RectTransform rectTransform = go.GetComponent<RectTransform>();
+            rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
+            rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
+            
+            // 使用该Sprite的原始尺寸
+            Vector2 spriteSize = new Vector2(sprite.rect.width, sprite.rect.height);
+            rectTransform.sizeDelta = spriteSize;
+
+            // 计算该单元在Canvas中的锚定位置(以Canvas中心为原点,右为+X,上为+Y)
+            // 使用当前选中图层的网格尺寸来计算位置
+            float cellWCanvas = this.spriteSize.x + cellGap.x;
+            float cellHCanvas = this.spriteSize.y + cellGap.y;
+            float x = -canvasSize.x / 2f + cell.x * cellWCanvas + this.spriteSize.x / 2f;
+            float y = canvasSize.y / 2f - cell.y * cellHCanvas - this.spriteSize.y / 2f;
+            
+            // 记录Canvas绝对位置
+            Vector2 canvasPosition = new Vector2(x, y);
+            layerCanvasPositions[sprite][cell] = canvasPosition;
+            rectTransform.anchoredPosition = canvasPosition;
+
+            spriteLayers[sprite][cell] = go;
+            Repaint();
+        }
+
+        /// <summary>
+        /// 删除指定单元的Image
+        /// </summary>
+        private void RemoveSpriteAtCell(Vector2Int cell, Sprite sprite)
+        {
+            if (sprite == null || !spriteLayers.ContainsKey(sprite)) return;
+            if (!spriteLayers[sprite].ContainsKey(cell)) return;
+            var go = spriteLayers[sprite][cell];
+            if (go != null)
+            {
+                GameObject.DestroyImmediate(go);
+            }
+            spriteLayers[sprite].Remove(cell);
+            
+            // 清理位置记录
+            if (layerCanvasPositions.ContainsKey(sprite) && layerCanvasPositions[sprite].ContainsKey(cell))
+            {
+                layerCanvasPositions[sprite].Remove(cell);
+            }
+            
+            Repaint();
+        }
+
+        /// <summary>
+        /// 按当前Sprite获取或创建一个层级父节点
+        /// </summary>
+        private GameObject GetOrCreateLayerParent(Sprite sprite)
+        {
+            string parentName = $"GridLayer_{sprite.name}";
+            Transform existing = targetCanvas.transform.Find(parentName);
+            if (existing != null) return existing.gameObject;
+            GameObject parent = new GameObject(parentName);
+            parent.transform.SetParent(targetCanvas.transform, false);
+            return parent;
+        }
+
+        /// <summary>
+        /// 绘制帮助框
+        /// </summary>
+        private void DrawHelpBox()
+        {
+            EditorGUILayout.BeginVertical("box");
+            EditorGUILayout.HelpBox(
+                "请先完成以下设置:\n\n" +
+                "1. 选择一个Canvas(用于确定画布大小)\n" +
+                "2. 添加图层并选择Sprite图片(用于确定网格单元大小)\n\n" +
+                "编辑器会自动计算网格数量并铺满Canvas",
+                MessageType.Info
+            );
+            EditorGUILayout.EndVertical();
+        }
+
+        /// <summary>
+        /// 更新Canvas大小
+        /// </summary>
+        private void UpdateCanvasSize()
+        {
+            if (targetCanvas == null)
+            {
+                canvasSize = Vector2.zero;
+                return;
+            }
+            
+            RectTransform rectTransform = targetCanvas.GetComponent<RectTransform>();
+            if (rectTransform != null)
+            {
+                canvasSize = rectTransform.sizeDelta;
+            }
+            else
+            {
+                canvasSize = new Vector2(800, 600); // 默认值
+            }
+        }
+
+        /// <summary>
+        /// 更新Sprite大小
+        /// </summary>
+        private void UpdateSpriteSize()
+        {
+            if (currentSprite == null)
+            {
+                spriteSize = Vector2.zero;
+                return;
+            }
+            
+            // 使用Sprite的像素大小
+            spriteSize = new Vector2(currentSprite.rect.width, currentSprite.rect.height);
+        }
+
+        /// <summary>
+        /// 计算网格
+        /// </summary>
+        private void CalculateGrid()
+        {
+            if (targetCanvas == null || currentSprite == null)
+            {
+                gridColumns = 0;
+                gridRows = 0;
+                return;
+            }
+            
+            if (spriteSize.x <= 0 || spriteSize.y <= 0)
+            {
+                gridColumns = 0;
+                gridRows = 0;
+                return;
+            }
+            
+            // 计算需要多少个单元以覆盖至少Canvas尺寸(向上取整)
+            float cellW = spriteSize.x + cellGap.x;
+            float cellH = spriteSize.y + cellGap.y;
+            gridColumns = Mathf.Max(1, Mathf.CeilToInt(canvasSize.x / Mathf.Max(1f, cellW)));
+            gridRows = Mathf.Max(1, Mathf.CeilToInt(canvasSize.y / Mathf.Max(1f, cellH)));
+            
+            Repaint();
+        }
+
+        /// <summary>
+        /// 检查是否有放置的Sprite
+        /// </summary>
+        private bool HasPlacedSprites()
+        {
+            foreach (var layer in spriteLayers.Values)
+            {
+                if (layer.Count > 0) return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// 保存为纹理贴图
+        /// </summary>
+        private void SaveAsTexture()
+        {
+            if (targetCanvas == null || currentSprite == null) return;
+
+            // 直接使用Canvas的实际尺寸作为纹理尺寸,不进行任何缩放
+            int textureWidth = Mathf.RoundToInt(canvasSize.x);
+            int textureHeight = Mathf.RoundToInt(canvasSize.y);
+
+            // Canvas坐标系:中心为原点,向右为+X,向上为+Y
+            // 纹理坐标系:左下角为原点,向右为+X,向上为+Y
+            float minX = -canvasSize.x / 2f;
+            float minY = -canvasSize.y / 2f;
+            float maxX = canvasSize.x / 2f;
+            float maxY = canvasSize.y / 2f;
+
+            // 收集所有已放置的Sprite信息(按图层顺序)
+            var entries = new System.Collections.Generic.List<(Sprite sprite, Texture2D tex, Rect spriteRect, Vector2 size, Vector2 center)>();
+
+            // 按图层列表的顺序收集Sprite(确保渲染顺序正确)
+            foreach (var layerInfo in spriteLayersList)
+            {
+                if (!layerInfo.isVisible || layerInfo.sprite == null) continue;
+                
+                if (spriteLayers.ContainsKey(layerInfo.sprite))
+                {
+                    var dict = spriteLayers[layerInfo.sprite];
+                foreach (var kvp in dict)
+                {
+                    GameObject go = kvp.Value;
+                    if (go == null) continue;
+                    var rt = go.GetComponent<RectTransform>();
+                    var img = go.GetComponent<UnityEngine.UI.Image>();
+                    if (rt == null || img == null || img.sprite == null) continue;
+
+                    Vector2 size = rt.sizeDelta;               // 实际显示尺寸(像素)
+                    Vector2 center = rt.anchoredPosition;       // 以Canvas中心为原点
+
+                    var sp = img.sprite;
+                    entries.Add((sp, sp.texture, sp.rect, size, center));
+                }
+            }
+            }
+
+            // 创建纹理
+            Texture2D finalTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.RGBA32, false);
+            // 填充透明背景(确保整个Canvas范围都是透明的)
+            Color[] pixels = new Color[textureWidth * textureHeight];
+            for (int i = 0; i < pixels.Length; i++) pixels[i] = Color.clear;
+            finalTexture.SetPixels(pixels);
+
+            // 2) 绘制每个已放置的Sprite元素:只渲染在Canvas范围内的部分
+            foreach (var e in entries)
+            {
+                // 计算Sprite在Canvas坐标系中的边界
+                float spriteLeft = e.center.x - e.size.x / 2f;
+                float spriteRight = e.center.x + e.size.x / 2f;
+                float spriteBottom = e.center.y - e.size.y / 2f;
+                float spriteTop = e.center.y + e.size.y / 2f;
+
+                // 检查Sprite是否与Canvas范围有交集
+                if (spriteRight <= minX || spriteLeft >= maxX || spriteTop <= minY || spriteBottom >= maxY)
+                {
+                    continue; // 完全超出Canvas范围,跳过
+                }
+
+                // 计算在Canvas范围内的裁剪区域
+                float clipLeft = Mathf.Max(spriteLeft, minX);
+                float clipRight = Mathf.Min(spriteRight, maxX);
+                float clipBottom = Mathf.Max(spriteBottom, minY);
+                float clipTop = Mathf.Min(spriteTop, maxY);
+
+                // 转换为纹理坐标(左下角为原点)
+                int startX = Mathf.RoundToInt(clipLeft - minX);
+                int startY = Mathf.RoundToInt(clipBottom - minY);
+                int endX = Mathf.RoundToInt(clipRight - minX);
+                int endY = Mathf.RoundToInt(clipTop - minY);
+
+                // 计算源纹理的UV偏移(由于裁剪)
+                float uvOffsetX = (clipLeft - spriteLeft) / e.size.x;
+                float uvOffsetY = (clipBottom - spriteBottom) / e.size.y;
+                float uvScaleX = (clipRight - clipLeft) / e.size.x;
+                float uvScaleY = (clipTop - clipBottom) / e.size.y;
+
+                // 源UV
+                Rect spriteRect = e.spriteRect;
+                Texture2D spriteTexture = e.tex;
+                Rect baseUv = new Rect(
+                    spriteRect.x / spriteTexture.width,
+                    spriteRect.y / spriteTexture.height,
+                    spriteRect.width / spriteTexture.width,
+                    spriteRect.height / spriteTexture.height
+                );
+
+                // 应用裁剪后的UV
+                Rect uv = new Rect(
+                    baseUv.x + uvOffsetX * baseUv.width,
+                    baseUv.y + uvOffsetY * baseUv.height,
+                    baseUv.width * uvScaleX,
+                    baseUv.height * uvScaleY
+                );
+
+                // 确保坐标在有效范围内
+                startX = Mathf.Clamp(startX, 0, textureWidth - 1);
+                startY = Mathf.Clamp(startY, 0, textureHeight - 1);
+                endX = Mathf.Clamp(endX, 0, textureWidth);
+                endY = Mathf.Clamp(endY, 0, textureHeight);
+
+                for (int y = startY; y < endY; y++)
+                {
+                    for (int x = startX; x < endX; x++)
+                    {
+                        float u = (x - startX) / (float)(endX - startX) * uv.width + uv.x;
+                        float v = (y - startY) / (float)(endY - startY) * uv.height + uv.y;
+
+                        int sx = Mathf.Clamp(Mathf.RoundToInt(u * spriteTexture.width), 0, spriteTexture.width - 1);
+                        int sy = Mathf.Clamp(Mathf.RoundToInt(v * spriteTexture.height), 0, spriteTexture.height - 1);
+                        Color pc = spriteTexture.GetPixel(sx, sy);
+
+                        int fi = y * textureWidth + x;
+                        if (pc.a > 0)
+                        {
+                            pixels[fi] = Color.Lerp(pixels[fi], pc, pc.a);
+                        }
+                    }
+                }
+            }
+
+            // 应用像素到纹理
+            finalTexture.SetPixels(pixels);
+            finalTexture.Apply();
+
+             // 保存纹理文件
+             string fileName = $"GridMap_{System.DateTime.Now:yyyyMMdd_HHmmss}.png";
+             string filePath = EditorUtility.SaveFilePanel("保存网格地图纹理", "Assets", fileName, "png");
+             
+             if (!string.IsNullOrEmpty(filePath))
+             {
+                 byte[] pngData = finalTexture.EncodeToPNG();
+                 System.IO.File.WriteAllBytes(filePath, pngData);
+                 
+                 // 刷新资源数据库
+                 AssetDatabase.Refresh();
+                 
+                 // 自动清理生成的GameObject和图层数据
+                 CleanupAllData();
+                 
+                 EditorUtility.DisplayDialog("保存成功", $"网格地图已保存为纹理贴图:\n{filePath}\n\n纹理尺寸:{textureWidth}x{textureHeight}\n(与Canvas尺寸完全一致,超出部分不渲染)\n\n已自动清理生成的GameObject和图层数据", "确定");
+             }
+
+            // 清理临时纹理
+            if (finalTexture != null)
+            {
+                DestroyImmediate(finalTexture);
+            }
+        }
+
+        /// <summary>
+        /// 添加新图层
+        /// </summary>
+        public void AddLayer(Sprite sprite)
+        {
+            if (sprite == null) return;
+            
+            var newLayer = new SpriteLayer(sprite);
+            spriteLayersList.Add(newLayer);
+            selectedLayerIndex = spriteLayersList.Count - 1;
+            currentSprite = sprite;
+            UpdateSpriteSize();
+            CalculateGrid();
+            
+            // 更新GameObject的渲染顺序
+            UpdateGameObjectRenderOrder();
+            
+            Repaint();
+        }
+        
+        /// <summary>
+        /// 删除图层
+        /// </summary>
+        private void RemoveLayer(int index)
+        {
+            if (index < 0 || index >= spriteLayersList.Count) return;
+            
+            var layer = spriteLayersList[index];
+            
+            // 清理该图层的GameObject
+            if (targetCanvas != null)
+            {
+                string parentName = $"GridLayer_{layer.sprite.name}";
+                Transform existing = targetCanvas.transform.Find(parentName);
+                if (existing != null)
+                {
+                    DestroyImmediate(existing.gameObject);
+                }
+            }
+            
+            // 从列表中移除
+            spriteLayersList.RemoveAt(index);
+            
+            // 更新选中索引
+            if (selectedLayerIndex == index)
+            {
+                selectedLayerIndex = -1;
+                currentSprite = null;
+            }
+            else if (selectedLayerIndex > index)
+            {
+                selectedLayerIndex--;
+            }
+            
+            // 如果还有图层,选择第一个
+            if (spriteLayersList.Count > 0 && selectedLayerIndex == -1)
+            {
+                selectedLayerIndex = 0;
+                currentSprite = spriteLayersList[0].sprite;
+                UpdateSpriteSize();
+                CalculateGrid();
+            }
+            
+            Repaint();
+        }
+        
+        /// <summary>
+        /// 向上移动图层
+        /// </summary>
+        private void MoveLayerUp(int index)
+        {
+            if (index <= 0) return;
+            
+            var layer = spriteLayersList[index];
+            spriteLayersList.RemoveAt(index);
+            spriteLayersList.Insert(index - 1, layer);
+            
+            // 更新选中索引
+            if (selectedLayerIndex == index)
+            {
+                selectedLayerIndex = index - 1;
+            }
+            else if (selectedLayerIndex == index - 1)
+            {
+                selectedLayerIndex = index;
+            }
+            
+            // 更新GameObject的渲染顺序
+            UpdateGameObjectRenderOrder();
+            
+            Repaint();
+        }
+        
+        /// <summary>
+        /// 向下移动图层
+        /// </summary>
+        private void MoveLayerDown(int index)
+        {
+            if (index >= spriteLayersList.Count - 1) return;
+            
+            var layer = spriteLayersList[index];
+            spriteLayersList.RemoveAt(index);
+            spriteLayersList.Insert(index + 1, layer);
+            
+            // 更新选中索引
+            if (selectedLayerIndex == index)
+            {
+                selectedLayerIndex = index + 1;
+            }
+            else if (selectedLayerIndex == index + 1)
+            {
+                selectedLayerIndex = index;
+            }
+            
+            // 更新GameObject的渲染顺序
+            UpdateGameObjectRenderOrder();
+            
+            Repaint();
+        }
+        
+        /// <summary>
+        /// 更新GameObject的渲染顺序
+        /// </summary>
+        private void UpdateGameObjectRenderOrder()
+        {
+            if (targetCanvas == null) return;
+            
+            // 按照图层列表的顺序重新排列GameObject
+            for (int i = 0; i < spriteLayersList.Count; i++)
+            {
+                var layer = spriteLayersList[i];
+                if (layer.sprite == null) continue;
+                
+                string parentName = $"GridLayer_{layer.sprite.name}";
+                Transform layerParent = targetCanvas.transform.Find(parentName);
+                if (layerParent != null)
+                {
+                    // 设置SiblingIndex来控制渲染顺序
+                    layerParent.SetSiblingIndex(i);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 清空所有图层
+        /// </summary>
+        private void ClearAllLayers()
+        {
+            // 使用统一的清理方法
+            CleanupAllData();
+        }
+
+        /// <summary>
+        /// 清理所有数据(保存后自动清理)
+        /// </summary>
+        private void CleanupAllData()
+        {
+            // 清理生成的GameObject
+            CleanupGeneratedObjects();
+            
+            // 清理图层列表
+            spriteLayersList.Clear();
+            selectedLayerIndex = -1;
+            currentSprite = null;
+            spriteSize = Vector2.zero;
+            gridColumns = 0;
+            gridRows = 0;
+            
+            Repaint();
+        }
+
+        /// <summary>
+        /// 清理生成的GameObject
+        /// </summary>
+        private void CleanupGeneratedObjects()
+        {
+            if (targetCanvas == null) return;
+
+            // 删除所有以"GridLayer_"开头的子对象
+            for (int i = targetCanvas.transform.childCount - 1; i >= 0; i--)
+            {
+                Transform child = targetCanvas.transform.GetChild(i);
+                if (child.name.StartsWith("GridLayer_"))
+                {
+                    DestroyImmediate(child.gameObject);
+                }
+            }
+
+            // 清空数据
+            spriteLayers.Clear();
+            layerCanvasPositions.Clear();
+            Repaint();
+        }
+
+        // -------- 配置保存/加载 --------
+        private void SaveConfigToFile()
+        {
+            if (targetCanvas == null)
+            {
+                EditorUtility.DisplayDialog("提示", "请先选择Canvas后再保存配置", "确定");
+                return;
+            }
+
+            var cfg = BuildCurrentConfig();
+            string fileName = $"GridMapConfig_{System.DateTime.Now:yyyyMMdd_HHmmss}.json";
+            string path = EditorUtility.SaveFilePanel("保存配置", "Assets", fileName, "json");
+            if (string.IsNullOrEmpty(path)) return;
+
+            string json = JsonUtility.ToJson(cfg, true);
+            System.IO.File.WriteAllText(path, json, System.Text.Encoding.UTF8);
+            AssetDatabase.Refresh();
+
+            // 保存后自动清理
+            CleanupAllData();
+            EditorUtility.DisplayDialog("保存成功", "配置已保存并已自动清理现场。", "确定");
+        }
+
+        private MapConfig BuildCurrentConfig()
+        {
+            var cfg = new MapConfig
+            {
+                canvasName = targetCanvas != null ? targetCanvas.name : string.Empty,
+                canvasWidth = canvasSize.x,
+                canvasHeight = canvasSize.y
+            };
+
+            for (int i = 0; i < spriteLayersList.Count; i++)
+            {
+                var layer = spriteLayersList[i];
+                if (layer.sprite == null) continue;
+
+                var layerCfg = new LayerConfig
+                {
+                    name = layer.name,
+                    isVisible = layer.isVisible,
+                    isPreviewVisible = layer.isPreviewVisible,
+                    spriteAssetPath = AssetDatabase.GetAssetPath(layer.sprite),
+                    resourcesPath = GetResourcesPath(layer.sprite)
+                };
+
+                if (spriteLayers.ContainsKey(layer.sprite))
+                {
+                    var dict = spriteLayers[layer.sprite];
+                    foreach (var kv in dict)
+                    {
+                        Vector2Int cell = kv.Key;
+                        Vector2 pos;
+                        if (layerCanvasPositions.ContainsKey(layer.sprite) && layerCanvasPositions[layer.sprite].TryGetValue(cell, out pos))
+                        {
+                            layerCfg.items.Add(new ItemConfig
+                            {
+                                cellX = cell.x,
+                                cellY = cell.y,
+                                posX = pos.x,
+                                posY = pos.y
+                            });
+                        }
+                    }
+                }
+                cfg.layers.Add(layerCfg);
+            }
+
+            return cfg;
+        }
+
+        private void LoadConfigFromFile()
+        {
+            string path = EditorUtility.OpenFilePanel("加载配置", "Assets", "json");
+            if (string.IsNullOrEmpty(path)) return;
+
+            string json = System.IO.File.ReadAllText(path, System.Text.Encoding.UTF8);
+            LoadConfigFromJson(json);
+        }
+
+        // 可在运行时调用
+        public void LoadConfigFromJson(string json)
+        {
+            if (string.IsNullOrEmpty(json)) return;
+            var cfg = JsonUtility.FromJson<MapConfig>(json);
+            if (cfg == null) return;
+
+            // 清空现有
+            ClearAllLayers();
+
+            // 恢复Canvas尺寸(仅编辑器显示用)
+            canvasSize = new Vector2(cfg.canvasWidth, cfg.canvasHeight);
+
+            // 重建图层
+            foreach (var layerCfg in cfg.layers)
+            {
+                Sprite sp = null;
+                // 优先从Asset路径加载(编辑器)
+                if (!string.IsNullOrEmpty(layerCfg.spriteAssetPath))
+                {
+                    sp = AssetDatabase.LoadAssetAtPath<Sprite>(layerCfg.spriteAssetPath);
+                }
+                // 其次尝试Resources路径(运行时)
+                if (sp == null && !string.IsNullOrEmpty(layerCfg.resourcesPath))
+                {
+                    sp = Resources.Load<Sprite>(layerCfg.resourcesPath);
+                }
+                if (sp == null) continue;
+
+                AddLayer(sp);
+                var layer = spriteLayersList[spriteLayersList.Count - 1];
+                layer.name = string.IsNullOrEmpty(layerCfg.name) ? sp.name : layerCfg.name;
+                layer.isVisible = layerCfg.isVisible;
+                layer.isPreviewVisible = layerCfg.isPreviewVisible;
+
+                // 以当前选中图层的网格基准进行单元计算,但位置使用记录的Canvas位置
+                foreach (var item in layerCfg.items)
+                {
+                    Vector2Int cell = new Vector2Int(item.cellX, item.cellY);
+                    if (!spriteLayers.ContainsKey(sp))
+                    {
+                        spriteLayers[sp] = new System.Collections.Generic.Dictionary<Vector2Int, GameObject>();
+                    }
+                    if (!layerCanvasPositions.ContainsKey(sp))
+                    {
+                        layerCanvasPositions[sp] = new System.Collections.Generic.Dictionary<Vector2Int, Vector2>();
+                    }
+
+                    // 创建GO
+                    GameObject parent = GetOrCreateLayerParent(sp);
+                    GameObject go = new GameObject($"Cell_{cell.x}_{cell.y}");
+                    go.transform.SetParent(parent.transform, false);
+                    var img = go.AddComponent<UnityEngine.UI.Image>();
+                    img.sprite = sp;
+
+                    var rt = go.GetComponent<RectTransform>();
+                    rt.anchorMin = new Vector2(0.5f, 0.5f);
+                    rt.anchorMax = new Vector2(0.5f, 0.5f);
+                    rt.sizeDelta = new Vector2(sp.rect.width, sp.rect.height);
+                    rt.anchoredPosition = new Vector2(item.posX, item.posY);
+
+                    spriteLayers[sp][cell] = go;
+                    layerCanvasPositions[sp][cell] = new Vector2(item.posX, item.posY);
+                }
+            }
+
+            // 刷新
+            UpdateSpriteSize();
+            CalculateGrid();
+            UpdateGameObjectRenderOrder();
+            Repaint();
+        }
+
+        private static string GetResourcesPath(Sprite sprite)
+        {
+            string assetPath = AssetDatabase.GetAssetPath(sprite);
+            if (string.IsNullOrEmpty(assetPath)) return string.Empty;
+            const string key = "/Resources/";
+            int idx = assetPath.LastIndexOf(key, System.StringComparison.OrdinalIgnoreCase);
+            if (idx < 0) return string.Empty;
+            string rel = assetPath.Substring(idx + key.Length);
+            int dot = rel.LastIndexOf('.');
+            if (dot >= 0) rel = rel.Substring(0, dot);
+            return rel.Replace('\\', '/');
+        }
+    }
+}
+

BIN
笔记文件/附件/Pasted image 20251017164511.png


BIN
笔记文件/附件/Pasted image 20251017164542.png


BIN
笔记文件/附件/Pasted image 20251017164704.png


BIN
笔记文件/附件/Pasted image 20251017164919.png


BIN
笔记文件/附件/Pasted image 20251017165421.png


BIN
笔记文件/附件/Pasted image 20251017171737.png


BIN
笔记文件/附件/Pasted image 20251017180740.png


BIN
笔记文件/附件/Pasted image 20251017180853.png


BIN
笔记文件/附件/slotFunction.zip