Files
KopMap/Assets/Scripts/Editor/MapScreenshotTool.cs
2025-09-02 18:55:19 +08:00

537 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<TiledMapScreenshotTool>("分块地图截图工具");
}
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;
}
}
}