using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using Framework; using Framework.Utils; using Gameplay; using Gameplay.Level; using TGS; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using PhxhSDK.Phxh; using Object = UnityEngine.Object; public static class LevelEditorHelper { private const string LEVEL_TEMPLATE_PATH = "Assets/Scenes/Maps/Template/MapDefault/MapDefault.unity"; private const string TERRAIN_LEVEL_TEMPLATE_PATH = "Assets/Scenes/Maps/Template/MapDefault/TerrainMapDefault.unity"; private const string LEGION_LEVEL_TEMPLATE_PATH = "Assets/Scenes/Maps/Template/MapDefault/LegionMapDefault.unity"; private const string MAP_FOLDER = "Assets/Scenes/Maps"; [MenuItem("Assets/*Create/地图场景/创建标准地图")] private static void CreateLevel() { var currDir = EditorUtil.GetCurrentAssetDirectory().Replace("\\", "/"); var dirName = Path.GetFileName(currDir); var count = AssetDatabase.FindAssets("t:SceneAsset", new[] { currDir }).Length + 1; var savePath = EditorUtil.GetAssetUniquePath($"{currDir}/{dirName}_{count}.unity", out var assetName); AssetDatabase.CopyAsset(LEVEL_TEMPLATE_PATH, savePath); var newScene = AssetDatabase.LoadAssetAtPath(savePath); EditorGUIUtility.PingObject(newScene); } [MenuItem("Assets/*Create/地图场景/创建标准地图(新)")] private static void CreateTerrainLevel() { var currDir = EditorUtil.GetCurrentAssetDirectory().Replace("\\", "/"); var dirName = Path.GetFileName(currDir); var count = AssetDatabase.FindAssets("t:SceneAsset", new[] { currDir }).Length + 1; var savePath = EditorUtil.GetAssetUniquePath($"{currDir}/{dirName}_{count}.unity", out var assetName); AssetDatabase.CopyAsset(TERRAIN_LEVEL_TEMPLATE_PATH, savePath); var newScene = AssetDatabase.LoadAssetAtPath(savePath); EditorGUIUtility.PingObject(newScene); } [MenuItem("Assets/*Create/地图场景/创建军团地图")] private static void CreateLegionLevel() { var currDir = EditorUtil.GetCurrentAssetDirectory().Replace("\\", "/"); var dirName = Path.GetFileName(currDir); var count = AssetDatabase.FindAssets("t:SceneAsset", new[] { currDir }).Length + 1; var savePath = EditorUtil.GetAssetUniquePath($"{currDir}/{dirName}_Legion_{count}.unity", out var assetName); AssetDatabase.CopyAsset(LEGION_LEVEL_TEMPLATE_PATH, savePath); var newScene = AssetDatabase.LoadAssetAtPath(savePath); EditorGUIUtility.PingObject(newScene); } [MenuItem("Tools/*关卡数据工具/关卡任务导出")] private static void ExportLevelData() { // 选择导出路径 var savePath = EditorUtility.SaveFilePanel("导出关卡任务数据", Application.dataPath, "LevelTasks.csv", "csv"); if (string.IsNullOrEmpty(savePath)) return; var sb = new StringBuilder(); // 表头 sb.AppendLine(string.Join(",", Csv("关卡json"), Csv("任务ID"), Csv("任务条件"), Csv("任务文本key"), Csv("任务文本"), Csv("任务注释"), Csv("是否为胜利条件"), Csv("是否为失败条件"), Csv("是否为1星条件"), Csv("是否为2星条件"), Csv("是否为3星条件"))); var levelCfgList = EditorTableManager.instance.tables.Level.DataList; try { var levelCount = levelCfgList.Count; for (var levelIndex = 0; levelIndex < levelCount; levelIndex++) { var cfg = levelCfgList[levelIndex]; // 更新进度条 var progress = (levelIndex + 1) / (float)levelCount; EditorUtility.DisplayProgressBar("导出关卡任务数据", $"正在处理关卡: {cfg.LevelData} ({levelIndex + 1}/{levelCount})", progress); // cfg.LevelData 为关卡数据名(不带扩展名) var levelJsonName = cfg.LevelData; var levelPath = string.Format(Constants.LEVEL_CONFIG_FORMAT_PATH, levelJsonName); if (!File.Exists(levelPath)) { Debug.LogWarning($"关卡数据不存在: {levelPath}"); continue; } LevelData levelData = null; try { levelData = JsonHelper.LoadJson(levelPath); } catch (Exception e) { Debug.LogError($"解析关卡数据失败: {levelPath}\n{e}"); continue; } if (levelData == null || levelData.taskInfos == null || levelData.taskInfos.Count == 0) continue; foreach (var task in levelData.taskInfos) { if (task == null || task.taskCondition == null) continue; var taskId = task.taskID; var condition = task.taskCondition; // 任务条件:ConditionType(参数1,参数2,...) var condTypeName = condition.conditionType.ToString(); var args = condition.Args(); string condWithArgs; if (args != null && args.Count > 0) { var argStr = string.Join(",", args.Select(a => a.ToString(CultureInfo.InvariantCulture))); condWithArgs = $"{condTypeName}({argStr})"; } else { condWithArgs = condTypeName; } // 文本 key:如果有手写替换 key(taskDesc)则使用,没有就留空 string textKey = string.IsNullOrEmpty(task.taskDesc) ? string.Empty : task.taskDesc; // 文本内容:仅当有有效 key 时,从 StringConfig 里拿 value,否则留空 string textValue = string.Empty; if (!string.IsNullOrEmpty(textKey)) { try { var dataString = EditorTableManager.instance.tables.StringConfig.GetOrDefault(textKey); textValue = dataString?.Value ?? string.Empty; } catch { // 忽略文本表中不存在的 key } } // 是否为胜利/失败/星级条件 var isSuccess = levelData.successTaskID == taskId; var isFail = levelData.failTaskID == taskId; var isStar1 = levelData.starTaskIDs != null && levelData.starTaskIDs.Count > 0 && levelData.starTaskIDs[0] == taskId; var isStar2 = levelData.starTaskIDs != null && levelData.starTaskIDs.Count > 1 && levelData.starTaskIDs[1] == taskId; var isStar3 = levelData.starTaskIDs != null && levelData.starTaskIDs.Count > 2 && levelData.starTaskIDs[2] == taskId; sb.AppendLine(string.Join(",", Csv(levelJsonName), Csv(taskId.ToString()), Csv(condWithArgs), Csv(textKey), Csv(textValue), Csv(task.taskNote ?? string.Empty), Csv(isSuccess ? "1" : "0"), Csv(isFail ? "1" : "0"), Csv(isStar1 ? "1" : "0"), Csv(isStar2 ? "1" : "0"), Csv(isStar3 ? "1" : "0"))); } } } finally { EditorUtility.ClearProgressBar(); } File.WriteAllText(savePath, sb.ToString(), Encoding.UTF8); AssetDatabase.Refresh(); EditorUtility.RevealInFinder(savePath); } /// /// 简单 CSV 转义:用双引号包裹,并转义内部双引号。 /// private static string Csv(string value) { if (string.IsNullOrEmpty(value)) return "\"\""; value = value.Replace("\"", "\"\""); return $"\"{value}\""; } private static string[] GetAllMapScenes(bool includeTemplate = false) { var guids = AssetDatabase.FindAssets("t:SceneAsset", new[] { MAP_FOLDER }); return guids.Select(AssetDatabase.GUIDToAssetPath).Where(path => includeTemplate || !path.Contains("Template")).ToArray(); } private static string[] GetAllPostProcessedMapScenes() { var mapScenes = GetAllMapScenes(); var result = mapScenes.Select(PostProcessData.EditorInstance.EditorGetPostProcessedPath).ToArray(); return result; } private static List GetModifiedScenes() { var scenePaths = GetAllMapScenes(); return scenePaths.Where(path => PostProcessData.EditorInstance.ShouldPostProcess(path)).ToList(); } //[MenuItem("Tools/地图场景/设置所有场景后处理指导")] private static void SetAllMapInstructor() { var scenePaths = GetAllMapScenes(); foreach (var scenePath in scenePaths) { var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); EditorSceneManager.MarkSceneDirty(scene); var instructor = PostProcessor.FindInstructorInScene(scene); if (instructor == null) { continue; } EditorUtility.SetDirty(instructor); var groundObject = instructor.combinedObjectsRoot.Find("GroundObject"); if (groundObject) { instructor.rootsToCombine.Add(groundObject); } var fog = instructor.combinedObjectsRoot.Find("Fog"); if (fog) { instructor.fog = fog.gameObject; } EditorSceneManager.SaveScene(scene); EditorSceneManager.CloseScene(scene, true); } } //[MenuItem("Tools/地图场景/删除所有地图EventSystem")] private static void DeleteAllEventSystem() { var scenePaths = GetAllMapScenes(); foreach (var scenePath in scenePaths) { var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); EditorSceneManager.MarkSceneDirty(scene); var eventSystem = FrameWorkUtils.GetSceneRootComponent(scene); if (eventSystem) Object.DestroyImmediate(eventSystem.gameObject); EditorSceneManager.SaveScene(scene); EditorSceneManager.CloseScene(scene, true); } var postProcessedMapScene = GetAllPostProcessedMapScenes(); foreach (var scenePath in postProcessedMapScene) { var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); EditorSceneManager.MarkSceneDirty(scene); var eventSystem = FrameWorkUtils.GetSceneRootComponent(scene); if (eventSystem) Object.DestroyImmediate(eventSystem.gameObject); EditorSceneManager.SaveScene(scene); EditorSceneManager.CloseScene(scene, true); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); PostProcessData.EditorInstance.ReCalculateHashCode(); } [MenuItem("Tools/*地图场景/后处理所有场景", false, (int)NLDMenuID.MapPostProcess)] private static void PostProcessAllMap() { var scenePaths = GetModifiedScenes(); if (EditorUtility.DisplayDialog("后处理所有地图场景", $"当前有修改的地图场景有:{scenePaths.Count}个,是否批量执行?", "确认", "取消")) { int index = 0; foreach (var scenePath in scenePaths) { EditorUtility.DisplayProgressBar("后处理所有地图场景", $"当前场景路径:{scenePath} {index + 1}/{scenePaths.Count}", (index + 1) / (float)scenePaths.Count); var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); try { ObjectPostProcessorManager.Instance.PostProcess(scene, true); } catch (Exception e) { Debug.LogError($"{e.Message} \r\n {e.StackTrace}"); } finally { EditorSceneManager.CloseScene(scene, true); } index++; } EditorUtility.ClearProgressBar(); Debug.Log("执行所有地图后处理完成!"); } } // [MenuItem("Tools/Addressables/转移所有场景到组的根部")] private static void MoveAllScenesToAddressableGroupRoot() { #if USE_ADDRESSABLES var settings = AddressableAssetSettingsDefaultObject.Settings; EditorUtility.SetDirty(settings); List entries = new(); foreach (var group in settings.groups) { entries.Clear(); group.GatherAllAssets(entries, true, true, false, e => e.MainAssetType == typeof(SceneAsset) || e.IsFolder); entries = entries.Where(e => e.MainAssetType == typeof(SceneAsset)).ToList(); if (entries.Count > 0) { EditorUtility.SetDirty(group); foreach (var sceneEntry in entries) { settings.MoveEntry(sceneEntry, group); } } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); #endif } // [MenuItem("Tools/Addressables/修改所有group下载信息")] private static void ModifyAllGroupDownloadInfo() { #if USE_ADDRESSABLES var settings = AddressableAssetSettingsDefaultObject.Settings; EditorUtility.SetDirty(settings); foreach (var group in settings.groups) { EditorUtility.SetDirty(group); var schema = group.GetSchema(); if (schema) { EditorUtility.SetDirty(schema); schema.RetryCount = 3; schema.Timeout = 5; } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); #endif } private static string[] GetAllOriginalAndProcessedMap() { var originalMap = GetAllMapScenes(true); var processedMap = GetAllPostProcessedMapScenes(); return originalMap.Concat(processedMap).ToArray(); } [MenuItem("Tools/*地图场景/修改所有场景TGS边框厚度", false, (int)NLDMenuID.MapTGSThickness)] private static void ModifyAllOriginalAndProcessedMapTGSThickness() { var maps = GetAllOriginalAndProcessedMap(); foreach (var map in maps) { var scene = EditorSceneManager.OpenScene(map, OpenSceneMode.Additive); var tgs = FrameWorkUtils.GetSceneRootComponent(scene); if (tgs) { tgs.cellBorderThickness = 0.04f; } EditorSceneManager.SaveScene(scene); EditorSceneManager.CloseScene(scene, true); } PostProcessData.EditorInstance.ReCalculateHashCode(); } }