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('\\', '/');
}
}
}