NLDClient-yudde/ProjectNLD/Assets/Editor/MapEditor/MapManagerInspector.cs

644 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<TerrainGridSystem>();
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<NLDSceneManager>();
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();
}
/// <summary>
/// 加载地图后应用存储的相机初始位置与FOV
/// </summary>
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<TextAsset>(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<Camera>().orthographicSize = Target.CameraController.CameraMaxSize;
if (_uiLevelCheck == null)
{
var ui = AssetDatabase.LoadAssetAtPath<GameObject>(UICanvasCheck);
_uiLevelCheck = Instantiate(ui);
var uiCamera = _uiLevelCheck.transform.Find("UICamera").GetComponent<Camera>();
var baseCameraData = Camera.main?.GetComponent<UniversalAdditionalCameraData>();
baseCameraData?.cameraStack.Add(uiCamera);
}
}
if (_isCheckingCamera && GUILayout.Button("退出相机检查"))
{
if (_uiLevelCheck)
DestroyImmediate(_uiLevelCheck);
_isCheckingCamera = false;
Target.CameraController.GetComponent<Camera>().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<Camera>();
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<Texture2D>(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
}