using System.IO; using cfg.FightCfg; using Gameplay; using MapEditor; using Sirenix.OdinInspector.Editor; using TGS; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Rendering.Universal; using UnityEngine.SceneManagement; using Constants = Framework.Constants; using Object = UnityEngine.Object; [CustomEditor(typeof(MapManager))] public class MapManagerInspector : OdinEditor { private enum ECameraCheckCorner { None, LeftDown, RightDown, LeftUp, RightUp, } private readonly string[] checkCornerType = new[] { "无", "左下角", "右下角", "左上角", "右上角", }; private readonly string[] environmentType = new[] { "平原", "山地", "草原", "林地", "雪地", "泥地", "戈壁", "沙漠", "高原", "竞技场", "火车关", }; private MapManager Target => target as MapManager; private TerrainGridSystem TGS => Target.TGS; private MapData MapData => Target.MapData; private Map Map => Target.Map; private bool _isEditingGrid; private bool _isEditingCameraOriginPos; private bool _isCheckingCamera; private bool _isEditingCameraInfo; private ECameraCheckCorner _checkType; private const string LegionMapConfigPrefix = "LegionMapData_"; private const string MapConfigPrefix = "MapData_"; private const string UICanvasCheck = "Assets/Editor/MapEditor/LevelUICheck.prefab"; private GameObject _uiLevelCheck; //scene gui private bool _isHitGround; private Vector3 _mouseHitPosition; private Event _currEvent; private GameObject _ground; private RaycastHit[] _hits = new RaycastHit[1]; protected override void OnEnable() { base.OnEnable(); var allTgs = Object.FindObjectsOfType(); foreach (var tgs in allTgs) { if (tgs != TGS) { foreach (Transform child in tgs.transform) { child.gameObject.SetActive(false); } } } DeActiveScene(); ApplyInitialCameraPosition(); } private void DeActiveScene() { var sceneManagers = FindObjectsOfType(); foreach (var sceneManager in sceneManagers) { SceneVisibilityManager.instance.DisablePicking(sceneManager.Art.gameObject, true); } } protected override void OnDisable() { if (_uiLevelCheck) DestroyImmediate(_uiLevelCheck); if (GameObject.Find("LevelUICheck(Clone)")) DestroyImmediate(GameObject.Find("LevelUICheck(Clone)")); base.OnDisable(); Target.SaveMapData(); } /// /// 加载地图后应用存储的相机初始位置与FOV /// private void ApplyInitialCameraPosition() { if (Application.isPlaying) return; if (Target == null || Target.CameraController == null || Map == null || TGS == null) return; var camera = Target.CameraController; camera.CameraBounds = TGS.bounds; var initPos = Map.BlockPosition2WorldPosition(MapData.initialCameraPosition); camera.InitializeCameraPosition( initPos, MapData.cameraMarginInfo, camera.rightMargin, camera.leftMargin, camera.topMargin, camera.bottomMargin); } public override void OnInspectorGUI() { if (Application.isPlaying) return; base.OnInspectorGUI(); DrawCreateMapData(); DrawMapData(); EditorSceneManager.MarkSceneDirty(Target.gameObject.scene); } private void DrawCreateMapData() { if (GUILayout.Button("创建地图配置")) { var mapDataDir = Path.GetDirectoryName(Constants.MAP_CONFIG_FORMAT_PATH); var mapDataName = Target.isLegionMap ? $"{mapDataDir}/{LegionMapConfigPrefix + Target.gameObject.scene.name}.json" : $"{mapDataDir}/{MapConfigPrefix + Target.gameObject.scene.name}.json"; var dataPath = EditorUtil.GetAssetUniquePath(mapDataName, out var textName); using (var fileStream = File.Create(dataPath)) { } AssetDatabase.Refresh(); var textAsset = AssetDatabase.LoadAssetAtPath(dataPath); EditorGUIUtility.PingObject(textAsset); Target.MapDataJson = textAsset; } } private void DrawMapData() { var mapAsset = Target.MapDataJson; EditorGUI.BeginChangeCheck(); mapAsset = EditorGUILayout.ObjectField("地图配置", mapAsset, typeof(TextAsset), false) as TextAsset; if (EditorGUI.EndChangeCheck()) { Target.MapDataJson = mapAsset; ApplyInitialCameraPosition(); } if (Target.MapDataJson != null) { EditorGUI.BeginChangeCheck(); var column = EditorGUILayout.DelayedIntField("网格宽度", TGS.columnCount); var raw = EditorGUILayout.DelayedIntField("网格高度", TGS.rowCount); if (EditorGUI.EndChangeCheck()) { var width = column; var height = raw; MapData.Width = width; MapData.Height = height; TGS.columnCount = width; TGS.rowCount = height; Target.SaveMapData(); Target.Refresh(); } EditorGUI.BeginChangeCheck(); var gridOffset = EditorGUILayout.Vector2Field("网格偏移", new Vector2(TGS.transform.position.x, TGS.transform.position.z)); if (EditorGUI.EndChangeCheck()) { var transform = TGS.transform; transform.position = new Vector3(gridOffset.x, 0, gridOffset.y); MapData.position = transform.position; } MapData.environmentType = (ELevelTerrainType)EditorGUILayout.Popup("环境类型", (int)MapData.environmentType, environmentType); DrawCheckCamera(); DrawCameraOriginPosition(); if (GUILayout.Button("打开网格编辑器")) { MapGridWindow.OpenWindow(Target, Target.isLegionMap); } // DrawCameraInfo(); if (GUILayout.Button("保存")) { Target.SaveMapData(); } } } /*private void DrawCameraInfo() { _isEditingCameraInfo = EditorGUILayout.Toggle("编辑相机间距", _isEditingCameraInfo); if (_isEditingCameraInfo) { MapData.cameraMarginInfo.leftMarginInFight = EditorGUILayout.FloatField("战斗中相机左间距", MapData.cameraMarginInfo.leftMarginInFight); MapData.cameraMarginInfo.rightMarginInFight = EditorGUILayout.FloatField("战斗中相机右间距", MapData.cameraMarginInfo.rightMarginInFight); MapData.cameraMarginInfo.topMarginInFight = EditorGUILayout.FloatField("战斗中相机上间距", MapData.cameraMarginInfo.topMarginInFight); MapData.cameraMarginInfo.bottomMarginInFight = EditorGUILayout.FloatField("战斗中相机下间距", MapData.cameraMarginInfo.bottomMarginInFight); MapData.cameraMarginInfo.leftMarginInPre = EditorGUILayout.FloatField("准备前相机左间距", MapData.cameraMarginInfo.leftMarginInPre); MapData.cameraMarginInfo.rightMarginInPre = EditorGUILayout.FloatField("准备前相机右间距", MapData.cameraMarginInfo.rightMarginInPre); MapData.cameraMarginInfo.topMarginInInPre = EditorGUILayout.FloatField("准备前相机上间距", MapData.cameraMarginInfo.topMarginInInPre); MapData.cameraMarginInfo.bottomMarginInPre = EditorGUILayout.FloatField("准备前相机下间距", MapData.cameraMarginInfo.bottomMarginInPre); } }*/ private void DrawCheckCamera() { if (!_isCheckingCamera && GUILayout.Button("检查相机可否超出地图")) { _isCheckingCamera = true; Target.CameraController.GetComponent().orthographicSize = Target.CameraController.CameraMaxSize; if (_uiLevelCheck == null) { var ui = AssetDatabase.LoadAssetAtPath(UICanvasCheck); _uiLevelCheck = Instantiate(ui); var uiCamera = _uiLevelCheck.transform.Find("UICamera").GetComponent(); var baseCameraData = Camera.main?.GetComponent(); baseCameraData?.cameraStack.Add(uiCamera); } } if (_isCheckingCamera && GUILayout.Button("退出相机检查")) { if (_uiLevelCheck) DestroyImmediate(_uiLevelCheck); _isCheckingCamera = false; Target.CameraController.GetComponent().orthographicSize = Target.CameraController.CameraMinSize; } if (_isCheckingCamera) { EditorGUI.BeginChangeCheck(); _checkType = (ECameraCheckCorner)EditorGUILayout.Popup("检查角落", (int)_checkType, checkCornerType); if (EditorGUI.EndChangeCheck()) { switch (_checkType) { case ECameraCheckCorner.None: break; case ECameraCheckCorner.LeftDown: var leftDownPoint = new Vector2Int(0, 0); Target.CameraController.InitializeCameraPosition( Map.BlockPosition2WorldPosition(leftDownPoint)); break; case ECameraCheckCorner.RightDown: var rightDownPoint = new Vector2Int(0, TGS.columnCount - 1); Target.CameraController.InitializeCameraPosition( Map.BlockPosition2WorldPosition(rightDownPoint)); break; case ECameraCheckCorner.LeftUp: var leftUpPoint = new Vector2Int(TGS.rowCount - 1, 0); Target.CameraController.InitializeCameraPosition(Map.BlockPosition2WorldPosition(leftUpPoint)); break; case ECameraCheckCorner.RightUp: var rightUpPoint = new Vector2Int(TGS.rowCount - 1, TGS.columnCount - 1); Target.CameraController.InitializeCameraPosition(Map.BlockPosition2WorldPosition(rightUpPoint)); break; default: break; } } } } private void DrawCameraOriginPosition() { if (!_isEditingCameraOriginPos) { if (GUILayout.Button("编辑相机初始位置和大小")) { _isEditingCameraOriginPos = true; // 编辑模式下提前设置相机包围盒,避免 CameraBounds 为空导致红框不显示/不更新 if (Target?.CameraController != null && Target?.TGS != null) { Target.CameraController.CameraBounds = Target.TGS.bounds; } } } else { // 添加相机FOV和正交大小设置 EditorGUI.BeginChangeCheck(); MapData.cameraMarginInfo.initialFOV = EditorGUILayout.Slider("初始FOV", MapData.cameraMarginInfo.initialFOV, Target.CameraController.CameraMinFOV, Target.CameraController.CameraMaxFOV); // MapData.cameraMarginInfo.initialOrthographicSize = EditorGUILayout.Slider("初始正交大小", MapData.cameraMarginInfo.initialOrthographicSize, Target.CameraController.CameraMinSize, Target.CameraController.CameraMaxSize); if (EditorGUI.EndChangeCheck()) { // 实时预览FOV设置效果 var camera = Target.CameraController.MainCamera; if (camera != null) { if (camera.orthographic) { camera.orthographicSize = MapData.cameraMarginInfo.initialOrthographicSize; } else { Target.CameraController.SetCameraFOVSize(MapData.cameraMarginInfo.initialFOV); camera.fieldOfView = Mathf.Clamp(MapData.cameraMarginInfo.initialFOV, Target.CameraController.CameraMinFOV, Target.CameraController.CameraMaxFOV); // 强制刷新场景视图 SceneView.RepaintAll(); } } } EditorGUILayout.LabelField("请在场景中点击网格来选取相机初始位置,并在Game窗口中查看"); if (GUILayout.Button("退出编辑相机初始位置")) { _isEditingCameraOriginPos = false; } } } private void OnSceneGUI() { if (!_isEditingCameraOriginPos) return; ResetSceneGUIState(Event.current); DrawMouse(); HandleInput(); } private void ResetSceneGUIState(Event e) { _isHitGround = false; _mouseHitPosition = Vector3.zero; _currEvent = e; _ground = null; } private void DrawMouse() { var mousePosSS = _currEvent.mousePosition; var ray = HandleUtility.GUIPointToWorldRay(mousePosSS); var count = Physics.RaycastNonAlloc(ray, _hits, 1000, 1 << LayerMask.NameToLayer("Ground")); if (count > 0) { var hit = _hits[0]; _mouseHitPosition = hit.point; Handles.color = Color.yellow; Handles.DrawSolidDisc(_mouseHitPosition, Vector3.up, 1); _isHitGround = true; _ground = hit.collider.gameObject; } } private void HandleInput() { if (!_isHitGround) return; var controlID = GUIUtility.GetControlID(FocusType.Passive); var eventType = _currEvent.GetTypeForControl(controlID); switch (eventType) { case EventType.MouseDown: if (_currEvent.button == 0) { GUIUtility.hotControl = controlID; if (_isEditingCameraOriginPos) { var cell = TGS.CellGetAtPosition(_mouseHitPosition, true); if (cell == null) { Debug.LogError("点击点附件没有网格!设置相机初始位置失败!"); return; } MapData.initialCameraPosition = MapData.BlockIndex2Position(cell.index); // 确保相机包围盒使用当前地图的网格范围 if (Target?.CameraController != null) { Target.CameraController.CameraBounds = TGS.bounds; } Target.CameraController.InitializeCameraPosition( Map.BlockPosition2WorldPosition(MapData.initialCameraPosition)); } } break; case EventType.MouseUp: if (GUIUtility.hotControl == controlID && _currEvent.button == 0) { GUIUtility.hotControl = 0; _currEvent.Use(); } break; } } #region 网格参考图导出功能 private void CaptureGridImage() { var tgs = TGS; if (tgs == null) { Debug.LogError("无法找到TerrainGridSystem组件"); return; } // 获取网格尺寸信息 int columnCount = tgs.columnCount; int rowCount = tgs.rowCount; if (columnCount <= 0 || rowCount <= 0) { Debug.LogError("网格尺寸无效"); return; } try { EditorUtility.DisplayProgressBar("导出网格参考图", "初始化...", 0.1f); // 询问用户选择网格颜色 bool useHighContrast = EditorUtility.DisplayDialog("选择网格颜色", "请选择导出的网格线颜色:", "默认颜色", "高对比度(白底黑线)"); // 获取网格的实际世界空间边界 Bounds gridBounds = tgs.bounds; // 计算网格的实际世界空间大小(单位:Unity单位) float worldWidth = gridBounds.size.x; float worldHeight = gridBounds.size.z; // 注意z轴是Unity中的垂直方向 // 计算图片像素尺寸 = 世界空间大小 * 100 int textureWidth = Mathf.CeilToInt(worldWidth * 100); int textureHeight = Mathf.CeilToInt(worldHeight * 100); // 显示计算结果并确认 if (!EditorUtility.DisplayDialog("确认导出", $"网格真实尺寸: {worldWidth:F2}x{worldHeight:F2} 单位\n" + $"导出图片分辨率: {textureWidth}x{textureHeight} 像素\n\n" + "继续导出?", "导出", "取消")) { EditorUtility.ClearProgressBar(); return; } EditorUtility.DisplayProgressBar("导出网格参考图", "准备相机...", 0.2f); // 创建临时相机 GameObject tempCameraObj = new GameObject("TempGridCamera"); Camera tempCamera = tempCameraObj.AddComponent(); tempCamera.clearFlags = CameraClearFlags.SolidColor; tempCamera.backgroundColor = useHighContrast ? Color.white : new Color(1, 1, 1, 0); // 高对比度时使用白底 tempCamera.orthographic = true; // 将相机放置在网格正上方 tempCamera.transform.position = new Vector3( gridBounds.center.x, gridBounds.max.y + 10, // 确保相机在网格上方足够高 gridBounds.center.z ); tempCamera.transform.rotation = Quaternion.Euler(90, 0, 0); // 俯视 // 设置相机的视图大小,使其刚好包含整个网格 tempCamera.orthographicSize = worldHeight * 0.5f; // 正交相机尺寸是高度的一半 tempCamera.aspect = worldWidth / worldHeight; EditorUtility.DisplayProgressBar("导出网格参考图", "创建渲染纹理...", 0.3f); // 创建RenderTexture RenderTexture renderTexture = new RenderTexture(textureWidth, textureHeight, 24, RenderTextureFormat.ARGB32); renderTexture.antiAliasing = 1; renderTexture.filterMode = FilterMode.Point; // 避免模糊 renderTexture.Create(); // 保存原始状态 bool originalShowCells = tgs.showCells; bool originalShowTerritories = tgs.showTerritories; Color originalCellBorderColor = tgs.cellBorderColor; float originalCellBorderThickness = tgs.cellBorderThickness; try { EditorUtility.DisplayProgressBar("导出网格参考图", "设置网格渲染参数...", 0.4f); // 临时设置网格渲染参数 tgs.showCells = true; tgs.showTerritories = false; // 设置网格线颜色和宽度 if (useHighContrast) { tgs.cellBorderColor = Color.black; tgs.cellBorderThickness = Mathf.Max(2.0f, tgs.cellBorderThickness); // 确保线条足够粗 } EditorUtility.DisplayProgressBar("导出网格参考图", "渲染网格...", 0.6f); // 设置并渲染 RenderTexture originalRenderTexture = RenderTexture.active; tempCamera.targetTexture = renderTexture; RenderTexture.active = renderTexture; // 设置相机渲染遮罩,只渲染网格层 tempCamera.cullingMask = 1 << tgs.gameObject.layer; // 渲染场景到RenderTexture tempCamera.Render(); EditorUtility.DisplayProgressBar("导出网格参考图", "创建图片...", 0.7f); // 创建纹理并读取像素 Texture2D texture2D = new Texture2D(textureWidth, textureHeight, TextureFormat.RGBA32, false); texture2D.ReadPixels(new Rect(0, 0, textureWidth, textureHeight), 0, 0); texture2D.Apply(); EditorUtility.DisplayProgressBar("导出网格参考图", "保存图片...", 0.8f); // 保存为PNG byte[] bytes = texture2D.EncodeToPNG(); string fileName = $"GridMap_{textureWidth}x{textureHeight}_{System.DateTime.Now:yyyyMMdd_HHmmss}.png"; string path = EditorUtility.SaveFilePanel("保存网格图片", Application.dataPath, fileName, "png"); if (!string.IsNullOrEmpty(path)) { try { System.IO.File.WriteAllBytes(path, bytes); EditorUtility.DisplayProgressBar("导出网格参考图", "刷新资源...", 0.9f); AssetDatabase.Refresh(); // 如果路径在Assets文件夹内,获取相对路径并高亮显示导出的贴图 if (path.StartsWith(Application.dataPath)) { var assetPath = "Assets" + path.Substring(Application.dataPath.Length); Texture2D exportedTexture = AssetDatabase.LoadAssetAtPath(assetPath); if (exportedTexture != null) { EditorGUIUtility.PingObject(exportedTexture); Selection.activeObject = exportedTexture; } } // 显示成功信息和使用说明 EditorUtility.DisplayDialog("导出成功", $"网格图片已导出\n" + $"网格实际尺寸: {worldWidth:F2}x{worldHeight:F2} 单位\n" + $"导出图片分辨率: {textureWidth}x{textureHeight} 像素\n" + $"缩放比例: 100像素/单位\n" + $"路径: {path}\n\n" + "使用说明:\n" + "1. 在美术软件中直接使用此图片作为参考层\n" + "2. 绘制的美术资源与网格完全对齐即可\n" + "3. 导入到Unity后无需调整Scale,将自动匹配网格", "确定"); } catch (System.Exception e) { EditorUtility.DisplayDialog("导出失败", $"保存图片时发生错误: {e.Message}", "确定"); } } // 清理资源 RenderTexture.active = originalRenderTexture; tempCamera.targetTexture = null; Object.DestroyImmediate(texture2D); Object.DestroyImmediate(renderTexture); } finally { // 恢复原始状态 tgs.showCells = originalShowCells; tgs.showTerritories = originalShowTerritories; tgs.cellBorderColor = originalCellBorderColor; tgs.cellBorderThickness = originalCellBorderThickness; // 清理相机 Object.DestroyImmediate(tempCameraObj); } } catch (System.Exception e) { Debug.LogException(e); EditorUtility.DisplayDialog("导出失败", $"导出过程中发生错误: {e.Message}", "确定"); } finally { // 无论如何都清除进度条 EditorUtility.ClearProgressBar(); } } #endregion }