using UnityEngine; using UnityEditor; namespace MapEditor { /// /// 网格地图编辑器 - 全新实现 /// 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 spriteLayersList = new System.Collections.Generic.List(); 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 layers = new System.Collections.Generic.List(); } [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 items = new System.Collections.Generic.List(); } [System.Serializable] private class ItemConfig { public int cellX; public int cellY; public float posX; // Canvas坐标(以中心为原点) public float posY; } // 放置的数据:按Sprite区分的"图层" private System.Collections.Generic.Dictionary> spriteLayers = new System.Collections.Generic.Dictionary>(); // 每个图层的Canvas绝对位置记录 private System.Collections.Generic.Dictionary> layerCanvasPositions = new System.Collections.Generic.Dictionary>(); // 单元格间距(Canvas单位,像素),默认0 private Vector2 cellGap = Vector2.zero; [MenuItem("工具/网格地图编辑器")] public static void ShowWindow() { GridMapEditor window = GetWindow("网格地图编辑器"); window.minSize = new Vector2(600, 600); } private void OnGUI() { DrawToolbar(); EditorGUILayout.Space(10); if (targetCanvas != null && spriteLayersList.Count > 0) { DrawPreviewArea(); } else { DrawHelpBox(); } } /// /// 绘制工具栏 /// 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(); } /// /// 绘制图层管理UI /// 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(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(); } /// /// 绘制单个图层项 /// 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); } /// /// 处理图层拖拽(简化版本) /// private void HandleLayerDrag(int index) { // 暂时禁用拖拽功能,避免卡顿 // 可以通过上下箭头按钮来调整顺序 } /// /// 绘制预览区域 /// 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(); } /// /// 绘制网格 /// 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(); } /// /// 在网格中绘制Sprite /// 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; } } /// /// 绘制单个图层的所有Sprite(使用记录的Canvas绝对位置) /// 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); } } /// /// 绘制单个图层的所有Sprite(旧方法,保持兼容) /// 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); } } /// /// 处理预览区域内的鼠标事件(左键放置,右键删除) /// 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(); } } } /// /// 将预览坐标转换为网格坐标 /// 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); } /// /// 在Canvas上指定单元放置一个Image /// private void PlaceSpriteAtCell(Vector2Int cell, Sprite sprite) { if (sprite == null) return; if (!spriteLayers.ContainsKey(sprite)) { spriteLayers[sprite] = new System.Collections.Generic.Dictionary(); layerCanvasPositions[sprite] = new System.Collections.Generic.Dictionary(); } 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(); img.sprite = sprite; RectTransform rectTransform = go.GetComponent(); 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(); } /// /// 删除指定单元的Image /// 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(); } /// /// 按当前Sprite获取或创建一个层级父节点 /// 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; } /// /// 绘制帮助框 /// private void DrawHelpBox() { EditorGUILayout.BeginVertical("box"); EditorGUILayout.HelpBox( "请先完成以下设置:\n\n" + "1. 选择一个Canvas(用于确定画布大小)\n" + "2. 添加图层并选择Sprite图片(用于确定网格单元大小)\n\n" + "编辑器会自动计算网格数量并铺满Canvas", MessageType.Info ); EditorGUILayout.EndVertical(); } /// /// 更新Canvas大小 /// private void UpdateCanvasSize() { if (targetCanvas == null) { canvasSize = Vector2.zero; return; } RectTransform rectTransform = targetCanvas.GetComponent(); if (rectTransform != null) { canvasSize = rectTransform.sizeDelta; } else { canvasSize = new Vector2(800, 600); // 默认值 } } /// /// 更新Sprite大小 /// private void UpdateSpriteSize() { if (currentSprite == null) { spriteSize = Vector2.zero; return; } // 使用Sprite的像素大小 spriteSize = new Vector2(currentSprite.rect.width, currentSprite.rect.height); } /// /// 计算网格 /// 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(); } /// /// 检查是否有放置的Sprite /// private bool HasPlacedSprites() { foreach (var layer in spriteLayers.Values) { if (layer.Count > 0) return true; } return false; } /// /// 保存为纹理贴图 /// 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(); var img = go.GetComponent(); 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); } } /// /// 添加新图层 /// 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(); } /// /// 删除图层 /// 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(); } /// /// 向上移动图层 /// 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(); } /// /// 向下移动图层 /// 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(); } /// /// 更新GameObject的渲染顺序 /// 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); } } } /// /// 清空所有图层 /// private void ClearAllLayers() { // 使用统一的清理方法 CleanupAllData(); } /// /// 清理所有数据(保存后自动清理) /// private void CleanupAllData() { // 清理生成的GameObject CleanupGeneratedObjects(); // 清理图层列表 spriteLayersList.Clear(); selectedLayerIndex = -1; currentSprite = null; spriteSize = Vector2.zero; gridColumns = 0; gridRows = 0; Repaint(); } /// /// 清理生成的GameObject /// 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(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(layerCfg.spriteAssetPath); } // 其次尝试Resources路径(运行时) if (sp == null && !string.IsNullOrEmpty(layerCfg.resourcesPath)) { sp = Resources.Load(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(); } if (!layerCanvasPositions.ContainsKey(sp)) { layerCanvasPositions[sp] = new System.Collections.Generic.Dictionary(); } // 创建GO GameObject parent = GetOrCreateLayerParent(sp); GameObject go = new GameObject($"Cell_{cell.x}_{cell.y}"); go.transform.SetParent(parent.transform, false); var img = go.AddComponent(); img.sprite = sp; var rt = go.GetComponent(); 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('\\', '/'); } } }