using UnityEngine; using UnityEditor; using System.IO; using System.Collections; using System.Collections.Generic; public class TiledMapScreenshotTool : EditorWindow { [Header("相机设置")] public Camera targetCamera; public LayerMask layerMask = -1; [Header("区域设置")] public Vector3 startPosition = new Vector3(-100, 0, -100); public Vector3 endPosition = new Vector3(100, 0, 100); public float cameraHeight = 100f; [Header("截图设置")] public int tileWidth = 2048; // 单个瓦片宽度 public int tileHeight = 2048; // 单个瓦片高度 public int antiAliasing = 4; // 抗锯齿 public float overlapPercent = 5f; // 重叠百分比,避免接缝 [Header("网格预览")] public bool showGridPreview = true; public Color gridColor = Color.yellow; [Header("输出设置")] public string fileName = "SuperMapScreenshot"; public string outputPath = "Assets/Screenshots/"; public bool saveIndividualTiles = false; // 是否保存单个瓦片 private Vector3 originalCameraPosition; private Quaternion originalCameraRotation; private bool originalOrthographic; private float originalOrthographicSize; private int originalCullingMask; private int gridWidth, gridHeight; private float tileWorldWidth, tileWorldHeight; private Vector2 totalWorldSize; private int finalImageWidth, finalImageHeight; [MenuItem("Tools/分块地图截图工具")] public static void ShowWindow() { GetWindow("分块地图截图工具"); } void OnEnable() { SceneView.duringSceneGui += OnSceneGUI; CalculateGrid(); } void OnDisable() { SceneView.duringSceneGui -= OnSceneGUI; } void OnGUI() { GUILayout.Label("分块地图截图工具", EditorStyles.boldLabel); EditorGUILayout.Space(); // 相机设置 EditorGUILayout.LabelField("相机设置", EditorStyles.boldLabel); targetCamera = (Camera)EditorGUILayout.ObjectField("目标相机", targetCamera, typeof(Camera), true); if (targetCamera == null) { EditorGUILayout.HelpBox("请选择一个相机作为截图相机", MessageType.Warning); if (GUILayout.Button("使用场景相机")) { targetCamera = SceneView.lastActiveSceneView?.camera; } } layerMask = EditorGUILayout.MaskField("渲染层级", layerMask, UnityEditorInternal.InternalEditorUtility.layers); EditorGUILayout.Space(); // 区域设置 EditorGUILayout.LabelField("截图区域设置", EditorStyles.boldLabel); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("设为当前相机位置", GUILayout.Width(120))) { if (targetCamera != null) { Vector3 pos = targetCamera.transform.position; startPosition = new Vector3(pos.x - 50, startPosition.y, pos.z - 50); } } startPosition = EditorGUILayout.Vector3Field("开始位置 (XZ)", startPosition); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("设为当前相机位置", GUILayout.Width(120))) { if (targetCamera != null) { Vector3 pos = targetCamera.transform.position; endPosition = new Vector3(pos.x + 50, endPosition.y, pos.z + 50); } } endPosition = EditorGUILayout.Vector3Field("结束位置 (XZ)", endPosition); EditorGUILayout.EndHorizontal(); cameraHeight = EditorGUILayout.FloatField("相机高度", cameraHeight); EditorGUILayout.Space(); // 截图设置 EditorGUILayout.LabelField("瓦片设置", EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); tileWidth = EditorGUILayout.IntField("瓦片宽度", tileWidth); tileHeight = EditorGUILayout.IntField("瓦片高度", tileHeight); antiAliasing = EditorGUILayout.IntPopup("抗锯齿", antiAliasing, new string[] {"无", "2x", "4x", "8x"}, new int[] {1, 2, 4, 8}); overlapPercent = EditorGUILayout.Slider("重叠百分比", overlapPercent, 0f, 20f); if (EditorGUI.EndChangeCheck()) { CalculateGrid(); } EditorGUILayout.Space(); // 网格预览 EditorGUILayout.LabelField("网格预览", EditorStyles.boldLabel); showGridPreview = EditorGUILayout.Toggle("显示网格预览", showGridPreview); if (showGridPreview) { gridColor = EditorGUILayout.ColorField("网格颜色", gridColor); } EditorGUILayout.Space(); // 计算信息显示 EditorGUILayout.LabelField("计算信息", EditorStyles.boldLabel); EditorGUILayout.LabelField($"世界区域大小: {totalWorldSize.x:F1} x {totalWorldSize.y:F1}"); EditorGUILayout.LabelField($"网格数量: {gridWidth} x {gridHeight} = {gridWidth * gridHeight} 张"); EditorGUILayout.LabelField($"最终图片尺寸: {finalImageWidth} x {finalImageHeight}"); EditorGUILayout.LabelField($"预估文件大小: ~{(finalImageWidth * finalImageHeight * 4 / 1024f / 1024f):F1} MB"); if (gridWidth * gridHeight > 100) { EditorGUILayout.HelpBox($"瓦片数量较多({gridWidth * gridHeight}张),截图可能需要较长时间", MessageType.Warning); } EditorGUILayout.Space(); // 输出设置 EditorGUILayout.LabelField("输出设置", EditorStyles.boldLabel); fileName = EditorGUILayout.TextField("文件名", fileName); EditorGUILayout.BeginHorizontal(); outputPath = EditorGUILayout.TextField("输出路径", outputPath); if (GUILayout.Button("选择", GUILayout.Width(50))) { string selectedPath = EditorUtility.OpenFolderPanel("选择输出文件夹", "Assets", ""); if (!string.IsNullOrEmpty(selectedPath)) { outputPath = "Assets" + selectedPath.Substring(Application.dataPath.Length) + "/"; } } EditorGUILayout.EndHorizontal(); saveIndividualTiles = EditorGUILayout.Toggle("保存单个瓦片", saveIndividualTiles); EditorGUILayout.Space(); // 操作按钮 GUI.enabled = targetCamera != null; if (GUILayout.Button("预览第一个瓦片位置", GUILayout.Height(25))) { PreviewTilePosition(0, 0); } if (GUILayout.Button("开始分块截图", GUILayout.Height(40))) { StartTiledScreenshot(); } GUI.enabled = true; } void CalculateGrid() { // 计算世界区域大小 totalWorldSize = new Vector2( Mathf.Abs(endPosition.x - startPosition.x), Mathf.Abs(endPosition.z - startPosition.z) ); // 计算每个瓦片对应的世界大小 float aspectRatio = (float)tileWidth / tileHeight; // 根据重叠百分比调整实际覆盖区域 float overlapFactor = 1f - (overlapPercent / 100f); // 估算合适的瓦片世界大小 tileWorldWidth = totalWorldSize.x / Mathf.Ceil(totalWorldSize.x / (totalWorldSize.x / 10f)); tileWorldHeight = tileWorldWidth / aspectRatio; // 计算网格数量 gridWidth = Mathf.CeilToInt(totalWorldSize.x / (tileWorldWidth * overlapFactor)); gridHeight = Mathf.CeilToInt(totalWorldSize.y / (tileWorldHeight * overlapFactor)); // 重新计算精确的瓦片世界大小 tileWorldWidth = totalWorldSize.x / gridWidth; tileWorldHeight = totalWorldSize.y / gridHeight; // 计算最终图片尺寸 finalImageWidth = gridWidth * tileWidth; finalImageHeight = gridHeight * tileHeight; // 重绘场景视图以更新网格预览 SceneView.RepaintAll(); } void OnSceneGUI(SceneView sceneView) { if (!showGridPreview) return; Handles.color = gridColor; // 绘制整体区域边界 Vector3 min = new Vector3(startPosition.x, cameraHeight, startPosition.z); Vector3 max = new Vector3(endPosition.x, cameraHeight, endPosition.z); // 绘制外边框 Handles.DrawLine(new Vector3(min.x, min.y, min.z), new Vector3(max.x, min.y, min.z)); Handles.DrawLine(new Vector3(max.x, min.y, min.z), new Vector3(max.x, min.y, max.z)); Handles.DrawLine(new Vector3(max.x, min.y, max.z), new Vector3(min.x, min.y, max.z)); Handles.DrawLine(new Vector3(min.x, min.y, max.z), new Vector3(min.x, min.y, min.z)); // 绘制网格线 for (int x = 1; x < gridWidth; x++) { float worldX = startPosition.x + (x * tileWorldWidth); Handles.DrawLine( new Vector3(worldX, cameraHeight, startPosition.z), new Vector3(worldX, cameraHeight, endPosition.z) ); } for (int z = 1; z < gridHeight; z++) { float worldZ = startPosition.z + (z * tileWorldHeight); Handles.DrawLine( new Vector3(startPosition.x, cameraHeight, worldZ), new Vector3(endPosition.x, cameraHeight, worldZ) ); } // 绘制瓦片中心点 Handles.color = Color.red; for (int x = 0; x < gridWidth; x++) { for (int z = 0; z < gridHeight; z++) { Vector3 tileCenter = GetTileWorldPosition(x, z); Handles.DrawWireCube(tileCenter, Vector3.one * 2f); Handles.Label(tileCenter + Vector3.up * 5f, $"{x},{z}"); } } } Vector3 GetTileWorldPosition(int gridX, int gridZ) { float worldX = startPosition.x + (gridX + 0.5f) * tileWorldWidth; float worldZ = startPosition.z + (gridZ + 0.5f) * tileWorldHeight; return new Vector3(worldX, cameraHeight, worldZ); } void PreviewTilePosition(int gridX, int gridZ) { if (targetCamera == null) return; Vector3 tileCenter = GetTileWorldPosition(gridX, gridZ); // 移动场景视图到瓦片位置 SceneView sceneView = SceneView.lastActiveSceneView; if (sceneView != null) { sceneView.pivot = tileCenter; sceneView.rotation = Quaternion.Euler(90, 0, 0); sceneView.size = Mathf.Max(tileWorldWidth, tileWorldHeight) / 2f; sceneView.orthographic = true; sceneView.Repaint(); } } void StartTiledScreenshot() { if (targetCamera == null) { EditorUtility.DisplayDialog("错误", "请先选择一个相机", "确定"); return; } // 确保输出目录存在 if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } EditorCoroutineUtility.StartCoroutine(TakeScreenshotCoroutine(), this); } IEnumerator TakeScreenshotCoroutine() { // 使用二维数组存储瓦片,保持空间位置关系 Texture2D[,] tileTextures = new Texture2D[gridWidth, gridHeight]; // try { // 保存原始相机设置 SaveOriginalCameraSettings(); // 设置相机为正交模式 targetCamera.orthographic = true; targetCamera.transform.rotation = Quaternion.Euler(90, 0, 0); targetCamera.cullingMask = layerMask; targetCamera.orthographicSize = Mathf.Max(tileWorldWidth, tileWorldHeight) / 2f; targetCamera.nearClipPlane = 0.1f; targetCamera.farClipPlane = cameraHeight + 50f; int totalTiles = gridWidth * gridHeight; int currentTile = 0; // 逐个拍摄瓦片 for (int z = 0; z < gridHeight; z++) { for (int x = 0; x < gridWidth; x++) { currentTile++; float progress = (float)currentTile / totalTiles; if (EditorUtility.DisplayCancelableProgressBar( "分块截图中", $"正在截图瓦片 {currentTile}/{totalTiles} ({x},{z})", progress)) { break; } // 移动相机到瓦片位置 Vector3 tilePosition = GetTileWorldPosition(x, z); targetCamera.transform.position = tilePosition; // 截图 Texture2D tileTexture = CaptureScreenshot(); if (tileTexture != null) { // 存储到对应位置 tileTextures[x, z] = tileTexture; // 如果需要保存单个瓦片 if (saveIndividualTiles) { byte[] bytes = tileTexture.EncodeToPNG(); string tilePath = Path.Combine(outputPath, $"{fileName}_tile_{x}_{z}.png"); File.WriteAllBytes(tilePath, bytes); } } yield return null; // 等待一帧 } } EditorUtility.DisplayProgressBar("合成图片", "正在合成最终图片...", 1f); // 合成最终图片 Texture2D finalTexture = CombineTiles(tileTextures); if (finalTexture != null) { // 保存最终图片 byte[] finalBytes = finalTexture.EncodeToPNG(); string finalPath = Path.Combine(outputPath, $"{fileName}_{finalImageWidth}x{finalImageHeight}.png"); File.WriteAllBytes(finalPath, finalBytes); DestroyImmediate(finalTexture); EditorUtility.DisplayDialog("成功", $"超大清晰地图已保存!\n" + $"文件: {finalPath}\n" + $"尺寸: {finalImageWidth}x{finalImageHeight}\n" + $"瓦片数: {totalTiles}", "确定"); } } // catch (System.Exception e) // { // EditorUtility.DisplayDialog("错误", $"截图失败: {e.Message}", "确定"); // Debug.LogError($"分块截图失败: {e}"); // } // finally { // 清理资源 for (int x = 0; x < gridWidth; x++) { for (int z = 0; z < gridHeight; z++) { if (tileTextures[x, z] != null) DestroyImmediate(tileTextures[x, z]); } } RestoreOriginalCameraSettings(); EditorUtility.ClearProgressBar(); AssetDatabase.Refresh(); } } Texture2D CaptureScreenshot() { // 创建渲染纹理 RenderTexture renderTexture = new RenderTexture(tileWidth, tileHeight, 24); renderTexture.antiAliasing = antiAliasing; renderTexture.Create(); // 渲染 RenderTexture originalTarget = targetCamera.targetTexture; targetCamera.targetTexture = renderTexture; targetCamera.Render(); // 读取像素 RenderTexture.active = renderTexture; Texture2D screenshot = new Texture2D(tileWidth, tileHeight, TextureFormat.RGB24, false); screenshot.ReadPixels(new Rect(0, 0, tileWidth, tileHeight), 0, 0); screenshot.Apply(); // 清理 RenderTexture.active = null; targetCamera.targetTexture = originalTarget; renderTexture.Release(); DestroyImmediate(renderTexture); return screenshot; } Texture2D CombineTiles(Texture2D[,] tiles) { Texture2D finalTexture = new Texture2D(finalImageWidth, finalImageHeight, TextureFormat.RGB24, false); // 关键修复:不翻转Z轴,保持原始顺序 for (int z = 0; z < gridHeight; z++) { for (int x = 0; x < gridWidth; x++) { Texture2D tile = tiles[x, z]; if (tile == null) continue; Color[] pixels = tile.GetPixels(); // 修复:直接映射,不进行Y轴翻转 // 世界坐标系的Z=0对应纹理的Y=0(底部) // 世界坐标系的Z=max对应纹理的Y=max(顶部) int startX = x * tileWidth; int startY = z * tileHeight; // 移除翻转:直接使用z finalTexture.SetPixels(startX, startY, tileWidth, tileHeight, pixels); } } finalTexture.Apply(); return finalTexture; } void SaveOriginalCameraSettings() { if (targetCamera == null) return; originalCameraPosition = targetCamera.transform.position; originalCameraRotation = targetCamera.transform.rotation; originalOrthographic = targetCamera.orthographic; originalOrthographicSize = targetCamera.orthographicSize; originalCullingMask = targetCamera.cullingMask; } void RestoreOriginalCameraSettings() { if (targetCamera == null) return; targetCamera.transform.position = originalCameraPosition; targetCamera.transform.rotation = originalCameraRotation; targetCamera.orthographic = originalOrthographic; targetCamera.orthographicSize = originalOrthographicSize; targetCamera.cullingMask = originalCullingMask; } void OnDestroy() { RestoreOriginalCameraSettings(); } } // 编辑器协程工具类 public static class EditorCoroutineUtility { public static EditorCoroutine StartCoroutine(IEnumerator routine, object owner) { return new EditorCoroutine(routine, owner); } } public class EditorCoroutine { private IEnumerator routine; private object owner; public EditorCoroutine(IEnumerator routine, object owner) { this.routine = routine; this.owner = owner; EditorApplication.update += Update; } void Update() { if (routine != null) { if (!routine.MoveNext()) { EditorApplication.update -= Update; } } else { EditorApplication.update -= Update; } } }