Files
KopMap/Assets/Scripts/Editor/MapScreenshotTool.cs

537 lines
18 KiB
C#
Raw Normal View History

2025-09-02 18:55:19 +08:00
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;
}
}
}