using TGS; using Framework; using System.IO; using UnityEditor; using UnityEngine; using System.Linq; using Gameplay.Level; using Sirenix.Utilities; using Sirenix.OdinInspector; using Sirenix.Utilities.Editor; using System.Collections.Generic; using cfg.FightCfg; using cfg.LevelCfg; using cfg.MonsterCfg; using cfg.VehicleCfg; using Sirenix.OdinInspector.Editor; using Level = cfg.LevelCfg.Level; using ObjectFieldAlignment = Sirenix.OdinInspector.ObjectFieldAlignment; using static Gameplay.Level.LevelData; public partial class LevelEditorWindow : OdinEditorWindow { [MenuItem("Tools/*地图场景/关卡编辑器", false, (int)NLDMenuID.LevelEditor)] private static void OpenLevelEditorWindow() { var window = GetWindow(); window.position = GUIHelper.GetEditorWindowRect().AlignCenter(800, 600); window.titleContent = new GUIContent("关卡编辑器"); } public static void OpenWindow(LevelEditor levelEditor) { MapEditorSettings.Init(); currWindow = GetWindow(); currWindow.position = GUIHelper.GetEditorWindowRect().AlignCenter(800, 600); currWindow.currentLevelData = null; currWindow._levelEditor = levelEditor; currWindow.LoadLevel(levelEditor.LevelData); var level = Path.GetFileNameWithoutExtension(levelEditor.LevelDataJson.name); currWindow.levelID = level.Substring(level.Length - 6); //currWindow.GetLevelMonsterClass(); DebugUtil.Log("关卡数据:{0}", currWindow.levelID); } private static LevelEditorWindow currWindow; private MapManager _mapManager; private Map _curMap; private LevelEditor _levelEditor; #region 配置表 private Level LevelCfg => EditorTableManager.instance.tables.Level; private Monster MonsterCfg => EditorTableManager.instance.tables.Monster; private MonsterClass MonsterClassCfg => EditorTableManager.instance.tables.MonsterClass; private VehicleConfig VehicleCfg => EditorTableManager.instance.tables.VehicleConfig; private FightNPCConfig FightNpcCfg => EditorTableManager.instance.tables.FightNPCConfig; private BuildConfig FightBuildCfg => EditorTableManager.instance.tables.BuildConfig; #endregion #region 地块颜色 private static readonly Color RegionColor = MapUtils.GetRGBAColor("FF960072"); private static readonly Color ShowRegionColor = Color.red; private static readonly Color CaptureRegionColor = Color.yellow; private static readonly Color RetreatRegionColor = Color.cyan; private static readonly Color GroupHighlightColor = new Color(1f, 0.5f, 0f, 0.8f); #endregion #region 任务相关 [BoxGroup("任务列表")] [LabelText("任务列表"), Indent] public List taskInfos = new List(); #endregion #region 关卡数据 [FoldoutGroup("当前关卡数据")] [HideLabel] public LevelData currentLevelData = null; #endregion #region 触发器 [FoldoutGroup("当前关卡数据")] [BoxGroup("当前关卡数据/触发器列表")] [Indent, LabelText("触发器")] public List levelTriggerData = new List(); #endregion #region 波次管理 [FoldoutGroup("当前关卡数据")] [BoxGroup("当前关卡数据/波次管理")] [LabelText("编辑波次"), Indent] [OnValueChanged(nameof(OnIsEditingWaveChanged))] [SerializeField] public bool isEditingWave; private void OnIsEditingWaveChanged() { if (isEditingWave) { // 刷新波次信息显示 RefreshWaveInfoEditors(); } } [FoldoutGroup("当前关卡数据")] [BoxGroup("当前关卡数据/波次管理")] [LabelText("波次列表(管理怪物组)"), Indent] [ShowIf(nameof(isEditingWave))] [InfoBox("说明:波次管理基于怪物组ID,不是NPC索引。一个怪物组只能属于一个波次。直接添加项来创建新波次,波次ID可修改。", InfoMessageType.Info)] [ListDrawerSettings( CustomRemoveIndexFunction = nameof(CustomRemoveWave), DraggableItems = false, ShowFoldout = true )] public List waveInfoEditors = new(); private void CustomRemoveWave(int index) { if (index >= 0 && index < waveInfoEditors.Count) { var wave = waveInfoEditors[index]; // 清除该波次中所有怪物组的怪物的波次信息 if (currentEnemyNpcInfo != null) { foreach (var groupId in wave.enemies) { foreach (var npc in currentEnemyNpcInfo) { if (npc.npcInfo.groups != null && npc.npcInfo.groups.Contains(groupId)) { npc.npcInfo.waveIndex = 0; npc.npcInfo.isHideOnLoad = false; } } } } waveInfoEditors.RemoveAt(index); } } /// /// 刷新波次信息编辑器 /// [FoldoutGroup("当前关卡数据")] [BoxGroup("当前关卡数据/波次管理")] [Button("刷新至怪物属性"), Indent] [ShowIf(nameof(isEditingWave))] private void RefreshWaveInfoEditors() { if (waveInfoEditors == null) waveInfoEditors = new List(); if (currentEnemyNpcInfo == null) return; // 第一步:重建 _enemyNpcObjectWaveDic 字典 // 创建新的字典结构 var newWaveDic = new Dictionary>(); // 遍历所有NPC,根据当前的waveIndex重新组织字典 foreach (var npc in currentEnemyNpcInfo) { int oldWaveIndex = npc.npcInfo.waveIndex; // 从旧字典中找到这个NPC的GameObject GameObject npcObj = null; if (_enemyNpcObjectWaveDic != null && _enemyNpcObjectWaveDic.TryGetValue(oldWaveIndex, out var oldNpcDic)) { if (oldNpcDic.TryGetValue(npc, out npcObj)) { // 找到了GameObject } } // 如果找不到,尝试在所有波次中查找 if (npcObj == null && _enemyNpcObjectWaveDic != null) { foreach (var waveDic in _enemyNpcObjectWaveDic.Values) { if (waveDic.TryGetValue(npc, out npcObj)) { break; } } } // 将GameObject添加到新字典中(按照即将更新的waveIndex) if (npcObj != null) { // 先暂存到旧的waveIndex,后面会更新 if (!newWaveDic.ContainsKey(oldWaveIndex)) { newWaveDic[oldWaveIndex] = new Dictionary(); } newWaveDic[oldWaveIndex][npc] = npcObj; } } // 第二步:清空所有NPC的波次信息 foreach (var npc in currentEnemyNpcInfo) { npc.npcInfo.waveIndex = 0; npc.npcInfo.isHideOnLoad = false; } // 第三步:根据波次编辑器更新NPC属性 var finalWaveDic = new Dictionary>(); foreach (var wave in waveInfoEditors) { foreach (var groupId in wave.enemies) { foreach (var npc in currentEnemyNpcInfo) { if (npc.npcInfo.groups != null && npc.npcInfo.groups.Contains(groupId)) { npc.npcInfo.waveIndex = wave.waveID; npc.npcInfo.isHideOnLoad = true; // 更新到最终字典(使用新的waveIndex) // 从临时字典中找到GameObject GameObject npcObj = null; foreach (var waveDic in newWaveDic.Values) { if (waveDic.TryGetValue(npc, out npcObj)) { break; } } if (npcObj != null) { if (!finalWaveDic.ContainsKey(wave.waveID)) { finalWaveDic[wave.waveID] = new Dictionary(); } finalWaveDic[wave.waveID][npc] = npcObj; } } } } } // 第四步:处理不在任何波次中的NPC(waveIndex = 0) foreach (var npc in currentEnemyNpcInfo) { if (npc.npcInfo.waveIndex == 0) { // 从临时字典中找到GameObject GameObject npcObj = null; foreach (var waveDic in newWaveDic.Values) { if (waveDic.TryGetValue(npc, out npcObj)) { break; } } if (npcObj != null) { if (!finalWaveDic.ContainsKey(0)) { finalWaveDic[0] = new Dictionary(); } finalWaveDic[0][npc] = npcObj; } } } // 第五步:替换原字典 _enemyNpcObjectWaveDic = finalWaveDic; DebugUtil.Log("已刷新波次信息至怪物属性,并更新了预制体字典"); } #endregion #region 战役关卡 [FoldoutGroup("当前关卡数据")] [BoxGroup("当前关卡数据/支线(战役)")] [LabelText("设置该关卡为评分关卡"), Indent] [OnValueChanged(nameof(OnScoreLevelChanged))] public bool scoreLevel; private void OnScoreLevelChanged() { currentLevelData.scoreLevel = scoreLevel; } [FoldoutGroup("当前关卡数据")] [BoxGroup("当前关卡数据/支线(战役)")] [Indent, ShowIf("@currentLevelData.scoreLevel")] [LabelText("评分条件")] public List scoreTasks = new List(); #endregion #region 关卡引导 [FoldoutGroup("关卡引导")] [BoxGroup("关卡引导/关卡进度")] [LabelText("关卡进度"), Indent] public List progressGuideDatas = new(); [FoldoutGroup("关卡引导")] [BoxGroup("关卡引导/立标数据")] [LabelText("立标数据"), Indent] public List setFlagEditorInfos = new(); [FoldoutGroup("关卡引导")] [BoxGroup("关卡引导/信息板数据")] [LabelText("信息板数据"), Indent] public List infoPanelGuideDatas = new(); #endregion #region 关卡AI广播 [FoldoutGroup("关卡AI广播")] [BoxGroup("关卡AI广播/数据")] [LabelText("AI广播数据"), Indent] public List listAIBroadcastData = new(); #endregion #region 地区相关 /// /// 每种地区对应的显示颜色 /// private static readonly Dictionary ShowRegionTypeColors = new Dictionary { { ShowRegionType.CaptureRegion, CaptureRegionColor }, { ShowRegionType.RetreatRegion, RetreatRegionColor }, { ShowRegionType.EnemyRetreatRegion, Color.green }, { ShowRegionType.MinesRegion, Color.black }, { ShowRegionType.DefenseRegion, Color.magenta }, { ShowRegionType.BlockRegion, Color.blue }, }; [FoldoutGroup("地区相关")] [LabelText("显示地区颜色")] [EnumToggleButtons] [OnValueChanged(nameof(OnShowRegionTypeChanged))] public ShowRegionType showRegionType; /// /// 根据当前选择的地区类型集合,高亮对应的地区格子,每种地区使用独立颜色。 /// private void OnShowRegionTypeChanged() { if (_curMap == null || currentLevelData == null) return; ClearShowRegionColor(); if (showRegionType == ShowRegionType.None) { SceneView.RepaintAll(); return; } var mask = showRegionType; if (mask.HasFlag(ShowRegionType.All)) { mask |= ShowRegionType.All; } foreach (var kv in ShowRegionTypeColors) { var type = kv.Key; var color = kv.Value; if (!mask.HasFlag(type)) continue; var points = GetRegionPoints(type); if (points == null || points.Count == 0) continue; _curMap.SetBlocksColor(points, true, color); } SceneView.RepaintAll(); } /// /// 按地区类型获取需要高亮的格子索引列表 /// private List GetRegionPoints(ShowRegionType type) { var points = new HashSet(); switch (type) { case ShowRegionType.CaptureRegion: if (captureEditorInfos != null) { foreach (var capture in captureEditorInfos) { if (capture?.capturePointEditorInfos == null) continue; foreach (var p in capture.capturePointEditorInfos) { points.Add(p.capturePoint); } } } break; case ShowRegionType.RetreatRegion: if (retreatEditorInfo != null) { foreach (var info in retreatEditorInfo) { points.Add(info.retreatPoint); } } break; case ShowRegionType.EnemyRetreatRegion: if (enemyRetreatEditorInfo?.fallbackReusablePoints != null) { foreach (var pos in enemyRetreatEditorInfo.fallbackReusablePoints) { points.Add(_curMap.BlockPosition2TGSCellIndex(pos)); } } break; case ShowRegionType.MinesRegion: if (minesRegionList != null) { foreach (var area in minesRegionList) { if (area?.Positions == null) continue; foreach (var cell in area.Positions) { points.Add(cell); } } } break; case ShowRegionType.DefenseRegion: if (defenseRegionList != null) { foreach (var area in defenseRegionList) { if (area?.Positions == null) continue; foreach (var cell in area.Positions) { points.Add(cell); } } } break; case ShowRegionType.BlockRegion: if (blockRegionList != null) { foreach (var area in blockRegionList) { if (area?.Positions == null) continue; foreach (var cell in area.Positions) { points.Add(cell); } } } break; } return points.ToList(); } #region 撤离相关 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/撤离相关")] [VerticalGroup("地区相关/撤离相关/编辑")] [LabelText("编辑撤离数据"), Indent] [OnValueChanged("OnIsEditingRetreatChanged")] public bool isEditingRetreat; [FoldoutGroup("地区相关")] [BoxGroup("地区相关/撤离相关")] [LabelText("当前撤离数据"), Indent] [ShowIf("isEditingRetreat")] [ListDrawerSettings(CustomRemoveIndexFunction = "CustomRemoveRetreatPoint", DraggableItems = false)] public List retreatEditorInfo = new(); private void CustomRemoveRetreatPoint(int index) { if (index >= retreatEditorInfo.Count) return; var point = retreatEditorInfo[index].retreatPoint; _curMap.TerrainGridSystem.CellHideRegionSurface(point); retreatEditorInfo.RemoveAt(index); } #endregion #region 敌人撤离相关 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/敌人撤离相关")] [LabelText("编辑敌人撤离数据"), Indent] [OnValueChanged("OnIsEditingEnemyRetreatChanged")] public bool isEditingEnemyRetreat; private void OnIsEditingEnemyRetreatChanged() { if (isEditingEnemyRetreat) return; isEditingFallbackReusable = false; } private bool isEditingFallbackReusable; [FoldoutGroup("地区相关")] [BoxGroup("地区相关/敌人撤离相关")] [LabelText("当前撤离数据"), Indent] [ShowIf("isEditingEnemyRetreat")] public EnemyRetreatEditorInfo enemyRetreatEditorInfo = new(); #endregion #region 出战防守区 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/出战防守")] [LabelText("当前地区数据"), Indent] public RegionData curRegionData = null; #endregion #region 占领相关 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/占领相关")] [VerticalGroup("地区相关/占领相关/编辑")] [LabelText("编辑占领区"), Indent] public bool isEditingCapture = false; [FoldoutGroup("地区相关")] [BoxGroup("地区相关/占领相关")] [VerticalGroup("地区相关/占领相关/编辑")] [LabelText("编辑占领区"), Indent] [ShowIf("isEditingCapture")] public List captureEditorInfos = new(); #endregion #region 雷达 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/编辑雷达信息")] [HorizontalGroup("地区相关/编辑雷达信息/radar")] [LabelText("编辑雷达信息"), Indent] [LabelWidth(100)] [SerializeField] private bool canEditorRadarInfo; [FoldoutGroup("地区相关")] [VerticalGroup("地区相关/编辑雷达信息/radarInfo"), Indent] [LabelText("雷达列表")] [ShowIf("canEditorRadarInfo")] public List radarInfoList = new(); #endregion #region 雷区 点击 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/雷区信息")] [HorizontalGroup("地区相关/雷区信息/Region")] [LabelText("编辑雷区"), Indent] [OnValueChanged("OnCanEditorMinesAreaTypeChanged")] [LabelWidth(100)] [SerializeField] private bool canEditorMinesArea; private void OnCanEditorMinesAreaTypeChanged() { if (canEditorMinesArea) { CloseBlockRegion(); CloseMonsterTrigger(); CloseHoldFlag(); CloseSearchLight(); } else { CloseMinesRegion(); } SceneView.RepaintAll(); } [FoldoutGroup("地区相关")] [VerticalGroup("地区相关/雷区信息/RegionInfo"), Indent] [LabelText("雷区列表")] [ShowIf("canEditorMinesArea")] [ListDrawerSettings(CustomAddFunction = "CustomAddMinesRegion", CustomRemoveIndexFunction = "CustomRemoveMinesRegion", DraggableItems = false)] public List minesRegionList = new(); private void CustomAddMinesRegion() { var region = new LevelData.Region(); currentLevelData.minesRegions.Add(region); UpdateMinesRegion(); } private void CustomRemoveMinesRegion(int index) { ClearMinesRegionColor(); currentLevelData.minesRegions.RemoveAt(index); UpdateMinesRegion(); } #endregion #region 阻挡区信息 点击 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/阻挡区信息")] [HorizontalGroup("地区相关/阻挡区信息/Region")] [LabelText("编辑阻挡区"), Indent] [OnValueChanged("OnCanEditorBlockAreaChanged")] [LabelWidth(100)] [SerializeField] private bool canEditorBlockArea; private void OnCanEditorBlockAreaChanged() { if (canEditorBlockArea) { CloseMinesRegion(); CloseMinesRegion(); CloseMonsterTrigger(); CloseHoldFlag(); } else { CloseBlockRegion(); } SceneView.RepaintAll(); } [FoldoutGroup("地区相关")] [LabelText("阻挡区列表")] [ShowIf("canEditorBlockArea")] [VerticalGroup("地区相关/阻挡区信息/RegionInfo"), Indent] [ListDrawerSettings(CustomAddFunction = "CustomAddBlockRegion", CustomRemoveIndexFunction = "CustomRemoveBlockRegionIndex", DraggableItems = false)] public List blockRegionList = new(); private void CustomAddBlockRegion() { var region = new BlockRegion(); currentLevelData.blockRegions.Add(region); UpdateBlockRegion(); } private void CustomRemoveBlockRegionIndex(int index) { if (index < blockRegionList.Count) { if (blockRegionList[index].obj != null) { DebugUtil.LogError($"删除{blockRegionList[index].id}的指示"); DestroyImmediate(blockRegionList[index].obj); } } ClearBlockRegionColor(); currentLevelData.blockRegions.RemoveAt(index); UpdateBlockRegion(); } #endregion #region 刷怪触发区 点击 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/刷怪触发区")] [HorizontalGroup("地区相关/刷怪触发区/Region")] [LabelText("编辑触发区"), Indent] [OnValueChanged("OnCanEditorMonsterTriggerChanged")] [LabelWidth(100)] [SerializeField] private bool canEditorMonsterTrigger; private void OnCanEditorMonsterTriggerChanged() { if (canEditorMonsterTrigger) { CloseBlockRegion(); CloseMinesRegion(); CloseSearchLight(); CloseHoldFlag(); } else { CloseMonsterTrigger(); } SceneView.RepaintAll(); } [FoldoutGroup("地区相关")] [LabelText("刷怪触发区列表")] [ShowIf("canEditorMonsterTrigger")] [VerticalGroup("地区相关/刷怪触发区/RegionInfo"), Indent] [ListDrawerSettings(CustomAddFunction = "CustomAddDefenseRegion", CustomRemoveIndexFunction = "CustomRemoveDefenseRegion", DraggableItems = false)] public List defenseRegionList = new(); private void CustomAddDefenseRegion() { var region = new LevelData.Region(); currentLevelData.defenseRegions.Add(region); UpdateMonsterTrigger(); } private void CustomRemoveDefenseRegion(int index) { ClearMonsterTriggerColor(); currentLevelData.defenseRegions.RemoveAt(index); UpdateMonsterTrigger(); } #endregion #region 战旗 点击 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/战旗")] [HorizontalGroup("地区相关/战旗/Region")] [LabelText("编辑战旗信息"), Indent] [OnValueChanged("OnCanEditorHoldFlagAreaChanged")] [LabelWidth(100)] [SerializeField] private bool canEditorHoldFlagArea; private void OnCanEditorHoldFlagAreaChanged() { if (canEditorHoldFlagArea) { CloseBlockRegion(); CloseMonsterTrigger(); CloseMinesRegion(); CloseSearchLight(); } else { CloseHoldFlag(); } SceneView.RepaintAll(); } [FoldoutGroup("地区相关")] [LabelText("战旗争夺区列表")] [ShowIf("canEditorHoldFlagArea")] [VerticalGroup("地区相关/战旗/RegionInfo"), Indent] [ListDrawerSettings(CustomAddFunction = "CustomAddHoldFlag", CustomRemoveIndexFunction = "CustomRemoveHoldFlag", DraggableItems = false)] public List holdFlagEditorList = new(); private void CustomAddHoldFlag() { var holdFlag = new HoldFlagData(); currentLevelData.holdGFlagInfos.Add(holdFlag); UpdateHoldFlag(); } private void CustomRemoveHoldFlag(int index) { ClearHoldFlagColor(); currentLevelData.holdGFlagInfos.RemoveAt(index); UpdateHoldFlag(); } #endregion #region 探照灯 点击 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/探照灯")] [HorizontalGroup("地区相关/探照灯/Region")] [LabelText("编辑探照灯"), Indent] [OnValueChanged("OnCanEditorSearchLightChanged")] [LabelWidth(100)] [SerializeField] private bool canEditorSearchLight; private void OnCanEditorSearchLightChanged() { if (canEditorSearchLight) { CloseBlockRegion(); CloseMonsterTrigger(); CloseMinesRegion(); CloseHoldFlag(); } else { CloseSearchLight(); } SceneView.RepaintAll(); } [FoldoutGroup("地区相关")] [VerticalGroup("地区相关/探照灯/RegionInfo"), Indent] [LabelText("探照灯列表")] [ShowIf("canEditorSearchLight")] [ListDrawerSettings(CustomAddFunction = "CustomAddSearchLight", CustomRemoveIndexFunction = "CustomRemoveSearchLight", DraggableItems = false)] public List searchLightList = new(); private void CustomAddSearchLight() { var searchLight = new LevelData.SearchLightInfo(); currentLevelData.searchLightInfos.Add(searchLight); UpdateSearchLightInfo(); } private void CustomRemoveSearchLight(int index) { ClearSearchLightColor(); currentLevelData.searchLightInfos.RemoveAt(index); UpdateSearchLightInfo(); } #endregion #region 轰炸区 [FoldoutGroup("地区相关")] [BoxGroup("地区相关/编辑轰炸区")] [HorizontalGroup("地区相关/编辑轰炸区/bomb")] [LabelText("编辑编辑轰炸区"), Indent] [LabelWidth(100)] [SerializeField] private bool canEditorBombAreaInfo; [FoldoutGroup("地区相关")] [VerticalGroup("地区相关/编辑轰炸区/bombInfo"), Indent] [LabelText("轰炸区列表")] [ShowIf("canEditorBombAreaInfo")] public List bombAreaInfoList = new(); #endregion #endregion #region 指定路线 [BoxGroup("制定路线")] [LabelText("编辑指定路径"), Indent] public bool isEditingRoad; [BoxGroup("制定路线")] [LabelText("编辑指定路线"), Indent] [ShowIf("isEditingRoad")] public List roadEditorInfos = new(); #endregion #region 指定区域 [BoxGroup("巡逻区域")] [LabelText("编辑巡逻区域"), Indent] public bool isEditingArea; [BoxGroup("巡逻区域")] [LabelText("编辑巡逻区域"), Indent] [ShowIf("isEditingArea")] public List areaEditorInfos = new(); #endregion #region 显示地图索引 [BoxGroup("显示地格索引")] [LabelText("显示类型"), Indent] [OnValueChanged("ShowCellIndex")] [LabelWidth(100)] [SerializeField] private CellDebugInfo cellDebugInfo = CellDebugInfo.Nothing; private void ShowCellIndex() { if (_curMap == null || _curMap.TerrainGridSystem == null) return; _curMap.TerrainGridSystem.displayCellDebugInfo = cellDebugInfo; var obj = FindObjectOfType().gameObject; Selection.activeGameObject = obj; } #endregion #region 敌方Npc private GameObject enemyObjRoot; /// /// 波次/怪物ID对应的NPC预制体 /// private Dictionary> _enemyNpcObjectWaveDic; [BoxGroup("敌方Npc")] [LabelText("编辑NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingEnemyNpcChanged")] public bool isEditingEnemyNpc; [BoxGroup("敌方Npc")] [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllEnemyNpcId")] [SerializeField] public bool isShowAllEnemyNpcID; [BoxGroup("敌方Npc")] [LabelText("筛选怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingEnemyNpc")] public string selectEnemyMonsterId; [BoxGroup("敌方Npc")] [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("EnemyMonsterIDChanged")] [ValueDropdown("GetEnemyMonsterIDs")] [ShowIf("isEditingEnemyNpc")] public string curEnemyMonsterIDStr; private int curEnemyMonsterID; [BoxGroup("敌方Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingEnemyNpc")] public GameObject enemyNpcObject; /// /// 真实敌人列表 /// [HideInInspector] public List currentEnemyNpcInfo = null; private IEnumerable GetEnemyMonsterIDs() { if (string.IsNullOrEmpty(selectEnemyMonsterId)) return _allMonsterIDs.Keys; _selectMonsterIDs.Clear(); foreach (var enemyMonster in _allMonsterIDs.Keys) { if (enemyMonster.Contains(selectEnemyMonsterId)) _selectMonsterIDs.Add(enemyMonster); } return _selectMonsterIDs; } private void EnemyMonsterIDChanged() { if (!_allMonsterIDs.TryGetValue(curEnemyMonsterIDStr, out var id)) return; curEnemyMonsterID = id; var obj = LoadEnemyNpcPrefab(curEnemyMonsterID); if (obj != null) enemyNpcObject = obj; } private void OnIsEditingEnemyNpcChanged() { if (isEditingEnemyNpc) { isEditingSelfNpc = false; isEditingMidNpc = false; return; } foreach (var npcEditorInfo in currentEnemyNpcInfo) { npcEditorInfo.isChangePos = false; } isEditingNpcPos = false; currWindow._currNpcEditorInfo = null; currWindow._currEditingNpcInfo = null; // 清除所有组的高亮显示 ClearAllGroupHighlights(); } private void ShowAllEnemyNpcId() { foreach (var npc in currentEnemyNpcInfo) { npc.isShowNpcID = isShowAllEnemyNpcID; if (isShowAllEnemyNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } [BoxGroup("敌方Npc")] [Button("保存"), ShowIf("isEditingEnemyNpc")] private void SaveNewGroup() { if (enemyNpcGroups == null || enemyNpcGroups.Count == 0) { DebugUtil.LogWarning("没有分组需要保存"); return; } int changedCount = 0; foreach (var group in enemyNpcGroups) { if (group != null) { group.ApplyGroupIdChange(false); changedCount++; } } // 所有修改完成后,统一刷新一次视图 RefreshEnemyNpcGroups(); } /// /// 敌方 NPC 的分组展示数据 /// [BoxGroup("敌方Npc")] [LabelText("按组查看怪物"), Indent, ShowIf("isEditingEnemyNpc")] [ShowInInspector] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, DraggableItems = false, ShowFoldout = true)] public List enemyNpcGroups = new(); /// /// 清除所有组的高亮显示 /// private void ClearAllGroupHighlights() { if (enemyNpcGroups == null) return; foreach (var group in enemyNpcGroups) { if (group != null) { // 强制清除高亮 group.ForceClearHighlight(); } } } #endregion #region 己方Npc private const int SelfID = 800; private GameObject selfObjRoot; private Dictionary _selfNpcObjectDic; [BoxGroup("己方Npc")] [LabelText("编辑己方NPC"), Indent] [LabelWidth(100)] [OnValueChanged("OnIsEditingSelfNpcChanged")] [SerializeField] public bool isEditingSelfNpc; [BoxGroup("己方Npc")] [LabelText("显示所有己方序号ID"), Indent] [OnValueChanged("ShowAllSelfNpcId")] [SerializeField] public bool isShowAllSelfNpcID; [BoxGroup("己方Npc")] [LabelText("筛选怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingSelfNpc")] public string selectSelfMonsterId; [BoxGroup("己方Npc")] [LabelText("己方NpcID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("SelfNpcIDChanged")] [ValueDropdown("GetSelfNpcIDs")] [ShowIf("isEditingSelfNpc")] public string curSelfNpcIDStr; private int curSelfNpcID; [BoxGroup("己方Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("己方NPC预制体预览图"), ShowIf("isEditingSelfNpc")] public GameObject selfNpcObject; [BoxGroup("己方Npc")] [LabelText("当前己方NPC列表"), Indent, ShowIf("isEditingSelfNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true)] public List currentSelfNpcInfo = null; private IEnumerable GetSelfNpcIDs() { if (string.IsNullOrEmpty(selectSelfMonsterId)) return _allMonsterIDs.Keys; _selectMonsterIDs.Clear(); foreach (var enemyMonster in _allMonsterIDs.Keys) { if (enemyMonster.Contains(selectSelfMonsterId)) _selectMonsterIDs.Add(enemyMonster); } return _selectMonsterIDs; } private void SelfNpcIDChanged() { if (!_allMonsterIDs.TryGetValue(curSelfNpcIDStr, out var id)) return; curSelfNpcID = id; var obj = LoadSelfNpcPrefab(curSelfNpcID); if (obj != null) selfNpcObject = obj; } private void OnIsEditingSelfNpcChanged() { if (isEditingSelfNpc) { isEditingEnemyNpc = false; isEditingMidNpc = false; return; } foreach (var npcEditorInfo in currentSelfNpcInfo) { npcEditorInfo.isChangePos = false; } isEditingNpcPos = false; currWindow._currNpcEditorInfo = null; currWindow._currEditingNpcInfo = null; } private void ShowAllSelfNpcId() { foreach (var npc in currentSelfNpcInfo) { npc.isShowNpcID = isShowAllSelfNpcID; if (isShowAllSelfNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 中立Npc private const int MidID = 500; private GameObject midObjRoot; /// /// 中立 波次/怪物ID对应的NPC预制体 /// private Dictionary> _midNpcObjectWaveDic; [BoxGroup("中立Npc")] [LabelText("编辑中立NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingMidNpcChanged")] public bool isEditingMidNpc; [BoxGroup("中立Npc")] [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllMidNpcId")] [SerializeField] public bool isShowAllMidNpcID; [BoxGroup("中立Npc")] [LabelText("筛选怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingMidNpc")] public string selectMidMonsterId; [BoxGroup("中立Npc")] [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("MidMonsterIDChanged")] [ValueDropdown("GetMidMonsterIDs")] [ShowIf("isEditingMidNpc")] public string curMidMonsterIDStr; private int curMidMonsterID; [BoxGroup("中立Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingMidNpc")] public GameObject midNpcObject; [BoxGroup("中立Npc")] [LabelText("当前怪物列表"), Indent, ShowIf("isEditingMidNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, ShowFoldout = true)] public List currentMidNpcInfo = null; private IEnumerable GetMidMonsterIDs() { if (string.IsNullOrEmpty(selectMidMonsterId)) return _allMonsterIDs.Keys; _selectMonsterIDs.Clear(); foreach (var enemyMonster in _allMonsterIDs.Keys) { if (enemyMonster.Contains(selectMidMonsterId)) _selectMonsterIDs.Add(enemyMonster); } return _selectMonsterIDs; } private void MidMonsterIDChanged() { if (!_allMonsterIDs.TryGetValue(curMidMonsterIDStr, out var id)) return; curMidMonsterID = id; var obj = LoadMidNpcPrefab(curMidMonsterID); if (obj != null) midNpcObject = obj; } private void OnIsEditingMidNpcChanged() { if (isEditingMidNpc) { isEditingEnemyNpc = false; isEditingSelfNpc = false; return; } foreach (var npcEditorInfo in currentMidNpcInfo) { npcEditorInfo.isChangePos = false; } isEditingNpcPos = false; currWindow._currNpcEditorInfo = null; currWindow._currEditingNpcInfo = null; } private void ShowAllMidNpcId() { foreach (var npc in currentMidNpcInfo) { npc.isShowNpcID = isShowAllMidNpcID; if (isShowAllMidNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 阻挡球编辑 [BoxGroup("攻击阻挡球")] [HorizontalGroup("攻击阻挡球/Block")] [LabelText("编辑攻击阻挡球"), Indent] [OnValueChanged("OnCanEditorBlockSphereChanged")] [LabelWidth(100)] [SerializeField] private bool canEditorBlockSphere; private void OnCanEditorBlockSphereChanged() { if (canEditorBlockSphere) { showBuildings = false; HideObjectsExcept(); blockSphereEditorList.Clear(); UpdateBlockSphereInfo(); foreach (var cellIndex in banAttackCells) { if (blockSphereInfo.TryGetValue(cellIndex, out var sphereInfo)) { var sphere = CreateSphere(cellIndex, sphereInfo.offestPos, Vector3.one * sphereInfo.scale); var blockEditorInfo = new BlockSpereEditorInfo(sphereInfo, sphere); blockSphereEditorList.Add(blockEditorInfo); } else { var pos = _curMap.TGSCellIndex2WorldPosition(cellIndex); var sphere = CreateSphere(cellIndex, pos, Vector3.one * Calibre); var blockEditorInfo = new BlockSpereEditorInfo(cellIndex, sphere); blockSphereEditorList.Add(blockEditorInfo); } } editingBlockSphereList.Clear(); foreach (var editorInfo in blockSphereEditorList) { if (editorInfo.isChanged) { editingBlockSphereList.Add(editorInfo); } } } else { // 清理所有临时材质 foreach (var editorInfo in blockSphereEditorList) { if (editorInfo.editingMaterial != null) { if (editorInfo.sphere != null) { var renderer = editorInfo.sphere.GetComponent(); if (renderer != null) { renderer.sharedMaterial = editorInfo.originalSharedMaterial; } } DestroyImmediate(editorInfo.editingMaterial); } } showBuildings = true; ShowHiddenObjects(); DestroyBlockSphere(); } } [LabelText("展示建筑")] [HorizontalGroup("攻击阻挡球/BlockBuildings"), Indent] [ShowIf("canEditorBlockSphere")] [OnValueChanged("OnShowBuildingsChanged")] public bool showBuildings; private void OnShowBuildingsChanged() { if (showBuildings) { ShowHiddenObjects(); } else { HideObjectsExcept(); } } [LabelText("添加地格")] [HorizontalGroup("攻击阻挡球/新数据"), Indent] [ShowIf("canEditorBlockSphere")] public int addCellIndex; [Button("添加编辑")] [HorizontalGroup("攻击阻挡球/新数据/Add"), Indent] [ShowIf("canEditorBlockSphere")] public void AddBlockSphere() { if (!banAttackCells.Contains(addCellIndex)) { EditorUtility.DisplayDialog("错误", $"地格 {addCellIndex} 不是攻击阻挡地格!", "确定"); return; } foreach (var blockSpereEditor in editingBlockSphereList) { if (blockSpereEditor.cellIndex == addCellIndex) { return; } } foreach (var blockSpereEditor in blockSphereEditorList) { if (blockSpereEditor.cellIndex == addCellIndex) { editingBlockSphereList.Add(blockSpereEditor); break; } } } [LabelText("阻挡球列表")] [HorizontalGroup("攻击阻挡球/EditList"), Indent] [ShowIf("canEditorBlockSphere")] [ListDrawerSettings(HideAddButton = true, CustomRemoveIndexFunction = "CustomRemoveBlockSphereIndex")] public List editingBlockSphereList = new(); [HideInInspector] public List blockSphereEditorList = new(); private void CustomRemoveBlockSphereIndex(int index) { if (index < editingBlockSphereList.Count) { editingBlockSphereList[index].Revert(); DebugUtil.Log($"删除并还原{editingBlockSphereList[index].cellIndex}的阻挡球编辑数据"); editingBlockSphereList.RemoveAt(index); } } #endregion #region 保存 [Button("保存当前关卡"), Indent] private void SaveCurrentLevel() { CheckCorrectLevel(); if (!CheckTask()) return; currentLevelData.taskInfos = taskInfos.Select(t => t.taskInfo).ToList(); ProcessCondition(); // 地区 currentLevelData.preparingRegionId = curRegionData.preparingRegionId; currentLevelData.preparingRegionToward = curRegionData.preparingRegionToward; currentLevelData.pvpDefendRegionId = curRegionData.pvpDefendRegionId; currentLevelData.pvpDefendToward = curRegionData.pvpDefendToward; // 评分关卡 currentLevelData.scoreTaskID = scoreTasks.Select(s => s.scoreTaskID).ToList(); //触发器 currentLevelData.levelTriggerDatas = levelTriggerData.Select(e => e.levelTriggerData).ToList(); #region 敌方NPC foreach (var npcEditor in currentEnemyNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; } currentLevelData.npcInfos = currentEnemyNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); #endregion #region 波次信息 // 验证波次ID是否有重复且大于0 if (waveInfoEditors != null && waveInfoEditors.Count > 0) { var waveIdSet = new HashSet(); foreach (var waveEditor in waveInfoEditors) { if (waveEditor.waveID <= 0) { EditorUtility.DisplayDialog("错误", $"波次ID必须大于0,当前有波次ID为 {waveEditor.waveID}!", "确定"); return; } if (!waveIdSet.Add(waveEditor.waveID)) { EditorUtility.DisplayDialog("错误", $"波次ID {waveEditor.waveID} 重复,请修改后再保存!", "确定"); return; } } } // 更新所有敌方NPC的波次信息(基于怪物组) foreach (var npc in currentEnemyNpcInfo) { bool inWave = false; if (npc.npcInfo.groups != null && npc.npcInfo.groups.Count > 0) { // 检查该NPC的任何一个组是否在某个波次中 foreach (var wave in waveInfoEditors) { foreach (var groupId in npc.npcInfo.groups) { if (wave.enemies.Contains(groupId)) { npc.npcInfo.waveIndex = wave.waveID; npc.npcInfo.isHideOnLoad = true; inWave = true; break; } } if (inWave) break; } } // 如果不在任何波次中,清除波次信息 if (!inWave) { npc.npcInfo.waveIndex = 0; npc.npcInfo.isHideOnLoad = false; } } #endregion #region 怪物组信息 // 保存敌方NPC的分组信息 currentLevelData.groupInfos.Clear(); if (enemyNpcGroups != null) { foreach (var groupView in enemyNpcGroups) { // 跳过"无编组怪物"组(groupId为-1) if (groupView.groupId < 0) continue; var groupInfo = new GroupInfo { groupID = groupView.groupId, groupName = groupView.groupName }; currentLevelData.groupInfos.Add(groupInfo); } } #endregion #region 己方NPC foreach (var npcFightEditorInfo in currentSelfNpcInfo) { npcFightEditorInfo.npcInfo.index = npcFightEditorInfo.id; } currentLevelData.selfNpcInfos = currentSelfNpcInfo.Select(editorFightNpcInfo => editorFightNpcInfo.npcInfo).ToList(); #endregion #region 中立NPC foreach (var npcEditor in currentMidNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; } currentLevelData.midNpcInfos = currentMidNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); #endregion // 占领数据 currentLevelData.CapturePoints.Clear(); foreach (var captureInfo in captureEditorInfos) { var points = captureInfo.capturePointEditorInfos.Select(x => x.capturePoint).ToList(); var flagPoint = 0; foreach (var pointData in captureInfo.capturePointEditorInfos) { if (pointData.captureFlag) flagPoint = pointData.capturePoint; } var capture = new CaptureInfo { captureRegionID = captureInfo.captureRegionID, capturePoints = points, captureFlag = flagPoint, CaptureProgress = captureInfo.captureProgress, captureEffectID = captureInfo.captureEffectID, captureEffectPoint = captureInfo.captureEffectPoint, }; currentLevelData.CapturePoints.Add(capture); } // 撤离数据 currentLevelData.retreatPoints.retreatPoints.Clear(); currentLevelData.retreatPoints.retreatFlag = -1; foreach (var retreatPoint in retreatEditorInfo) { if (retreatPoint.retreatFlag) currentLevelData.retreatPoints.retreatFlag = retreatPoint.retreatPoint; currentLevelData.retreatPoints.retreatPoints.Add(retreatPoint.retreatPoint); } // 撤离数据 currentLevelData.enemyRetreatReusablePoints.Clear(); foreach (var fallback in enemyRetreatEditorInfo.fallbackReusablePoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(fallback); currentLevelData.enemyRetreatReusablePoints.Add(cellIndex); } // 指定路径 currentLevelData.patrolRoadData = roadEditorInfos.Select(editorInfo => new PatrolData { id = editorInfo.roadID, patrolInfos = editorInfo.roadPoints, }).ToList(); // 指定区域 currentLevelData.patrolRegionData = areaEditorInfos.Select(editorInfo => new PatrolData { id = editorInfo.areaID, patrolInfos = editorInfo.areaPoints, }).ToList(); // 关卡进度 currentLevelData.progressGuideDatas = progressGuideDatas.Select(p => p.progressGuideData).ToList(); // 立标数据 currentLevelData.setFlagGuideDatas = setFlagEditorInfos.Select(f => f.flagGuideData).ToList(); // 信息板数据 currentLevelData.infoPanelGuideDatas = infoPanelGuideDatas.Select(i => i.infoPanelGuideData).ToList(); // 关卡广播 currentLevelData.listAIBroadcastData = listAIBroadcastData.Select(a => a.aiBroadcastData).ToList(); // 雷达信息 currentLevelData.radarInfos = radarInfoList.Select(r => r.radarInfo).ToList(); // 探照灯 currentLevelData.searchLightInfos = searchLightList.Select(s => s.info).ToList(); // 轰炸区 currentLevelData.bombAreaInfos = bombAreaInfoList.Select(b => b.info).ToList(); // 战旗 currentLevelData.holdGFlagInfos = holdFlagEditorList.Select(h => h.info).ToList(); // 阻挡球 SaveBlockSphereInfo(); _levelEditor.SaveLevelData(); _mapManager.SaveMapData(); } #endregion #region 关卡ID相关 /// /// 关卡ID 通过关卡数据 json文件名 获得 /// [LabelText("手动指定关卡ID"), Indent, LabelWidth(100), ShowIf("needManualInputLevelID")] [InfoBox("无法正确获得关卡ID,请手动指定")] [SerializeField] public string levelID; /// /// 需要手动输入关卡ID /// [HideInInspector] public bool needManualInputLevelID; #endregion #region Npc某些值改变时响应事件 private void ReorderEnemyNpcID() { for (var i = 0; i < currentEnemyNpcInfo.Count; i++) { currentEnemyNpcInfo[i].id = i + 1; currentEnemyNpcInfo[i].npcInfo.index = currentEnemyNpcInfo[i].id; } // 注意:波次存储的是怪物组ID,不是NPC ID,所以不需要更新波次信息 } private void ReorderMidNpcID() { for (var i = 0; i < currentMidNpcInfo.Count; i++) { currentMidNpcInfo[i].id = MidID + i + 1; currentMidNpcInfo[i].npcInfo.index = currentMidNpcInfo[i].id; } } private void ReorderSelfNpcID() { for (var i = 0; i < currentSelfNpcInfo.Count; i++) { currentSelfNpcInfo[i].id = SelfID + i + 1; } } private void ChangeEnemyNpcID(NpcEditorInfo npcEditorInfo, int newMonsterID) { if (_enemyNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var newObj = LoadEnemyNpcPrefab(newMonsterID); if (newObj != null) { var obj = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); if (enemyObjRoot == null) enemyObjRoot = new GameObject("enemyObjRoot"); if (obj != null) obj.transform.SetParent(enemyObjRoot.transform); npcEditorInfo.npcInfo.id = newMonsterID; npcEditorInfo.level = MonsterCfg.DataMap[newMonsterID].MonsterLevel; npcObjectDic[npcEditorInfo] = obj; DestroyImmediate(npcObj); DebugUtil.Log( $"从[ {npcEditorInfo.npcInfo.position} ]的怪物ID改为:[ {npcEditorInfo.npcInfo.id} ]"); } } } } private void ChangeMidNpcID(NpcEditorInfo npcEditorInfo, int newMonsterID) { if (_midNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var newObj = LoadMidNpcPrefab(newMonsterID); if (newObj != null) { var obj = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); if (midObjRoot == null) midObjRoot = new GameObject("midObjRoot"); if (obj != null) obj.transform.SetParent(midObjRoot.transform); npcEditorInfo.npcInfo.id = newMonsterID; npcEditorInfo.level = MonsterCfg.DataMap[newMonsterID].MonsterLevel; npcObjectDic[npcEditorInfo] = obj; DestroyImmediate(npcObj); DebugUtil.Log( $"从[ {npcEditorInfo.npcInfo.position} ]的怪物ID改为:[ {npcEditorInfo.npcInfo.id} ]"); } } } } private void ChangeSelfNpcID(NPCInfo npcInfoInfo, int newMonsterID) { if (_selfNpcObjectDic.TryGetValue(npcInfoInfo.position, out var npcObj)) { var newObj = LoadSelfNpcPrefab(newMonsterID); if (newObj != null) { var obj = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); if (selfObjRoot == null) selfObjRoot = new GameObject("selfObjRoot"); if (obj != null) obj.transform.SetParent(selfObjRoot.transform); npcInfoInfo.id = newMonsterID; _selfNpcObjectDic[npcInfoInfo.position] = obj; DestroyImmediate(npcObj); DebugUtil.Log("位置上[ {0} ]的fight怪物ID改为: [ {1} ] ", npcInfoInfo.position, npcInfoInfo.id); } } } #endregion }